Skip to content

Commit

Permalink
Bail out of static rendering for pages and routes in app dir (#1541)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamjmcgrath authored Nov 13, 2023
2 parents 4306696 + ed41013 commit 456ec7c
Show file tree
Hide file tree
Showing 40 changed files with 531 additions and 720 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
"!<rootDir>/src/edge.ts",
"!<rootDir>/src/index.ts",
"!<rootDir>/src/shared.ts",
"!<rootDir>/src/version.ts",
"!<rootDir>/src/auth0-session/config.ts",
"!<rootDir>/src/auth0-session/index.ts",
"!<rootDir>/src/auth0-session/session-cache.ts"
Expand Down
2 changes: 2 additions & 0 deletions src/auth0-session/client/abstract-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,5 @@ export abstract class AbstractClient {
abstract generateRandomNonce(): string;
abstract calculateCodeChallenge(codeVerifier: string): Promise<string> | string;
}

export type GetClient = (config: Config) => Promise<AbstractClient>;
10 changes: 10 additions & 0 deletions src/auth0-session/client/edge-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,13 @@ export class EdgeClient extends AbstractClient {
return oauth.calculatePKCECodeChallenge(codeVerifier);
}
}

export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise<EdgeClient>) => {
let client: EdgeClient;
return async (config) => {
if (!client) {
client = new EdgeClient(config, telemetry);
}
return client;
};
};
14 changes: 13 additions & 1 deletion src/auth0-session/client/node-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
CallbackParamsType,
OpenIDCallbackChecks,
TokenEndpointResponse,
AbstractClient
AbstractClient,
Telemetry
} from './abstract-client';
import {
Client,
Expand All @@ -23,6 +24,7 @@ import urlJoin from 'url-join';
import createDebug from '../utils/debug';
import { IncomingMessage } from 'http';
import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors';
import { Config } from '../config';

const debug = createDebug('client');

Expand Down Expand Up @@ -235,3 +237,13 @@ export class NodeClient extends AbstractClient {
return generators.codeChallenge(codeVerifier);
}
}

export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise<NodeClient>) => {
let client: NodeClient;
return async (config) => {
if (!client) {
client = new NodeClient(config, telemetry);
}
return client;
};
};
3 changes: 3 additions & 0 deletions src/auth0-session/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
} from './client/abstract-client';
import type { Agent } from 'https';
import { SessionStore } from './session/stateful-session';
import { Auth0RequestCookies } from './http';

/**
* Configuration properties.
Expand Down Expand Up @@ -364,3 +365,5 @@ export interface LogoutOptions {
*/
logoutParams?: { [key: string]: any };
}

export type GetConfig = Config | ((req: Auth0RequestCookies) => Config | Promise<Config>);
13 changes: 8 additions & 5 deletions src/auth0-session/handlers/callback.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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';
import { MalformedStateCookieError, MissingStateCookieError, MissingStateParamError } from '../utils/errors';
import { Auth0Request, Auth0Response } from '../http';
import { AbstractClient } from '../client/abstract-client';
import { GetClient } from '../client/abstract-client';
import type { AuthVerification } from './login';

function getRedirectUri(config: Config): string {
Expand All @@ -25,12 +25,15 @@ export type CallbackOptions = {
export type HandleCallback = (req: Auth0Request, res: Auth0Response, options?: CallbackOptions) => Promise<void>;

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

let tokenResponse;
Expand Down Expand Up @@ -91,7 +94,7 @@ export default function callbackHandlerFactory(
}

const openidState: { returnTo?: string } = decodeState(expectedState as string)!;

Check warning on line 96 in src/auth0-session/handlers/callback.ts

View workflow job for this annotation

GitHub Actions / Lint Code

Forbidden non-null assertion
let session = sessionCache.fromTokenEndpointResponse(tokenResponse);
let session = await sessionCache.fromTokenEndpointResponse(req, res, tokenResponse);

if (options?.afterCallback) {
session = await options.afterCallback(session, openidState);
Expand Down
11 changes: 7 additions & 4 deletions src/auth0-session/handlers/login.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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';
import { Auth0Request, Auth0Response } from '../http';
import { AbstractClient } from '../client/abstract-client';
import { GetClient } from '../client/abstract-client';

const debug = createDebug('handlers');

Expand All @@ -23,11 +23,14 @@ export type AuthVerification = {
};

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

const opts = {
Expand Down
11 changes: 7 additions & 4 deletions src/auth0-session/handlers/logout.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
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';
import { GetClient } from '../client/abstract-client';

const debug = createDebug('logout');

export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: LogoutOptions) => Promise<void>;

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

Expand Down
12 changes: 10 additions & 2 deletions src/auth0-session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ export { StatelessSession } from './session/stateless-session';
export { AbstractSession, SessionPayload } from './session/abstract-session';
export { StatefulSession, SessionStore } from './session/stateful-session';
export { default as TransientStore } from './transient-store';
export { Config, SessionConfig, CookieConfig, LoginOptions, LogoutOptions, AuthorizationParameters } from './config';
export {
Config,
GetConfig,
SessionConfig,
CookieConfig,
LoginOptions,
LogoutOptions,
AuthorizationParameters
} from './config';
export { get as getConfig, ConfigParameters, DeepPartial } from './get-config';
export { default as loginHandler, HandleLogin } from './handlers/login';
export { default as logoutHandler, HandleLogout } from './handlers/logout';
export { default as callbackHandler, CallbackOptions, AfterCallback, HandleCallback } from './handlers/callback';
export { TokenEndpointResponse, AbstractClient } from './client/abstract-client';
export { TokenEndpointResponse, AbstractClient, Telemetry } from './client/abstract-client';
export { SessionCache } from './session-cache';
2 changes: 1 addition & 1 deletion src/auth0-session/session-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export interface SessionCache<Req = any, Res = any, Session = { [key: string]: a
delete(req: Req, res: Res): Promise<void>;
isAuthenticated(req: Req, res: Res): Promise<boolean>;
getIdToken(req: Req, res: Res): Promise<string | undefined>;
fromTokenEndpointResponse(tokenSet: TokenEndpointResponse): Session;
fromTokenEndpointResponse(req: Req, res: Res, tokenSet: TokenEndpointResponse): Promise<Session>;
}
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: (req: Auth0RequestCookies) => 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(req);
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(req);
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
42 changes: 25 additions & 17 deletions src/auth0-session/session/stateful-session.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { CookieSerializeOptions } from 'cookie';
import createDebug from '../utils/debug';
import { Config } from '../config';
import { AbstractSession, SessionPayload } from './abstract-session';
import { generateCookieValue, getCookieValue } from '../utils/signed-cookies';
import { signing } from '../utils/hkdf';
import { Auth0RequestCookies, Auth0ResponseCookies } from '../http';
import { Config } from '../config';

const debug = createDebug('stateful-session');

Expand All @@ -29,31 +29,35 @@ export class StatefulSession<
Session extends { [key: string]: any } = { [key: string]: any }
> extends AbstractSession<Session> {
private keys?: Uint8Array[];
private store: SessionStore<Session>;
private store?: SessionStore<Session>;

constructor(protected config: Config) {
super(config);
this.store = config.session.store as SessionStore<Session>;
private async getStore(config: Config): Promise<SessionStore<Session>> {
if (!this.store) {
this.store = config.session.store as SessionStore<Session>;
}
return this.store;
}

private async getKeys(): Promise<Uint8Array[]> {
private async getKeys(config: Config): Promise<Uint8Array[]> {
if (!this.keys) {
const secret = this.config.secret;
const secret = config.secret;
const secrets = Array.isArray(secret) ? secret : [secret];
this.keys = await Promise.all(secrets.map(signing));
}
return this.keys;
}

async getSession(req: Auth0RequestCookies): Promise<SessionPayload<Session> | undefined | null> {
const { name: sessionName } = this.config.session;
const config = await this.getConfig(req);
const { name: sessionName } = config.session;
const cookies = req.getCookies();
const keys = await this.getKeys();
const keys = await this.getKeys(config);
const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);

if (sessionId) {
const store = await this.getStore(config);
debug('reading session from %s store', sessionId);
return this.store.get(sessionId);
return store.get(sessionId);
}
return;
}
Expand All @@ -68,16 +72,18 @@ export class StatefulSession<
cookieOptions: CookieSerializeOptions,
isNewSession: boolean
): Promise<void> {
const { name: sessionName, genId } = this.config.session;
const config = await this.getConfig(req);
const store = await this.getStore(config);
const { name: sessionName, genId } = config.session;
const cookies = req.getCookies();
const keys = await this.getKeys();
const keys = await this.getKeys(config);
let sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);

// If this is a new session created by a new login we need to remove the old session
// from the store and regenerate the session id to prevent session fixation issue.
if (sessionId && isNewSession) {
debug('regenerating session id %o to prevent session fixation', sessionId);
await this.store.delete(sessionId);
await store.delete(sessionId);
sessionId = undefined;
}

Expand All @@ -88,7 +94,7 @@ export class StatefulSession<
debug('set session %o', sessionId);
const cookieValue = await generateCookieValue(sessionName, sessionId, keys[0]);
res.setCookie(sessionName, cookieValue, cookieOptions);
await this.store.set(sessionId, {
await store.set(sessionId, {
header: { iat, uat, exp },
data: session
});
Expand All @@ -99,15 +105,17 @@ export class StatefulSession<
res: Auth0ResponseCookies,
cookieOptions: CookieSerializeOptions
): Promise<void> {
const { name: sessionName } = this.config.session;
const config = await this.getConfig(req);
const { name: sessionName } = config.session;
const cookies = req.getCookies();
const keys = await this.getKeys();
const keys = await this.getKeys(config);
const sessionId = await getCookieValue(sessionName, cookies[sessionName], keys);

if (sessionId) {
const store = await this.getStore(config);
debug('deleting session %o', sessionId);
res.clearCookie(sessionName, cookieOptions);
await this.store.delete(sessionId);
await store.delete(sessionId);
}
}
}
Loading

0 comments on commit 456ec7c

Please sign in to comment.