auth-node: createOAuthHandleres -> createOAuthRouteHandlers + refactor state transform

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-07-29 11:19:31 +02:00
parent 5195c2adaa
commit 3c1df5d4a9
3 changed files with 26 additions and 27 deletions
@@ -23,7 +23,12 @@ import {
isError,
NotAllowedError,
} from '@backstage/errors';
import { defaultStateDecoder, defaultStateEncoder, OAuthState } from './state';
import {
encodeOAuthState,
decodeOAuthState,
OAuthStateTransform,
OAuthState,
} from './state';
import { sendWebMessageResponse } from '../flow';
import { prepareBackstageIdentityResponse } from '../identity';
import { OAuthCookieManager } from './OAuthCookieManager';
@@ -42,15 +47,15 @@ import {
import { Config } from '@backstage/config';
/** @public */
export interface OAuthHandlersOptions<TProfile> {
authenticator: OAuthAuthenticator<unknown, TProfile>;
export interface OAuthRouteHandlersOptions<TProfile> {
authenticator: OAuthAuthenticator<any, TProfile>;
appUrl: string;
baseUrl: string;
isOriginAllowed: (origin: string) => boolean;
callbackUrl: string;
providerId: string;
config: Config;
resolverContext: AuthResolverContext;
stateTransform?: OAuthStateTransform;
profileTransform?: OAuthProfileTransform<TProfile>;
cookieConfigurer?: CookieConfigurer;
signInResolver?: SignInResolver<OAuthAuthenticatorResult<TProfile>>;
@@ -77,8 +82,8 @@ type ClientOAuthResponse = ClientAuthResponse<{
}>;
/** @public */
export function createOAuthHandlers<TProfile>(
options: OAuthHandlersOptions<TProfile>,
export function createOAuthRouteHandlers<TProfile>(
options: OAuthRouteHandlersOptions<TProfile>,
): AuthProviderRouteHandlers {
const {
authenticator,
@@ -97,6 +102,7 @@ export function createOAuthHandlers<TProfile>(
config.getOptionalString('callbackUrl') ??
`${baseUrl}/${providerId}/handler/frame`;
const stateTransform = options.stateTransform ?? (state => ({ state }));
const profileTransform =
options.profileTransform ?? authenticator.defaultProfileTransform;
const authenticatorCtx = authenticator.initialize({ config, callbackUrl });
@@ -137,7 +143,8 @@ export function createOAuthHandlers<TProfile>(
state.scope = scope;
}
const { encodedState } = await defaultStateEncoder(state, { req });
const { state: transformedState } = await stateTransform(state, { req });
const encodedState = encodeOAuthState(transformedState);
const { url, status } = await options.authenticator.start(
{ req, scope, state: encodedState },
@@ -158,10 +165,7 @@ export function createOAuthHandlers<TProfile>(
let appOrigin = defaultAppOrigin;
try {
const { state } = await defaultStateDecoder(
req.query.state?.toString() ?? '',
{ req },
);
const state = decodeOAuthState(req.query.state?.toString() ?? '');
if (state.origin) {
try {
+3 -3
View File
@@ -15,9 +15,9 @@
*/
export {
createOAuthHandlers,
type OAuthHandlersOptions,
} from './createOAuthHandlers';
createOAuthRouteHandlers,
type OAuthRouteHandlersOptions,
} from './createOAuthRouteHandlers';
export {
PassportOAuthAuthenticatorHelper,
type PassportOAuthDoneCallback,
+8 -13
View File
@@ -17,6 +17,7 @@
import pickBy from 'lodash/pickBy';
import { Request } from 'express';
/** @public */
export type OAuthState = {
/* A type for the serialized value in the `state` parameter of the OAuth authorization flow
*/
@@ -29,28 +30,22 @@ export type OAuthState = {
};
/** @public */
export type OAuthStateEncoder = (
export type OAuthStateTransform = (
state: OAuthState,
context: { req: Request },
) => Promise<{ encodedState: string }>;
/** @public */
export type OAuthStateDecoder = (
encodedState: string,
context: { req: Request },
) => Promise<{ state: OAuthState }>;
/** @public */
export const defaultStateEncoder: OAuthStateEncoder = async state => {
export function encodeOAuthState(state: OAuthState): string {
const stateString = new URLSearchParams(
pickBy<string>(state, value => value !== undefined),
).toString();
return { encodedState: Buffer.from(stateString, 'utf-8').toString('hex') };
};
return Buffer.from(stateString, 'utf-8').toString('hex');
}
/** @public */
export const defaultStateDecoder: OAuthStateDecoder = async encodedState => {
export function decodeOAuthState(encodedState: string): OAuthState {
const state = Object.fromEntries(
new URLSearchParams(Buffer.from(encodedState, 'hex').toString('utf-8')),
);
@@ -63,5 +58,5 @@ export const defaultStateDecoder: OAuthStateDecoder = async encodedState => {
throw Error(`Invalid state passed via request`);
}
return { state: state as OAuthState };
};
return state as OAuthState;
}