forked from wpengine/faustjs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FaustNX Auth Utilities (wpengine#947)
* Setup config getter/setter * Add proper auth functionality to FaustNX * Add tests / folder structure * Delete coverage * Fix tests for FaustNX * Delete coverage files * Add back `.gitignore` * Add exports to `faust-nx` * Add faustnx scripts to monorepo root * Update docs site package lock * Downgrade `@ypes/react` * Add faustnx workspace to clean script Co-authored-by: Joseph Fusco <[email protected]> * Add clean script to FaustNX * Add FaustNX codecoverage check Signed-off-by: Joe Fusco <[email protected]> * `skipLibCheck` in TS Config * Add jest npm scripts Signed-off-by: Joe Fusco <[email protected]> * Add CJS/MJS builds for FaustNX * Fix package.json * Clean before build & sort scripts A->Z Signed-off-by: Joe Fusco <[email protected]> * Revert "Add FaustNX codecoverage check" This reverts commit 972283f. Signed-off-by: Joe Fusco <[email protected]> * Fix CJS/MJS dist builds Co-authored-by: Joseph Fusco <[email protected]> Co-authored-by: Joe Fusco <[email protected]>
- Loading branch information
1 parent
a585eff
commit 148a1dc
Showing
39 changed files
with
8,708 additions
and
4,058 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
dist | ||
jest.setup.js | ||
jest.setup.d.ts | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module.exports = { | ||
roots: ['<rootDir>/tests'], | ||
|
||
// Adds Jest support for TypeScript using ts-jest. | ||
transform: { | ||
'^.+\\.tsx?$': 'ts-jest', | ||
}, | ||
|
||
// Run code before each file in the suite is tested. | ||
setupFilesAfterEnv: ['./jest.setup.ts'], | ||
|
||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', | ||
|
||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], | ||
|
||
// ESM Support | ||
// @link https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/ | ||
extensionsToTreatAsEsm: ['.ts'], | ||
globals: { | ||
'ts-jest': { | ||
useESM: true, | ||
}, | ||
}, | ||
moduleNameMapper: { | ||
'^(\\.{1,2}/.*)\\.js$': '$1', | ||
}, | ||
collectCoverage: true, | ||
coverageReporters: ['json', 'html'], | ||
passWithNoTests: true, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import '@testing-library/jest-dom'; // For custom matchers. See https://github.com/testing-library/jest-dom#custom-matchers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import 'isomorphic-fetch'; | ||
import isString from 'lodash/isString.js'; | ||
import { getWpUrl } from '../lib/getWpUrl.js'; | ||
import { getQueryParam, removeURLParam } from '../utils/index.js'; | ||
import { fetchAccessToken } from './client/accessToken.js'; | ||
|
||
export interface EnsureAuthorizationOptions { | ||
redirectUri?: string; | ||
loginPageUri?: string; | ||
} | ||
|
||
/* eslint-disable consistent-return */ | ||
/** | ||
* Checks for an existing Access Token and returns one if it exists. Otherwise returns | ||
* an object containing a redirect URI to send the client to for authorization. | ||
* | ||
* @export | ||
* @param {string} EnsureAuthorizationOptions | ||
* @returns {(string | { redirect: string })} | ||
*/ | ||
export async function ensureAuthorization( | ||
options?: EnsureAuthorizationOptions, | ||
): Promise< | ||
true | { redirect?: string | undefined; login?: string | undefined } | ||
> { | ||
const wpUrl = getWpUrl(); | ||
const { redirectUri, loginPageUri } = options || {}; | ||
|
||
// Get the authorization code from the URL if it exists | ||
const code: string | undefined = | ||
typeof window !== 'undefined' | ||
? getQueryParam(window.location.href, 'code') | ||
: undefined; | ||
|
||
const unauthorized: { redirect?: string; login?: string } = {}; | ||
|
||
if (isString(redirectUri)) { | ||
unauthorized.redirect = `${wpUrl}/generate?redirect_uri=${encodeURIComponent( | ||
redirectUri, | ||
)}`; | ||
} | ||
|
||
if (isString(loginPageUri)) { | ||
unauthorized.login = loginPageUri; | ||
} | ||
|
||
const token = await fetchAccessToken(code); | ||
|
||
if (!token) { | ||
return unauthorized; | ||
} | ||
|
||
if (code) { | ||
window.history.replaceState( | ||
{}, | ||
document.title, | ||
removeURLParam(window.location.href, 'code'), | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
/* eslint-enable consistent-return */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import { | ||
FAUSTNX_API_BASE_PATH, | ||
TOKEN_ENDPOINT_PARTIAL_PATH, | ||
} from '../../lib/constants.js'; | ||
import { isServerSide } from '../../utils/index.js'; | ||
import isString from 'lodash/isString.js'; | ||
|
||
export interface AccessToken { | ||
/** | ||
* Base 64 encoded access token | ||
*/ | ||
token: string | undefined; | ||
/** | ||
* The time in seconds until the access token expires. | ||
*/ | ||
expiration: number | undefined; | ||
} | ||
|
||
export type RefreshTimer = ReturnType<typeof setTimeout> | undefined; | ||
|
||
/** | ||
* The amount of time in seconds until the access token is fetched | ||
* before it expires. | ||
* | ||
* For example, if the access token expires in 5 minutes (300 seconds), and | ||
* this value is 60, then the access token will be refreshed at 240 seconds. | ||
* | ||
* This allows for enough time to fetch a new access token before it expires. | ||
* | ||
*/ | ||
export const TIME_UNTIL_REFRESH_BEFORE_TOKEN_EXPIRES = 60; | ||
|
||
/** | ||
* The setTimeout instance that refreshes the access token. | ||
*/ | ||
let __REFRESH_TIMER: RefreshTimer = undefined; | ||
|
||
export function getRefreshTimer(): RefreshTimer { | ||
return __REFRESH_TIMER; | ||
} | ||
|
||
export function setRefreshTimer(timer: RefreshTimer): void { | ||
__REFRESH_TIMER = timer; | ||
} | ||
|
||
/** | ||
* The access token object | ||
*/ | ||
let accessToken: AccessToken | undefined; | ||
|
||
/** | ||
* Get an access token from memory if one exists | ||
* | ||
* @returns {string | undefined} | ||
*/ | ||
export function getAccessToken(): string | undefined { | ||
return accessToken?.token; | ||
} | ||
|
||
/** | ||
* Get an access token expiration from memory if one exists | ||
* | ||
* @returns {number | undefined} | ||
*/ | ||
export function getAccessTokenExpiration(): number | undefined { | ||
return accessToken?.expiration; | ||
} | ||
|
||
/** | ||
* Set an access token and/or its expiration in memory | ||
* | ||
* @param {string} token | ||
* @param {number} expiration | ||
* | ||
* @returns {void} | ||
*/ | ||
export function setAccessToken( | ||
token: string | undefined, | ||
expiration: number | undefined, | ||
): void { | ||
if (isServerSide()) { | ||
return; | ||
} | ||
|
||
accessToken = { | ||
token, | ||
expiration, | ||
}; | ||
} | ||
|
||
/** | ||
* Creates the access token refresh timer that will fetch a new access token | ||
* before the current one expires. | ||
* | ||
* @returns {void} | ||
*/ | ||
export function setAccessTokenRefreshTimer(): void { | ||
const currentTimeInSeconds = Math.floor(Date.now() / 1000); | ||
const accessTokenExpirationInSeconds = getAccessTokenExpiration(); | ||
|
||
// If there is no access token/expiration, don't create a timer. | ||
if (accessTokenExpirationInSeconds === undefined) { | ||
return; | ||
} | ||
|
||
const secondsUntilExpiration = | ||
accessTokenExpirationInSeconds - currentTimeInSeconds; | ||
const secondsUntilRefresh = | ||
secondsUntilExpiration - TIME_UNTIL_REFRESH_BEFORE_TOKEN_EXPIRES; | ||
|
||
setRefreshTimer( | ||
setTimeout(() => void fetchAccessToken(), secondsUntilRefresh * 1000), | ||
); | ||
} | ||
|
||
/** | ||
* Clears the current access token refresh timer if one exists. | ||
*/ | ||
export function clearAccessTokenRefreshTimer(): void { | ||
const timer = getRefreshTimer(); | ||
if (timer !== undefined) { | ||
clearTimeout(timer); | ||
} | ||
} | ||
|
||
/** | ||
* Fetch an access token from the authorizeHandler middleware | ||
* | ||
* @export | ||
* @param {string} code An authorization code to fetch an access token | ||
*/ | ||
export async function fetchAccessToken(code?: string): Promise<string | null> { | ||
let url = `${FAUSTNX_API_BASE_PATH}/${TOKEN_ENDPOINT_PARTIAL_PATH}`; | ||
|
||
// Add the code to the url if it exists | ||
if (isString(code) && code.length > 0) { | ||
url += `?code=${code}`; | ||
} | ||
|
||
try { | ||
const response = await fetch(url, { | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
|
||
const result = (await response.json()) as { | ||
accessToken: string; | ||
accessTokenExpiration: number; | ||
}; | ||
|
||
// If the response is not ok, clear the access token | ||
if (!response.ok) { | ||
setAccessToken(undefined, undefined); | ||
return null; | ||
} | ||
|
||
setAccessToken(result.accessToken, result.accessTokenExpiration); | ||
|
||
// If there is an existing refresh timer, clear it. | ||
clearAccessTokenRefreshTimer(); | ||
|
||
/** | ||
* Set a refresh timer to fetch a new access token before | ||
* the current one expires. | ||
*/ | ||
setAccessTokenRefreshTimer(); | ||
|
||
return result.accessToken; | ||
} catch (error) { | ||
setAccessToken(undefined, undefined); | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './authorize.js'; | ||
export * from './client/accessToken.js'; |
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const FAUSTNX_API_BASE_PATH = '/api/faust'; | ||
export const TOKEN_ENDPOINT_PARTIAL_PATH = 'auth/token'; | ||
export const LOGOUT_ENDPOINT_PARTIAL_PATH = 'auth/logout'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function getWpSecret() { | ||
return process.env.FAUSTNX_SECRET_KEY; | ||
} |
Oops, something went wrong.