Skip to content

Commit

Permalink
Fix edge client issue with reusing abort signal
Browse files Browse the repository at this point in the history
  • Loading branch information
adamjmcgrath committed Nov 7, 2023
1 parent 77b2ef0 commit 62a2626
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 219 deletions.
3 changes: 2 additions & 1 deletion src/auth0-session/client/abstract-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Auth0Request } from '../http';
import { Config } from '../config';
import { Auth0Request } from '../http';

export type Telemetry = {
name: string;
Expand Down Expand Up @@ -85,6 +85,7 @@ export interface AuthorizationParameters {
}

export abstract class AbstractClient {
constructor(protected config: Config, protected telemetry: Telemetry) {}
abstract authorizationUrl(parameters: Record<string, unknown>): Promise<string>;
abstract callbackParams(req: Auth0Request, expectedState: string): Promise<URLSearchParams>;
abstract callback(
Expand Down
150 changes: 82 additions & 68 deletions src/auth0-session/client/edge-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,69 @@ const encodeBase64 = (input: string) => {
};

export class EdgeClient extends AbstractClient {
constructor(
private client: oauth.Client,
private as: oauth.AuthorizationServer,
private config: Config,
private httpOptions: oauth.HttpRequestOptions
) {
super();
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.');
}

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
};
};
}

private async getClient(): Promise<[oauth.AuthorizationServer, oauth.Client]> {
if (this.as) {
return [this.as, this.client as oauth.Client];
}

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

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

async authorizationUrl(parameters: Record<string, unknown>): Promise<string> {
const authorizationUrl = new URL(this.as.authorization_endpoint as string);
const [as] = await this.getClient();
const authorizationUrl = new URL(as.authorization_endpoint as string);
authorizationUrl.searchParams.set('client_id', this.config.clientID);
Object.entries(parameters).forEach(([key, value]) => {
if (value === null || value === undefined) {
Expand All @@ -48,11 +100,12 @@ export class EdgeClient extends AbstractClient {
}

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());
let result: ReturnType<typeof oauth.validateAuthResponse>;
try {
result = oauth.validateAuthResponse(this.as, this.client, url, expectedState);
result = oauth.validateAuthResponse(as, client, url, expectedState);
} catch (e) {
throw new ApplicationError(e);
}
Expand All @@ -72,6 +125,8 @@ export class EdgeClient extends AbstractClient {
checks: OpenIDCallbackChecks,
extras: CallbackExtras
): Promise<TokenEndpointResponse> {
const [as, client] = await this.getClient();

const { clientAssertionSigningKey, clientAssertionSigningAlg } = this.config;

let clientPrivateKey = clientAssertionSigningKey as CryptoKey | undefined;
Expand All @@ -80,21 +135,21 @@ export class EdgeClient extends AbstractClient {
clientPrivateKey = await jose.importPKCS8<CryptoKey>(clientPrivateKey, clientAssertionSigningAlg || 'RS256');
}
const response = await oauth.authorizationCodeGrantRequest(
this.as,
this.client,
as,
client,
parameters,
redirectUri,
checks.code_verifier as string,
{
additionalParameters: extras.exchangeBody,
...(clientPrivateKey && { clientPrivateKey }),
...this.httpOptions
...this.httpOptions()
}
);

const result = await oauth.processAuthorizationCodeOpenIDResponse(
this.as,
this.client,
as,
client,
response,
checks.nonce,
checks.max_age
Expand All @@ -110,14 +165,15 @@ export class EdgeClient extends AbstractClient {
}

async endSessionUrl(parameters: EndSessionParameters): Promise<string> {
const issuerUrl = new URL(this.as.issuer);
const [as] = await this.getClient();
const issuerUrl = new URL(as.issuer);

if (
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(this.as.issuer, '/v2/logout'));
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);
Object.entries(extraParams).forEach(([key, value]: [string, string]) => {
Expand All @@ -128,10 +184,10 @@ export class EdgeClient extends AbstractClient {
});
return auth0LogoutUrl.toString();
}
if (!this.as.end_session_endpoint) {
if (!as.end_session_endpoint) {
throw new Error('RP Initiated Logout is not supported on your Authorization Server.');
}
const oidcLogoutUrl = new URL(this.as.end_session_endpoint);
const oidcLogoutUrl = new URL(as.end_session_endpoint);
Object.entries(parameters).forEach(([key, value]: [string, string]) => {
if (value === null || value === undefined) {
return;
Expand All @@ -144,21 +200,23 @@ export class EdgeClient extends AbstractClient {
}

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

try {
return await oauth.processUserInfoResponse(this.as, this.client, oauth.skipSubjectCheck, response);
return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response);
} catch (e) {
throw new UserInfoError(e.message);
}
}

async refresh(refreshToken: string, extras: { exchangeBody: Record<string, any> }): Promise<TokenEndpointResponse> {
const res = await oauth.refreshTokenGrantRequest(this.as, this.client, refreshToken, {
const [as, client] = await this.getClient();
const res = await oauth.refreshTokenGrantRequest(as, client, refreshToken, {
additionalParameters: extras.exchangeBody,
...this.httpOptions
...this.httpOptions()
});
const result = await oauth.processRefreshTokenResponse(this.as, this.client, res);
const result = await oauth.processRefreshTokenResponse(as, client, res);
if (oauth.isOAuth2Error(result)) {
throw new AccessTokenError(
AccessTokenErrorCode.FAILED_REFRESH_GRANT,
Expand Down Expand Up @@ -190,51 +248,7 @@ export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise
let client: EdgeClient;
return async (config) => {
if (!client) {
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
}
})
)
);
}
const httpOptions: oauth.HttpRequestOptions = {
signal: AbortSignal.timeout(config.httpTimeout),
headers
};

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(config.issuerBaseURL);
let as: oauth.AuthorizationServer;
try {
as = await oauth
.discoveryRequest(issuer, httpOptions)
.then((response) => oauth.processDiscoveryResponse(issuer, response));
} catch (e) {
throw new DiscoveryError(e, config.issuerBaseURL);
}

const oauthClient: oauth.Client = {
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
};

client = new EdgeClient(oauthClient, as, config, httpOptions);
client = new EdgeClient(config, telemetry);
}
return client;
};
Expand Down
Loading

0 comments on commit 62a2626

Please sign in to comment.