Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bail out of static rendering for pages and routes in app dir #1541

Merged
merged 8 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -99,7 +99,7 @@
return authorizationUrl.toString();
}

async callbackParams(req: Auth0Request, expectedState: string) {

Check warning on line 102 in src/auth0-session/client/edge-client.ts

View workflow job for this annotation

GitHub Actions / Lint Code

Missing return type on function
const [as, client] = await this.getClient();
const url =
req.getMethod().toUpperCase() === 'GET' ? new URL(req.getUrl()) : new URLSearchParams(await req.getBody());
Expand Down Expand Up @@ -172,7 +172,7 @@
this.config.idpLogout &&
(this.config.auth0Logout || (issuerUrl.hostname.match('\\.auth0\\.com$') && this.config.auth0Logout !== false))
) {
const { id_token_hint, post_logout_redirect_uri, ...extraParams } = parameters;

Check warning on line 175 in src/auth0-session/client/edge-client.ts

View workflow job for this annotation

GitHub Actions / Lint Code

'id_token_hint' is assigned a value but never used
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);
Expand Down Expand Up @@ -243,3 +243,13 @@
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 @@
CallbackParamsType,
OpenIDCallbackChecks,
TokenEndpointResponse,
AbstractClient
AbstractClient,
Telemetry
} from './abstract-client';
import {
Client,
Expand All @@ -23,6 +24,7 @@
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 @@ -138,7 +140,7 @@
) {
Object.defineProperty(this.client, 'endSessionUrl', {
value(params: EndSessionParameters) {
const { id_token_hint, post_logout_redirect_uri, ...extraParams } = params;

Check warning on line 143 in src/auth0-session/client/node-client.ts

View workflow job for this annotation

GitHub Actions / Lint Code

'id_token_hint' is assigned a value but never used
const parsedUrl = new URL(urlJoin(issuer.metadata.issuer, '/v2/logout'));
parsedUrl.searchParams.set('client_id', config.clientID);
post_logout_redirect_uri && parsedUrl.searchParams.set('returnTo', post_logout_redirect_uri);
Expand All @@ -164,7 +166,7 @@
return client.authorizationUrl(parameters);
}

async callbackParams(req: Auth0Request) {

Check warning on line 169 in src/auth0-session/client/node-client.ts

View workflow job for this annotation

GitHub Actions / Lint Code

Missing return type on function
const client = await this.getClient();
const obj: CallbackParamsType = client.callbackParams({
method: req.getMethod(),
Expand Down Expand Up @@ -235,3 +237,13 @@
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 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 @@ -90,8 +93,8 @@
throw err;
}

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 @@
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,27 +72,29 @@
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;
}

if (!sessionId) {
sessionId = await genId!(req, session);

Check warning on line 91 in src/auth0-session/session/stateful-session.ts

View workflow job for this annotation

GitHub Actions / Lint Code

Forbidden non-null assertion
debug('generated new session id %o', sessionId);
}
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 @@
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
Loading