Skip to content

Commit

Permalink
Allow lazy config in auth0-session
Browse files Browse the repository at this point in the history
  • Loading branch information
adamjmcgrath committed Nov 7, 2023
1 parent f78b7f5 commit 30c100e
Show file tree
Hide file tree
Showing 18 changed files with 206 additions and 108 deletions.
7 changes: 5 additions & 2 deletions src/auth0-session/client/abstract-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Config } from '../config';
import { Config, GetConfig } from '../config';
import { Auth0Request } from '../http';

export type Telemetry = {
Expand Down Expand Up @@ -85,7 +85,10 @@ export interface AuthorizationParameters {
}

export abstract class AbstractClient {
constructor(protected config: Config, protected telemetry: Telemetry) {}
protected getConfig: () => Config | Promise<Config>;
constructor(getConfig: GetConfig, protected telemetry: Telemetry) {
this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
}
abstract authorizationUrl(parameters: Record<string, unknown>): Promise<string>;
abstract callbackParams(req: Auth0Request, expectedState: string): Promise<URLSearchParams>;
abstract callback(
Expand Down
90 changes: 43 additions & 47 deletions src/auth0-session/client/edge-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import {
OpenIDCallbackChecks,
TokenEndpointResponse,
AbstractClient,
EndSessionParameters,
Telemetry
EndSessionParameters
} from './abstract-client';
import { ApplicationError, DiscoveryError, IdentityProviderError, UserInfoError } from '../utils/errors';
import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors';
import urlJoin from 'url-join';
import { Config } from '../config';

const encodeBase64 = (input: string) => {
const unencoded = new TextEncoder().encode(input);
Expand All @@ -28,68 +26,66 @@ const encodeBase64 = (input: string) => {
export class EdgeClient extends AbstractClient {
private client?: oauth.Client;
private as?: oauth.AuthorizationServer;
private httpOptions: () => oauth.HttpRequestOptions;

constructor(protected config: Config, protected telemetry: Telemetry) {
super(config, telemetry);
if (config.authorizationParams.response_type !== 'code') {
throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
private async httpOptions(): Promise<oauth.HttpRequestOptions> {
const headers = new Headers();
const config = await this.getConfig();
if (config.enableTelemetry) {
const { name, version } = this.telemetry;
headers.set('User-Agent', `${name}/${version}`);
headers.set(
'Auth0-Client',
encodeBase64(
JSON.stringify({
name,
version,
env: {
edge: true
}
})
)
);
}

this.httpOptions = () => {
const headers = new Headers();
if (config.enableTelemetry) {
const { name, version } = telemetry;
headers.set('User-Agent', `${name}/${version}`);
headers.set(
'Auth0-Client',
encodeBase64(
JSON.stringify({
name,
version,
env: {
edge: true
}
})
)
);
}
return {
signal: AbortSignal.timeout(this.config.httpTimeout),
headers
};
return {
signal: AbortSignal.timeout(config.httpTimeout),
headers
};
}

private async getClient(): Promise<[oauth.AuthorizationServer, oauth.Client]> {
if (this.as) {
return [this.as, this.client as oauth.Client];
}
const config = await this.getConfig();
if (config.authorizationParams.response_type !== 'code') {
throw new Error('This SDK only supports `response_type=code` when used in an Edge runtime.');
}

const issuer = new URL(this.config.issuerBaseURL);
const issuer = new URL(config.issuerBaseURL);
try {
this.as = await oauth
.discoveryRequest(issuer, this.httpOptions())
.discoveryRequest(issuer, await this.httpOptions())
.then((response) => oauth.processDiscoveryResponse(issuer, response));
} catch (e) {
throw new DiscoveryError(e, this.config.issuerBaseURL);
throw new DiscoveryError(e, config.issuerBaseURL);
}

this.client = {
client_id: this.config.clientID,
...(!this.config.clientAssertionSigningKey && { client_secret: this.config.clientSecret }),
token_endpoint_auth_method: this.config.clientAuthMethod,
id_token_signed_response_alg: this.config.idTokenSigningAlg,
[oauth.clockTolerance]: this.config.clockTolerance
client_id: config.clientID,
...(!config.clientAssertionSigningKey && { client_secret: config.clientSecret }),
token_endpoint_auth_method: config.clientAuthMethod,
id_token_signed_response_alg: config.idTokenSigningAlg,
[oauth.clockTolerance]: config.clockTolerance
};

return [this.as, this.client];
}

async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
const [as] = await this.getClient();
const config = await this.getConfig();
const authorizationUrl = new URL(as.authorization_endpoint as string);
authorizationUrl.searchParams.set('client_id', this.config.clientID);
authorizationUrl.searchParams.set('client_id', config.clientID);
Object.entries(parameters).forEach(([key, value]) => {
if (value === null || value === undefined) {
return;
Expand Down Expand Up @@ -126,8 +122,7 @@ export class EdgeClient extends AbstractClient {
extras: CallbackExtras
): Promise<TokenEndpointResponse> {
const [as, client] = await this.getClient();

const { clientAssertionSigningKey, clientAssertionSigningAlg } = this.config;
const { clientAssertionSigningKey, clientAssertionSigningAlg } = await this.getConfig();

let clientPrivateKey = clientAssertionSigningKey as CryptoKey | undefined;
/* c8 ignore next 3 */
Expand Down Expand Up @@ -167,15 +162,16 @@ export class EdgeClient extends AbstractClient {
async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
const [as] = await this.getClient();
const issuerUrl = new URL(as.issuer);
const config = await this.getConfig();

if (
this.config.idpLogout &&
(this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
config.idpLogout &&
(config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false))
) {
const { id_token_hint, post_logout_redirect_uri, ...extraParams } = parameters;
const auth0LogoutUrl: URL = new URL(urlJoin(as.issuer, '/v2/logout'));
post_logout_redirect_uri && auth0LogoutUrl.searchParams.set('returnTo', post_logout_redirect_uri);
auth0LogoutUrl.searchParams.set('client_id', this.config.clientID);
auth0LogoutUrl.searchParams.set('client_id', config.clientID);
Object.entries(extraParams).forEach(([key, value]: [string, string]) => {
if (value === null || value === undefined) {
return;
Expand All @@ -195,13 +191,13 @@ export class EdgeClient extends AbstractClient {
oidcLogoutUrl.searchParams.set(key, value);
});

oidcLogoutUrl.searchParams.set('client_id', this.config.clientID);
oidcLogoutUrl.searchParams.set('client_id', config.clientID);
return oidcLogoutUrl.toString();
}

async userinfo(accessToken: string): Promise<Record<string, unknown>> {
const [as, client] = await this.getClient();
const response = await oauth.userInfoRequest(as, client, accessToken, this.httpOptions());
const response = await oauth.userInfoRequest(as, client, accessToken, await this.httpOptions());

try {
return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response);
Expand Down
8 changes: 3 additions & 5 deletions src/auth0-session/client/node-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ export class NodeClient extends AbstractClient {
return this.client;
}
const {
config,
getConfig,
telemetry: { name, version }
} = this;
const config = await getConfig();

const defaultHttpOptions: CustomHttpOptionsProvider = (_url, options) => ({
...options,
Expand Down Expand Up @@ -132,10 +133,7 @@ export class NodeClient extends AbstractClient {
const issuerUrl = new URL(issuer.metadata.issuer);

if (config.idpLogout) {
if (
this.config.idpLogout &&
(this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
) {
if (config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && config.auth0Logout !== false)) {
Object.defineProperty(this.client, 'endSessionUrl', {
value(params: EndSessionParameters) {
const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;
Expand Down
2 changes: 2 additions & 0 deletions src/auth0-session/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,5 @@ export interface LogoutOptions {
*/
logoutParams?: { [key: string]: any };
}

export type GetConfig = Config | (() => Config | Promise<Config>);
6 changes: 4 additions & 2 deletions src/auth0-session/handlers/callback.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import urlJoin from 'url-join';
import { AuthorizationParameters, Config } from '../config';
import { AuthorizationParameters, GetConfig, Config } from '../config';
import TransientStore from '../transient-store';
import { decodeState } from '../utils/encoding';
import { SessionCache } from '../session-cache';
Expand All @@ -25,12 +25,14 @@ export type CallbackOptions = {
export type HandleCallback = (req: Auth0Request, res: Auth0Response, options?: CallbackOptions) => Promise<void>;

export default function callbackHandlerFactory(
config: Config,
getConfig: GetConfig,
client: AbstractClient,
sessionCache: SessionCache,
transientCookieHandler: TransientStore
): HandleCallback {
const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
return async (req, res, options) => {
const config = await getConfigFn();
const redirectUri = options?.redirectUri || getRedirectUri(config);

let tokenResponse;
Expand Down
6 changes: 4 additions & 2 deletions src/auth0-session/handlers/login.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import urlJoin from 'url-join';
import { Config, LoginOptions } from '../config';
import { Config, GetConfig, LoginOptions } from '../config';
import TransientStore from '../transient-store';
import { encodeState } from '../utils/encoding';
import createDebug from '../utils/debug';
Expand All @@ -23,11 +23,13 @@ export type AuthVerification = {
};

export default function loginHandlerFactory(
config: Config,
getConfig: GetConfig,
client: AbstractClient,
transientHandler: TransientStore
): HandleLogin {
const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
return async (req, res, options = {}) => {
const config = await getConfigFn();
const returnTo = options.returnTo || config.baseURL;

const opts = {
Expand Down
6 changes: 4 additions & 2 deletions src/auth0-session/handlers/logout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import urlJoin from 'url-join';
import createDebug from '../utils/debug';
import { Config, LogoutOptions } from '../config';
import { GetConfig, LogoutOptions } from '../config';
import { SessionCache } from '../session-cache';
import { Auth0Request, Auth0Response } from '../http';
import { AbstractClient } from '../client/abstract-client';
Expand All @@ -10,11 +10,13 @@ const debug = createDebug('logout');
export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: LogoutOptions) => Promise<void>;

export default function logoutHandlerFactory(
config: Config,
getConfig: GetConfig,
client: AbstractClient,
sessionCache: SessionCache
): HandleLogout {
const getConfigFn = typeof getConfig === 'function' ? getConfig : () => getConfig;
return async (req, res, options = {}) => {
const config = await getConfigFn();
let returnURL = options.returnTo || config.routes.postLogoutRedirect;
debug('logout() with return url: %s', returnURL);

Expand Down
22 changes: 14 additions & 8 deletions src/auth0-session/session/abstract-session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import createDebug from '../utils/debug';
import { CookieSerializeOptions } from 'cookie';
import { Config } from '../config';
import { Config, GetConfig } from '../config';
import { Auth0RequestCookies, Auth0ResponseCookies } from '../http';

const debug = createDebug('session');
Expand Down Expand Up @@ -36,7 +36,11 @@ const assert = (bool: boolean, msg: string) => {
};

export abstract class AbstractSession<Session> {
constructor(protected config: Config) {}
protected getConfig: () => Config | Promise<Config>;

constructor(getConfig: GetConfig) {
this.getConfig = typeof getConfig === 'function' ? getConfig : () => getConfig;
}

abstract getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null>;

Expand All @@ -58,7 +62,8 @@ export abstract class AbstractSession<Session> {
): Promise<void>;

public async read(req: Auth0RequestCookies): Promise<[Session?, number?]> {
const { rollingDuration, absoluteDuration } = this.config.session;
const config = await this.getConfig();
const { rollingDuration, absoluteDuration } = config.session;

try {
const existingSessionValue = await this.getSession(req);
Expand Down Expand Up @@ -95,9 +100,10 @@ export abstract class AbstractSession<Session> {
session: Session | null | undefined,
createdAt?: number
): Promise<void> {
const config = await this.getConfig();
const {
cookie: { transient, ...cookieConfig }
} = this.config.session;
} = config.session;

if (!session) {
await this.deleteSession(req, res, cookieConfig);
Expand All @@ -107,7 +113,7 @@ export abstract class AbstractSession<Session> {
const isNewSession = typeof createdAt === 'undefined';
const uat = epoch();
const iat = typeof createdAt === 'number' ? createdAt : uat;
const exp = this.calculateExp(iat, uat);
const exp = this.calculateExp(iat, uat, config);

const cookieOptions: CookieSerializeOptions = {
...cookieConfig
Expand All @@ -119,9 +125,9 @@ export abstract class AbstractSession<Session> {
await this.setSession(req, res, session, uat, iat, exp, cookieOptions, isNewSession);
}

private calculateExp(iat: number, uat: number): number {
const { absoluteDuration } = this.config.session;
const { rolling, rollingDuration } = this.config.session;
private calculateExp(iat: number, uat: number, config: Config): number {
const { absoluteDuration } = config.session;
const { rolling, rollingDuration } = config.session;

if (typeof absoluteDuration !== 'number') {
return uat + (rollingDuration as number);
Expand Down
Loading

0 comments on commit 30c100e

Please sign in to comment.