diff --git a/README.md b/README.md index 2c661d4..965dcb4 100644 --- a/README.md +++ b/README.md @@ -46,41 +46,35 @@ Content-Type: application/json } //DOCS: Result (access token's value is your authorization key below) +Set-Cookie: refreshToken + { - "accessToken": "abcde", - "refreshToken": "fghij" + "accessToken": "abcde" } ### -//DOCS: Replace an expired authToken pair with new values +//DOCS: Replace an expired accessToken with a new value POST /auth/token -Content-Type: application/json - -{ - "token": "refreshToken" -} +Cookie: refreshToken //DOCS: Result +Set-Cookie: refreshToken + { - "accessToken": "abcde", - "refreshToken": "fghij" + "accessToken": "abcde" } ### -//DOCS: After this is called, the refresh route will no longer work +//DOCS: After this is called, the /auth/token route will no longer work DELETE /auth/logout Authorization: Bearer accessToken - -{ - "token": "refreshToken" -} - +Cookie: refreshToken ### diff --git a/package-lock.json b/package-lock.json index df0e6c8..9ad3760 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "auth-server", - "version": "1.4.14", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "auth-server", - "version": "1.4.14", + "version": "1.6.0", "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", @@ -1057,9 +1057,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.6.tgz", - "integrity": "sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "node_modules/@types/prettier": { "version": "2.6.3", @@ -1517,9 +1517,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001369", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001369.tgz", - "integrity": "sha512-OY1SBHaodJc4wflDIKnlkdqWzJZd1Ls/2zbVJHBSv3AT7vgOJ58yAhd2CN4d57l2kPJrgMb7P9+N1Mhy4tNSQA==", + "version": "1.0.30001370", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001370.tgz", + "integrity": "sha512-3PDmaP56wz/qz7G508xzjx8C+MC2qEm4SYhSEzC9IBROo+dGXFWRuaXkWti0A9tuI00g+toiriVqxtWMgl350g==", "dev": true, "funding": [ { @@ -1694,9 +1694,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "engines": { "node": ">= 0.6" } @@ -1713,14 +1713,6 @@ "node": ">= 0.8.0" } }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -1925,9 +1917,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.199.tgz", - "integrity": "sha512-WIGME0Cs7oob3mxsJwHbeWkH0tYkIE/sjkJ8ML2BYmuRcjhRl/q5kVDXG7W9LOOKwzPU5M0LBlXRq9rlSgnNlg==", + "version": "1.4.200", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.200.tgz", + "integrity": "sha512-nPyI7oHc8T64oSqRXrAt99gNMpk0SAgPHw/o+hkNKyb5+bcdnFtZcSO9FUJES5cVkVZvo8u4qiZ1gQILl8UXsA==", "dev": true }, "node_modules/emittery": { @@ -2137,6 +2129,14 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6208,9 +6208,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "@types/node": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.6.tgz", - "integrity": "sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "@types/prettier": { "version": "2.6.3", @@ -6566,9 +6566,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001369", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001369.tgz", - "integrity": "sha512-OY1SBHaodJc4wflDIKnlkdqWzJZd1Ls/2zbVJHBSv3AT7vgOJ58yAhd2CN4d57l2kPJrgMb7P9+N1Mhy4tNSQA==", + "version": "1.0.30001370", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001370.tgz", + "integrity": "sha512-3PDmaP56wz/qz7G508xzjx8C+MC2qEm4SYhSEzC9IBROo+dGXFWRuaXkWti0A9tuI00g+toiriVqxtWMgl350g==", "dev": true }, "chalk": { @@ -6699,9 +6699,9 @@ } }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, "cookie-parser": { "version": "1.4.6", @@ -6710,13 +6710,6 @@ "requires": { "cookie": "0.4.1", "cookie-signature": "1.0.6" - }, - "dependencies": { - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" - } } }, "cookie-signature": { @@ -6884,9 +6877,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.199.tgz", - "integrity": "sha512-WIGME0Cs7oob3mxsJwHbeWkH0tYkIE/sjkJ8ML2BYmuRcjhRl/q5kVDXG7W9LOOKwzPU5M0LBlXRq9rlSgnNlg==", + "version": "1.4.200", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.200.tgz", + "integrity": "sha512-nPyI7oHc8T64oSqRXrAt99gNMpk0SAgPHw/o+hkNKyb5+bcdnFtZcSO9FUJES5cVkVZvo8u4qiZ1gQILl8UXsA==", "dev": true }, "emittery": { @@ -7039,6 +7032,13 @@ "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + } } }, "fast-json-stable-stringify": { diff --git a/package.json b/package.json index 5101299..43ffe7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "auth-server", - "version": "1.4.14", + "version": "1.6.0", "description": "An API centric auth server. Uses Sequelize and mariaDB by default.", "main": "server/server.js", "scripts": { diff --git a/tools/react/README.md b/tools/react/README.md index 8d85b13..70aa599 100644 --- a/tools/react/README.md +++ b/tools/react/README.md @@ -47,7 +47,7 @@ export default Component; The most useful features provided by TokenProvider are: * `tokenFetch()`, which wraps the `fetch()` API to ensure that your access token is valid -* `tokenCallback()`, which passes the authTokens as a parameter to any function passed into it +* `tokenCallback()`, which passes the accessToken as a parameter to any function passed into it * `getPayload()`, which returns the payload of the accessToken (including as "email", "username", "admin", and "mod") * `accessToken`, this will be falsy if the user is not logged in diff --git a/tools/react/token-provider.jsx b/tools/react/token-provider.jsx new file mode 100644 index 0000000..6ad9614 --- /dev/null +++ b/tools/react/token-provider.jsx @@ -0,0 +1,107 @@ +import React, { useState, useEffect, createContext } from 'react'; +import decode from 'jwt-decode'; + +export const TokenContext = createContext(); + +//DOCS: tokenFetch() and tokenCallback() are actually closures here + +const TokenProvider = props => { + //state to be used + const [accessToken, setAccessToken] = useState(''); + + //make the access token persist between reloads + useEffect(() => { + setAccessToken(localStorage.getItem("accessToken") || ''); + }, []); + + //update the stored copies + useEffect(() => { + localStorage.setItem("accessToken", accessToken); + }, [accessToken]); + + //wrap the default fetch function + const tokenFetch = async (url, options) => { + //use this? + let bearer = accessToken; + + //if expired (10 minutes, normally) + const expired = new Date(decode(accessToken).exp * 1000) < Date.now(); + + if (expired) { + //BUGFIX: if logging out, just skip over the refresh token + if (url === `${process.env.AUTH_URI}/auth/logout`) { + return fetch(url, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${bearer}` + }, + credentials: 'include' + }); + } + + //ping the auth server for a new access token + const response = await fetch(`${process.env.AUTH_URI}/auth/token`, { + method: 'POST', + credentials: 'include' + }); + + //any errors, throw them + if (!response.ok) { + throw `${response.status}: ${await response.text()}`; + } + + //save the new auth stuff (setting bearer as well) + const newAuth = await response.json(); + + setAccessToken(newAuth.accessToken); + bearer = newAuth.accessToken; + } + + //finally, delegate to fetch + return fetch(url, { + ...(options || {}), + headers: { + ...(options || { headers: {} }).headers, + 'Authorization': `Bearer ${bearer}` + }, + credentials: 'include' + }); + }; + + //access the refreshed token via callback + const tokenCallback = async (cb) => { + //if expired (10 minutes, normally) + const expired = new Date(decode(accessToken).exp * 1000) < Date.now(); + + if (expired) { + //ping the auth server for a new token + const response = await fetch(`${process.env.AUTH_URI}/auth/token`, { + method: 'POST', + credentials: 'include' + }); + + //any errors, throw them + if (!response.ok) { + throw `${response.status}: ${await response.text()}`; + } + + //save the new auth stuff (setting bearer as well) + const newAuth = await response.json(); + + setAccessToken(newAuth.accessToken); + + //finally + return cb(newAuth.accessToken); + } else { + return cb(accessToken); + } + }; + + return ( + decode(accessToken) }}> + {props.children} + + ) +}; + +export default TokenProvider;