Add support for callbackUrl when setting cookie configuration

Signed-off-by: Marcus Eide <eide@spotify.com>
This commit is contained in:
Marcus Eide
2022-01-24 10:55:42 +01:00
parent 52621b9755
commit aec40d321d
3 changed files with 41 additions and 9 deletions
@@ -35,7 +35,7 @@ import {
NotAllowedError,
} from '@backstage/errors';
import { TokenIssuer } from '../../identity/types';
import { readState, verifyNonce } from './helpers';
import { getCookieConfig, readState, verifyNonce } from './helpers';
import { postMessageResponse, ensuresXRequestedWith } from '../flow';
import {
OAuthHandlers,
@@ -58,25 +58,32 @@ export type Options = {
appOrigin: string;
tokenIssuer: TokenIssuer;
isOriginAllowed: (origin: string) => boolean;
callbackUrl?: string;
};
export class OAuthAdapter implements AuthProviderRouteHandlers {
static fromConfig(
config: AuthProviderConfig,
handlers: OAuthHandlers,
options: Pick<
Options,
'providerId' | 'persistScopes' | 'disableRefresh' | 'tokenIssuer'
| 'providerId'
| 'persistScopes'
| 'disableRefresh'
| 'tokenIssuer'
| 'callbackUrl'
>,
): OAuthAdapter {
const { origin: appOrigin } = new URL(config.appUrl);
const secure = config.baseUrl.startsWith('https://');
const url = new URL(config.baseUrl);
const cookiePath = `${url.pathname}/${options.providerId}`;
const authUrl = new URL(options.callbackUrl ?? config.baseUrl);
const { cookieDomain, cookiePath, secure } = getCookieConfig(
authUrl,
options.providerId,
);
return new OAuthAdapter(handlers, {
...options,
appOrigin,
cookieDomain: url.hostname,
cookieDomain,
cookiePath,
secure,
isOriginAllowed: config.isOriginAllowed,
@@ -263,7 +270,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
secure: this.options.secure,
sameSite: 'lax',
domain: this.options.cookieDomain,
path: `${this.options.cookiePath}/handler`,
path: this.options.cookiePath,
httpOnly: true,
});
};
@@ -274,7 +281,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
secure: this.options.secure,
sameSite: 'lax',
domain: this.options.cookieDomain,
path: `${this.options.cookiePath}/handler`,
path: this.options.cookiePath,
httpOnly: true,
});
};
@@ -57,3 +57,27 @@ export const verifyNonce = (req: express.Request, providerId: string) => {
throw new Error('Invalid nonce');
}
};
export const getCookieConfig = (authUrl: URL, providerId: string) => {
const { hostname: cookieDomain, pathname, protocol } = authUrl;
const secure = protocol === 'https:';
// If the provider supports callbackUrls, the pathname will
// contain the complete path to the frame handler.
// The regex captures the leading path including the provider.
//
// Example match: /api/auth/provider/handler/frame
// Example result: /api/auth/provider
const matcher = pathname.match(
new RegExp(`(?<path>^\/.*?\/${providerId})\/handler\/frame$`),
)?.groups;
// If there's no match we can manually construct the path
const cookiePath = matcher?.path ?? `${pathname}/${providerId}`;
return {
cookieDomain,
cookiePath,
secure,
};
};
@@ -313,6 +313,7 @@ export const createGithubProvider = (
persistScopes: true,
providerId,
tokenIssuer,
callbackUrl,
});
});
};