@auth0/auth0-spa-js 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ import { AuthorizationParams } from './global';
2
+ import { Fetcher } from './fetcher';
3
+
4
+ interface ConnectRequest {
5
+ /** The name of the connection to link the account with (e.g., 'google-oauth2', 'facebook'). */
6
+ connection: string;
7
+ /** The URI to redirect to after the connection process completes. */
8
+ redirect_uri: string;
9
+ /** An opaque value used to maintain state between the request and callback. */
10
+ state?: string;
11
+ /** A string value used to associate a Client session with an ID Token, and to mitigate replay attacks. */
12
+ nonce?: string;
13
+ /** The PKCE code challenge derived from the code verifier. */
14
+ code_challenge?: string;
15
+ /** The method used to derive the code challenge. Required when code_challenge is provided. */
16
+ code_challenge_method?: 'S256';
17
+ authorization_params?: AuthorizationParams;
18
+ }
19
+
20
+ interface ConnectResponse {
21
+ /** The base URI to initiate the account connection flow. */
22
+ connect_uri: string;
23
+ /** The authentication session identifier. */
24
+ auth_session: string;
25
+ /** Parameters to be used with the connect URI. */
26
+ connect_params: {
27
+ /** The ticket identifier to be used with the connection URI. */
28
+ ticket: string;
29
+ };
30
+ /** The number of seconds until the ticket expires. */
31
+ expires_in: number;
32
+ }
33
+
34
+ interface CompleteRequest {
35
+ /** The authentication session identifier */
36
+ auth_session: string;
37
+ /** The authorization code returned from the connect flow */
38
+ connect_code: string;
39
+ /** The redirect URI used in the original request */
40
+ redirect_uri: string;
41
+ /** The PKCE code verifier */
42
+ code_verifier?: string;
43
+ }
44
+
45
+ export interface CompleteResponse {
46
+ /** The unique identifier of the connected account */
47
+ id: string;
48
+ /** The connection name */
49
+ connection: string;
50
+ /** The access type, always 'offline' */
51
+ access_type: 'offline';
52
+ /** Array of scopes granted */
53
+ scopes?: string[];
54
+ /** ISO date string of when the connected account was created */
55
+ created_at: string;
56
+ /** ISO date string of when the refresh token expires (optional) */
57
+ expires_at?: string;
58
+ }
59
+
60
+ // Validation error returned from MyAccount API
61
+ export interface ErrorResponse {
62
+ type: string;
63
+ status: number;
64
+ title: string;
65
+ detail: string;
66
+ validation_errors?: {
67
+ detail: string;
68
+ field?: string;
69
+ pointer?: string;
70
+ source?: string;
71
+ }[];
72
+ }
73
+
74
+ /**
75
+ * Subset of the MyAccount API that handles the connect accounts flow.
76
+ */
77
+ export class MyAccountApiClient {
78
+ constructor(
79
+ private myAccountFetcher: Fetcher<Response>,
80
+ private apiBase: string
81
+ ) {}
82
+
83
+ /**
84
+ * Get a ticket for the connect account flow.
85
+ */
86
+ async connectAccount(params: ConnectRequest): Promise<ConnectResponse> {
87
+ const res = await this.myAccountFetcher.fetchWithAuth(
88
+ `${this.apiBase}v1/connected-accounts/connect`,
89
+ {
90
+ method: 'POST',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ body: JSON.stringify(params)
93
+ }
94
+ );
95
+ return this._handleResponse(res);
96
+ }
97
+
98
+ /**
99
+ * Verify the redirect from the connect account flow and complete the connecting of the account.
100
+ */
101
+ async completeAccount(params: CompleteRequest): Promise<CompleteResponse> {
102
+ const res = await this.myAccountFetcher.fetchWithAuth(
103
+ `${this.apiBase}v1/connected-accounts/complete`,
104
+ {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify(params)
108
+ }
109
+ );
110
+ return this._handleResponse(res);
111
+ }
112
+
113
+ private async _handleResponse(res: Response) {
114
+ let body: any;
115
+ try {
116
+ body = await res.text();
117
+ body = JSON.parse(body);
118
+ } catch (err) {
119
+ throw new MyAccountApiError({
120
+ type: 'invalid_json',
121
+ status: res.status,
122
+ title: 'Invalid JSON response',
123
+ detail: body || String(err)
124
+ });
125
+ }
126
+
127
+ if (res.ok) {
128
+ return body;
129
+ } else {
130
+ throw new MyAccountApiError(body);
131
+ }
132
+ }
133
+ }
134
+
135
+ export class MyAccountApiError extends Error {
136
+ public readonly type: string;
137
+ public readonly status: number;
138
+ public readonly title: string;
139
+ public readonly detail: string;
140
+ public readonly validation_errors?: ErrorResponse['validation_errors'];
141
+
142
+ constructor({
143
+ type,
144
+ status,
145
+ title,
146
+ detail,
147
+ validation_errors
148
+ }: ErrorResponse) {
149
+ super(detail);
150
+ this.name = 'MyAccountApiError';
151
+ this.type = type;
152
+ this.status = status;
153
+ this.title = title;
154
+ this.detail = detail;
155
+ this.validation_errors = validation_errors;
156
+ Object.setPrototypeOf(this, MyAccountApiError.prototype);
157
+ }
158
+ }
package/src/errors.ts CHANGED
@@ -35,6 +35,24 @@ export class AuthenticationError extends GenericError {
35
35
  }
36
36
  }
37
37
 
38
+ /**
39
+ * Thrown when handling the redirect callback for the connect flow fails, will be one of Auth0's
40
+ * Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses
41
+ */
42
+ export class ConnectError extends GenericError {
43
+ constructor(
44
+ error: string,
45
+ error_description: string,
46
+ public connection: string,
47
+ public state: string,
48
+ public appState: any = null
49
+ ) {
50
+ super(error, error_description);
51
+ //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
52
+ Object.setPrototypeOf(this, ConnectError.prototype);
53
+ }
54
+ }
55
+
38
56
  /**
39
57
  * Thrown when silent auth times out (usually due to a configuration issue) or
40
58
  * when network requests to the Auth server timeout.
package/src/fetcher.ts CHANGED
@@ -114,10 +114,10 @@ export class Fetcher<TOutput extends CustomFetchMinimalOutput> {
114
114
  );
115
115
  }
116
116
 
117
- protected async setAuthorizationHeader(
117
+ protected setAuthorizationHeader(
118
118
  request: Request,
119
119
  accessToken: string
120
- ): Promise<void> {
120
+ ): void {
121
121
  request.headers.set(
122
122
  'authorization',
123
123
  `${this.config.dpopNonceId ? 'DPoP' : 'Bearer'} ${accessToken}`
package/src/global.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ICache } from './cache';
2
2
  import type { Dpop } from './dpop/dpop';
3
+ import { CompleteResponse } from './MyAccountApiClient';
3
4
 
4
5
  export interface AuthorizationParams {
5
6
  /**
@@ -265,10 +266,10 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
265
266
 
266
267
  /**
267
268
  * If provided, the SDK will load the token worker from this URL instead of the integrated `blob`. An example of when this is useful is if you have strict
268
- * Content-Security-Policy (CSP) and wish to avoid needing to set `worker-src: blob:`. We recommend either serving the worker, which you can find in the module
269
- * at `<module_path>/dist/auth0-spa-js.worker.production.js`, from the same host as your application or using the Auth0 CDN
269
+ * Content-Security-Policy (CSP) and wish to avoid needing to set `worker-src: blob:`. We recommend either serving the worker, which you can find in the module
270
+ * at `<module_path>/dist/auth0-spa-js.worker.production.js`, from the same host as your application or using the Auth0 CDN
270
271
  * `https://cdn.auth0.com/js/auth0-spa-js/<version>/auth0-spa-js.worker.production.js`.
271
- *
272
+ *
272
273
  * **Note**: The worker is only used when `useRefreshTokens: true`, `cacheLocation: 'memory'`, and the `cache` is not custom.
273
274
  */
274
275
  workerUrl?: string;
@@ -353,11 +354,26 @@ export interface RedirectLoginOptions<TAppState = any>
353
354
  openUrl?: (url: string) => Promise<void> | void;
354
355
  }
355
356
 
357
+ /**
358
+ * The types of responses expected from the authorization server.
359
+ * - `code`: used for the standard login flow.
360
+ * - `connect_code`: used for the connect account flow.
361
+ */
362
+ export enum ResponseType {
363
+ Code = 'code',
364
+ ConnectCode = 'connect_code'
365
+ }
366
+
356
367
  export interface RedirectLoginResult<TAppState = any> {
357
368
  /**
358
369
  * State stored when the redirect request was made
359
370
  */
360
371
  appState?: TAppState;
372
+
373
+ /**
374
+ * The type of response, for login it will be `code`
375
+ */
376
+ response_type: ResponseType.Code;
361
377
  }
362
378
 
363
379
  export interface PopupLoginOptions extends BaseLoginOptions { }
@@ -523,12 +539,98 @@ export interface LogoutOptions extends LogoutUrlOptions {
523
539
  openUrl?: false | ((url: string) => Promise<void> | void);
524
540
  }
525
541
 
542
+ export interface RedirectConnectAccountOptions<TAppState = any> {
543
+ /**
544
+ * The name of the connection to link (e.g. 'google-oauth2').
545
+ */
546
+ connection: string;
547
+
548
+ /**
549
+ * Additional authorization parameters for the request.
550
+ *
551
+ * @example
552
+ * await auth0.connectAccountWithRedirect({
553
+ * connection: 'google-oauth2',
554
+ * authorization_params: {
555
+ * scope: 'https://www.googleapis.com/auth/calendar'
556
+ * access_type: 'offline'
557
+ * }
558
+ * });
559
+ *
560
+ * @example
561
+ * await auth0.connectAccountWithRedirect({
562
+ * connection: 'github',
563
+ * authorization_params: {
564
+ * scope: 'repo user',
565
+ * audience: 'https://api.github.com'
566
+ * }
567
+ * });
568
+ */
569
+ authorization_params?: AuthorizationParams;
570
+
571
+ /**
572
+ * The URI to redirect back to after connecting the account.
573
+ */
574
+ redirectUri?: string;
575
+
576
+ /**
577
+ * Optional application state to persist through the transaction.
578
+ *
579
+ * @example
580
+ * await auth0.connectAccountWithRedirect({
581
+ * connection: 'google-oauth2',
582
+ * appState: { returnTo: '/settings' }
583
+ * });
584
+ */
585
+ appState?: TAppState;
586
+
587
+ /**
588
+ * Optional function to handle the redirect URL.
589
+ *
590
+ * @example
591
+ * await auth0.connectAccountWithRedirect({
592
+ * connection: 'google-oauth2',
593
+ * openUrl: async (url) => { myBrowserApi.open(url); }
594
+ * });
595
+ */
596
+ openUrl?: (url: string) => Promise<void>;
597
+ }
598
+
599
+ /**
600
+ * The result returned after a successful account connection redirect.
601
+ *
602
+ * Combines the redirect login result (including any persisted app state)
603
+ * with the complete response from the My Account API.
604
+ *
605
+ * @template TAppState - The type of application state persisted through the transaction.
606
+ * @example
607
+ * const result = await auth0.connectAccountWithRedirect(options);
608
+ * console.log(result.appState); // Access persisted app state
609
+ * console.log(result.connection); // The connection of the account you connected to.
610
+ * console.log(result.response_type === 'connect_code'); // The response type will be 'connect_code'
611
+ */
612
+ export type ConnectAccountRedirectResult<TAppState = any> = CompleteResponse & {
613
+ /**
614
+ * State stored when the redirect request was made
615
+ */
616
+ appState?: TAppState;
617
+
618
+ /**
619
+ * The type of response, for connect account it will be `connect_code`
620
+ */
621
+ response_type: ResponseType.ConnectCode;
622
+ };
623
+
526
624
  /**
527
625
  * @ignore
528
626
  */
529
627
  export interface AuthenticationResult {
530
628
  state: string;
531
629
  code?: string;
630
+ /**
631
+ * This is for the redirect from the connect account flow.
632
+ */
633
+ connect_code?: string;
532
634
  error?: string;
533
635
  error_description?: string;
534
636
  }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export async function createAuth0Client(options: Auth0ClientOptions) {
23
23
  export { Auth0Client };
24
24
 
25
25
  export {
26
+ ConnectError,
26
27
  GenericError,
27
28
  AuthenticationError,
28
29
  TimeoutError,
@@ -48,3 +49,7 @@ export {
48
49
  } from './cache';
49
50
 
50
51
  export { type FetcherConfig } from './fetcher';
52
+
53
+ export {
54
+ MyAccountApiError
55
+ } from './MyAccountApiClient';
@@ -2,7 +2,7 @@ import { ClientStorage } from './storage';
2
2
 
3
3
  const TRANSACTION_STORAGE_KEY_PREFIX = 'a0.spajs.txs';
4
4
 
5
- interface Transaction {
5
+ export interface LoginTransaction {
6
6
  nonce: string;
7
7
  scope: string;
8
8
  audience: string;
@@ -11,6 +11,19 @@ interface Transaction {
11
11
  redirect_uri?: string;
12
12
  organization?: string;
13
13
  state?: string;
14
+ response_type: 'code';
15
+ }
16
+
17
+ export interface ConnectAccountTransaction {
18
+ appState?: any;
19
+ audience?: string;
20
+ auth_session: string;
21
+ code_verifier: string;
22
+ redirect_uri: string;
23
+ scope?: string;
24
+ state: string;
25
+ connection: string;
26
+ response_type: 'connect_code';
14
27
  }
15
28
 
16
29
  export class TransactionManager {
@@ -24,14 +37,14 @@ export class TransactionManager {
24
37
  this.storageKey = `${TRANSACTION_STORAGE_KEY_PREFIX}.${this.clientId}`;
25
38
  }
26
39
 
27
- public create(transaction: Transaction) {
40
+ public create<T extends Object = LoginTransaction>(transaction: T) {
28
41
  this.storage.save(this.storageKey, transaction, {
29
42
  daysUntilExpire: 1,
30
43
  cookieDomain: this.cookieDomain
31
44
  });
32
45
  }
33
46
 
34
- public get(): Transaction | undefined {
47
+ public get<T extends Object = LoginTransaction>(): T | undefined {
35
48
  return this.storage.get(this.storageKey);
36
49
  }
37
50
 
@@ -40,4 +53,4 @@ export class TransactionManager {
40
53
  cookieDomain: this.cookieDomain
41
54
  });
42
55
  }
43
- }
56
+ }
package/src/utils.ts CHANGED
@@ -24,6 +24,7 @@ export const parseAuthenticationResult = (
24
24
  return {
25
25
  state: searchParams.get('state')!,
26
26
  code: searchParams.get('code') || undefined,
27
+ connect_code: searchParams.get('connect_code') || undefined,
27
28
  error: searchParams.get('error') || undefined,
28
29
  error_description: searchParams.get('error_description') || undefined
29
30
  };
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export default '2.5.0';
1
+ export default '2.6.0';