auth-node: createOAuthHandleres -> createOAuthRouteHandlers + refactor state transform
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
+15
-11
@@ -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 {
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
export {
|
||||
createOAuthHandlers,
|
||||
type OAuthHandlersOptions,
|
||||
} from './createOAuthHandlers';
|
||||
createOAuthRouteHandlers,
|
||||
type OAuthRouteHandlersOptions,
|
||||
} from './createOAuthRouteHandlers';
|
||||
export {
|
||||
PassportOAuthAuthenticatorHelper,
|
||||
type PassportOAuthDoneCallback,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user