@auth0/auth0-spa-js
Version:
Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE
159 lines (146 loc) • 4.52 kB
text/typescript
import { AuthorizationParams } from './global';
import { Fetcher } from './fetcher';
interface ConnectRequest {
/** The name of the connection to link the account with (e.g., 'google-oauth2', 'facebook'). */
connection: string;
/** Array of scopes to request from the Identity Provider during the connect account flow. */
scopes?: string[];
/** The URI to redirect to after the connection process completes. */
redirect_uri: string;
/** An opaque value used to maintain state between the request and callback. */
state?: string;
/** The PKCE code challenge derived from the code verifier. */
code_challenge?: string;
/** The method used to derive the code challenge. Required when code_challenge is provided. */
code_challenge_method?: 'S256';
authorization_params?: AuthorizationParams;
}
interface ConnectResponse {
/** The base URI to initiate the account connection flow. */
connect_uri: string;
/** The authentication session identifier. */
auth_session: string;
/** Parameters to be used with the connect URI. */
connect_params: {
/** The ticket identifier to be used with the connection URI. */
ticket: string;
};
/** The number of seconds until the ticket expires. */
expires_in: number;
}
interface CompleteRequest {
/** The authentication session identifier */
auth_session: string;
/** The authorization code returned from the connect flow */
connect_code: string;
/** The redirect URI used in the original request */
redirect_uri: string;
/** The PKCE code verifier */
code_verifier?: string;
}
export interface CompleteResponse {
/** The unique identifier of the connected account */
id: string;
/** The connection name */
connection: string;
/** The access type, always 'offline' */
access_type: 'offline';
/** Array of scopes granted */
scopes?: string[];
/** ISO date string of when the connected account was created */
created_at: string;
/** ISO date string of when the refresh token expires (optional) */
expires_at?: string;
}
// Validation error returned from MyAccount API
export interface ErrorResponse {
type: string;
status: number;
title: string;
detail: string;
validation_errors?: {
detail: string;
field?: string;
pointer?: string;
source?: string;
}[];
}
/**
* Subset of the MyAccount API that handles the connect accounts flow.
*/
export class MyAccountApiClient {
constructor(
private myAccountFetcher: Fetcher<Response>,
private apiBase: string
) {}
/**
* Get a ticket for the connect account flow.
*/
async connectAccount(params: ConnectRequest): Promise<ConnectResponse> {
const res = await this.myAccountFetcher.fetchWithAuth(
`${this.apiBase}v1/connected-accounts/connect`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
}
);
return this._handleResponse(res);
}
/**
* Verify the redirect from the connect account flow and complete the connecting of the account.
*/
async completeAccount(params: CompleteRequest): Promise<CompleteResponse> {
const res = await this.myAccountFetcher.fetchWithAuth(
`${this.apiBase}v1/connected-accounts/complete`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
}
);
return this._handleResponse(res);
}
private async _handleResponse(res: Response) {
let body: any;
try {
body = await res.text();
body = JSON.parse(body);
} catch (err) {
throw new MyAccountApiError({
type: 'invalid_json',
status: res.status,
title: 'Invalid JSON response',
detail: body || String(err)
});
}
if (res.ok) {
return body;
} else {
throw new MyAccountApiError(body);
}
}
}
export class MyAccountApiError extends Error {
public readonly type: string;
public readonly status: number;
public readonly title: string;
public readonly detail: string;
public readonly validation_errors?: ErrorResponse['validation_errors'];
constructor({
type,
status,
title,
detail,
validation_errors
}: ErrorResponse) {
super(detail);
this.name = 'MyAccountApiError';
this.type = type;
this.status = status;
this.title = title;
this.detail = detail;
this.validation_errors = validation_errors;
Object.setPrototypeOf(this, MyAccountApiError.prototype);
}
}