Merge pull request #7409 from RoadieHQ/ch3367

Adopt extra field for OAuth state
This commit is contained in:
Patrik Oldsberg
2021-10-28 16:01:48 +02:00
committed by GitHub
5 changed files with 53 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Allow OAuth state to be encoded by a stateEncoder.
+9 -2
View File
@@ -293,6 +293,7 @@ export type GithubProviderOptions = {
signIn?: {
resolver?: SignInResolver<GithubOAuthResult>;
};
stateEncoder?: StateEncoder;
};
// Warning: (ae-missing-release-tag) "GitlabProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -582,6 +583,12 @@ export type WebMessageResponse =
// src/providers/atlassian/provider.d.ts:37:5 - (ae-forgotten-export) The symbol "AuthHandler" needs to be exported by the entry point index.d.ts
// src/providers/atlassian/provider.d.ts:42:9 - (ae-forgotten-export) The symbol "SignInResolver" needs to be exported by the entry point index.d.ts
// src/providers/aws-alb/provider.d.ts:77:5 - (ae-forgotten-export) The symbol "AwsAlbResult" needs to be exported by the entry point index.d.ts
// src/providers/types.d.ts:99:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts
// src/providers/types.d.ts:121:8 - (tsdoc-missing-deprecation-message) The @deprecated block must include a deprecation message, e.g. describing the recommended alternative
// src/providers/github/provider.d.ts:71:58 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
// src/providers/github/provider.d.ts:71:90 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
// src/providers/github/provider.d.ts:71:89 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/providers/github/provider.d.ts:71:67 - (tsdoc-malformed-html-name) Invalid HTML element: Expecting an HTML name
// src/providers/github/provider.d.ts:71:68 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/providers/github/provider.d.ts:78:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts
// src/providers/types.d.ts:100:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts
// src/providers/types.d.ts:122:8 - (tsdoc-missing-deprecation-message) The @deprecated block must include a deprecation message, e.g. describing the recommended alternative
```
@@ -25,6 +25,7 @@ import {
} from './provider';
import * as helpers from '../../lib/passport/PassportStrategyHelper';
import { makeProfileInfo } from '../../lib/passport/PassportStrategyHelper';
import { OAuthStartRequest, encodeState } from '../../lib/oauth';
const mockFrameHandler = jest.spyOn(
helpers,
@@ -56,6 +57,9 @@ describe('GithubAuthProvider', () => {
authHandler: async ({ fullProfile }) => ({
profile: makeProfileInfo(fullProfile),
}),
stateEncoder: async (req: OAuthStartRequest) => ({
encodedState: encodeState(req.state),
}),
callbackUrl: 'mock',
clientId: 'mock',
clientSecret: 'mock',
@@ -31,6 +31,7 @@ import {
AuthProviderFactory,
AuthHandler,
SignInResolver,
StateEncoder,
} from '../types';
import {
OAuthAdapter,
@@ -66,6 +67,7 @@ export type GithubAuthProviderOptions = OAuthProviderOptions & {
authorizationUrl?: string;
signInResolver?: SignInResolver<GithubOAuthResult>;
authHandler: AuthHandler<GithubOAuthResult>;
stateEncoder: StateEncoder;
tokenIssuer: TokenIssuer;
catalogIdentityClient: CatalogIdentityClient;
logger: Logger;
@@ -78,10 +80,12 @@ export class GithubAuthProvider implements OAuthHandlers {
private readonly tokenIssuer: TokenIssuer;
private readonly catalogIdentityClient: CatalogIdentityClient;
private readonly logger: Logger;
private readonly stateEncoder: StateEncoder;
constructor(options: GithubAuthProviderOptions) {
this.signInResolver = options.signInResolver;
this.authHandler = options.authHandler;
this.stateEncoder = options.stateEncoder;
this.tokenIssuer = options.tokenIssuer;
this.catalogIdentityClient = options.catalogIdentityClient;
this.logger = options.logger;
@@ -109,7 +113,7 @@ export class GithubAuthProvider implements OAuthHandlers {
async start(req: OAuthStartRequest): Promise<RedirectInfo> {
return await executeRedirectStrategy(req, this._strategy, {
scope: req.scope,
state: encodeState(req.state),
state: (await this.stateEncoder(req)).encodedState,
});
}
@@ -209,6 +213,24 @@ export type GithubProviderOptions = {
*/
resolver?: SignInResolver<GithubOAuthResult>;
};
/**
* The state encoder used to encode the 'state' parameter on the OAuth request.
*
* It should return a string that takes the state params (from the request), url encodes the params
* and finally base64 encodes them.
*
* Providing your own stateEncoder will allow you to add addition parameters to the state field.
*
* It is typed as follows:
* export type StateEncoder = (input: OAuthState) => Promise<{encodedState: string}>;
*
* Note: the stateEncoder must encode a 'nonce' value and an 'env' value. Without this, the OAuth flow will fail
* (These two values will be set by the req.state by default)
*
* For more information, please see the helper module in ../../oauth/helpers #readState
*/
stateEncoder?: StateEncoder;
};
export const createGithubProvider = (
@@ -263,6 +285,12 @@ export const createGithubProvider = (
logger,
});
const stateEncoder: StateEncoder =
options?.stateEncoder ??
(async (req: OAuthStartRequest): Promise<{ encodedState: string }> => {
return { encodedState: encodeState(req.state) };
});
const provider = new GithubAuthProvider({
clientId,
clientSecret,
@@ -274,6 +302,7 @@ export const createGithubProvider = (
authHandler,
tokenIssuer,
catalogIdentityClient,
stateEncoder,
logger,
});
@@ -21,6 +21,7 @@ import { Config } from '@backstage/config';
import express from 'express';
import { Logger } from 'winston';
import { TokenIssuer } from '../identity/types';
import { OAuthStartRequest } from '../lib/oauth/types';
import { CatalogIdentityClient } from '../lib/catalog';
export type AuthProviderConfig = {
@@ -223,3 +224,7 @@ export type AuthHandlerResult = { profile: ProfileInfo };
export type AuthHandler<AuthResult> = (
input: AuthResult,
) => Promise<AuthHandlerResult>;
export type StateEncoder = (
req: OAuthStartRequest,
) => Promise<{ encodedState: string }>;