@usync/oauth2 0.1.4 → 0.1.6

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.
package/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # @usync/oauth2
2
2
 
3
- [![NPM](https://img.shields.io/npm/v/@usync/oauth2.svg)](https://npm.im/@usync/oauth2)
3
+ [![NPM](https://img.shields.io/npm/v/@usync/oauth2.svg)](https://npmx.dev/package/@usync/oauth2)
4
4
  ![License](https://img.shields.io/npm/l/@usync/oauth2.svg)
5
- [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue)](https://www.jsdocs.io/package/@usync/oauth2)
6
5
 
7
6
  OAuth2 support for provider-based authorization flows and token management.
8
7
 
package/dist/index.js CHANGED
@@ -18,7 +18,18 @@ function o() {
18
18
  function s() {
19
19
  return e(64);
20
20
  }
21
- async function c(e) {
21
+ function c() {
22
+ return e(32);
23
+ }
24
+ function l(e) {
25
+ let t = e.split(".");
26
+ if (t.length !== 3) throw Error("Invalid JWT");
27
+ let n = atob(t[1].replace(/-/g, "+").replace(/_/g, "/")), r = new Uint8Array(n.length);
28
+ for (let e = 0; e < n.length; e++) r[e] = n.charCodeAt(e);
29
+ let i = new TextDecoder("utf-8").decode(r);
30
+ return JSON.parse(i);
31
+ }
32
+ async function u(e) {
22
33
  let t = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(e));
23
34
  return {
24
35
  codeChallenge: a(new Uint8Array(t)),
@@ -27,9 +38,9 @@ async function c(e) {
27
38
  }
28
39
  //#endregion
29
40
  //#region src/providers/base.ts
30
- var l = class {
41
+ var d = class {
31
42
  constructor(e, t) {
32
- this.options = e, this._accessToken = null, this._refreshToken = null, t && (this.setRefreshToken(t.refreshToken), this.setAccessToken(t.accessToken), this.session = t.session);
43
+ this.options = e, this._accessToken = null, this._refreshToken = null, this._idToken = null, t && (this.setRefreshToken(t.refreshToken), this.setAccessToken(t.accessToken), this.session = t.session);
33
44
  }
34
45
  _getValidToken(e) {
35
46
  return e && (!e.expiresAt || Date.now() < e.expiresAt) ? e.token : void 0;
@@ -57,7 +68,13 @@ var l = class {
57
68
  setRefreshToken(e) {
58
69
  this._refreshToken = e ?? null;
59
70
  }
60
- }, u = "https://www.dropbox.com/oauth2/authorize", d = "https://api.dropbox.com/oauth2/token", f = class extends l {
71
+ getClaims() {
72
+ if (!this._idToken) throw new i(2, "OIDC not enabled or claims not available");
73
+ let e = l(this._idToken);
74
+ if (this.session?.nonce && e.nonce !== this.session.nonce) throw new i(2, "nonce mismatch");
75
+ return e;
76
+ }
77
+ }, f = "https://www.dropbox.com/oauth2/authorize", p = "https://api.dropbox.com/oauth2/token", m = class extends d {
61
78
  static {
62
79
  this.Scopes = { account: "account_info.read" };
63
80
  }
@@ -66,7 +83,7 @@ var l = class {
66
83
  state: o(),
67
84
  codeVerifier: s()
68
85
  };
69
- let { codeChallenge: e, codeChallengeMethod: t } = await c(this.session.codeVerifier), n = new URL(u);
86
+ let { codeChallenge: e, codeChallengeMethod: t } = await u(this.session.codeVerifier), n = new URL(f);
70
87
  return Object.entries({
71
88
  client_id: this.options.clientId,
72
89
  code_challenge: e,
@@ -95,7 +112,7 @@ var l = class {
95
112
  }).forEach(([e, t]) => {
96
113
  t != null && n.append(e, t);
97
114
  });
98
- let r = await fetch(d, {
115
+ let r = await fetch(p, {
99
116
  method: "POST",
100
117
  body: n
101
118
  }), a = await r.json();
@@ -103,8 +120,7 @@ var l = class {
103
120
  status: r.status,
104
121
  data: a
105
122
  };
106
- if (!a.refresh_token) throw new i(2, "Failed to get refresh_token");
107
- return this._updateRefreshToken({
123
+ return a.refresh_token && this._updateRefreshToken({
108
124
  token: a.refresh_token,
109
125
  scope: a.scope
110
126
  }), this._updateAccessToken({
@@ -122,7 +138,7 @@ var l = class {
122
138
  }).forEach(([t, n]) => {
123
139
  n != null && e.append(t, n);
124
140
  });
125
- let n = await fetch(d, {
141
+ let n = await fetch(p, {
126
142
  method: "POST",
127
143
  body: e
128
144
  }), r = await n.json();
@@ -135,7 +151,10 @@ var l = class {
135
151
  expiresAt: Date.now() + r.expires_in * 1e3
136
152
  }), r.access_token;
137
153
  }
138
- }, p = "https://accounts.google.com/o/oauth2/v2/auth", m = "https://oauth2.googleapis.com/token", h = class extends l {
154
+ getClaims() {
155
+ throw new i(2, "OIDC not supported by Dropbox");
156
+ }
157
+ }, h = "https://accounts.google.com/o/oauth2/v2/auth", g = "https://oauth2.googleapis.com/token", _ = class extends d {
139
158
  static {
140
159
  this.Scopes = {
141
160
  account: "https://www.googleapis.com/auth/userinfo.profile",
@@ -143,19 +162,24 @@ var l = class {
143
162
  imap: "https://mail.google.com/"
144
163
  };
145
164
  }
165
+ get isOidc() {
166
+ return this.options.scope?.split(/\s+/).includes("openid") ?? !1;
167
+ }
146
168
  async buildAuthUrl() {
147
169
  this.session = {
148
170
  state: o(),
149
- codeVerifier: s()
171
+ codeVerifier: s(),
172
+ ...this.isOidc ? { nonce: c() } : {}
150
173
  };
151
- let { codeChallenge: e, codeChallengeMethod: t } = await c(this.session.codeVerifier), n = new URL(p);
174
+ let { codeChallenge: e, codeChallengeMethod: t } = await u(this.session.codeVerifier), n = new URL(h);
152
175
  return Object.entries({
153
- access_type: "offline",
176
+ access_type: this.options.provider?.google?.accessType,
154
177
  client_id: this.options.clientId,
155
178
  code_challenge: e,
156
179
  code_challenge_method: t,
157
180
  include_granted_scopes: "true",
158
- prompt: "consent",
181
+ nonce: this.session.nonce,
182
+ prompt: this.options.provider?.google?.prompt,
159
183
  redirect_uri: this.options.redirectUrl,
160
184
  response_type: "code",
161
185
  scope: this.options.scope,
@@ -179,7 +203,7 @@ var l = class {
179
203
  }).forEach(([e, t]) => {
180
204
  t != null && n.append(e, t);
181
205
  });
182
- let r = await fetch(m, {
206
+ let r = await fetch(g, {
183
207
  method: "POST",
184
208
  body: n
185
209
  }), a = await r.json();
@@ -187,14 +211,17 @@ var l = class {
187
211
  status: r.status,
188
212
  data: a
189
213
  };
190
- if (!a.refresh_token) throw new i(2, "Failed to get refresh_token");
191
- return this._updateRefreshToken({
214
+ if (a.refresh_token && this._updateRefreshToken({
192
215
  token: a.refresh_token,
193
216
  scope: a.scope
194
217
  }), this._updateAccessToken({
195
218
  token: a.access_token,
196
219
  expiresAt: Date.now() + a.expires_in * 1e3
197
- }), a.access_token;
220
+ }), this.isOidc) {
221
+ if (!a.id_token) throw new i(2, "OIDC enabled but no id_token returned");
222
+ this._idToken = a.id_token;
223
+ }
224
+ return a.access_token;
198
225
  }
199
226
  async refreshToken() {
200
227
  let e = new URLSearchParams(), t = this.getRefreshToken();
@@ -207,7 +234,7 @@ var l = class {
207
234
  }).forEach(([t, n]) => {
208
235
  n != null && e.append(t, n);
209
236
  });
210
- let n = await fetch(m, {
237
+ let n = await fetch(g, {
211
238
  method: "POST",
212
239
  body: e
213
240
  }), r = await n.json();
@@ -220,26 +247,31 @@ var l = class {
220
247
  expiresAt: Date.now() + r.expires_in * 1e3
221
248
  }), r.access_token;
222
249
  }
223
- }, g = class extends l {
250
+ }, v = class extends d {
224
251
  static {
225
252
  this.Scopes = {
226
253
  imap: "offline_access https://outlook.office.com/IMAP.AccessAsUser.All",
227
254
  onedrive: "openid profile Files.ReadWrite.AppFolder offline_access"
228
255
  };
229
256
  }
257
+ get isOidc() {
258
+ return this.options.scope?.split(/\s+/).includes("openid") ?? !1;
259
+ }
230
260
  oauth2Url(e) {
231
261
  return `https://login.microsoftonline.com/${this.options.provider?.microsoft?.accountType ?? "common"}/oauth2/v2.0/${e}`;
232
262
  }
233
263
  async buildAuthUrl() {
234
264
  this.session = {
235
265
  state: o(),
236
- codeVerifier: s()
266
+ codeVerifier: s(),
267
+ ...this.isOidc ? { nonce: c() } : {}
237
268
  };
238
- let { codeChallenge: e, codeChallengeMethod: t } = await c(this.session.codeVerifier), n = new URL(this.oauth2Url("authorize"));
269
+ let { codeChallenge: e, codeChallengeMethod: t } = await u(this.session.codeVerifier), n = new URL(this.oauth2Url("authorize"));
239
270
  return Object.entries({
240
271
  client_id: this.options.clientId,
241
272
  code_challenge: e,
242
273
  code_challenge_method: t,
274
+ nonce: this.session.nonce,
243
275
  redirect_uri: this.options.redirectUrl,
244
276
  response_mode: "query",
245
277
  response_type: "code",
@@ -273,14 +305,17 @@ var l = class {
273
305
  status: r.status,
274
306
  data: a
275
307
  };
276
- if (!a.refresh_token) throw new i(2, "Failed to get refresh_token");
277
- return this._updateRefreshToken({
308
+ if (a.refresh_token || this._updateRefreshToken({
278
309
  token: a.refresh_token,
279
310
  scope: a.scope
280
311
  }), this._updateAccessToken({
281
312
  token: a.access_token,
282
313
  expiresAt: Date.now() + a.expires_in * 1e3
283
- }), a.access_token;
314
+ }), this.isOidc) {
315
+ if (!a.id_token) throw new i(2, "OIDC enabled but no id_token returned");
316
+ this._idToken = a.id_token;
317
+ }
318
+ return a.access_token;
284
319
  }
285
320
  async refreshToken() {
286
321
  let e = new URLSearchParams(), t = this.getRefreshToken();
@@ -306,17 +341,17 @@ var l = class {
306
341
  expiresAt: Date.now() + r.expires_in * 1e3
307
342
  }), r.access_token;
308
343
  }
309
- }, _ = {
310
- dropbox: f,
311
- google: h,
312
- microsoft: g
344
+ }, y = {
345
+ dropbox: m,
346
+ google: _,
347
+ microsoft: v
313
348
  };
314
349
  //#endregion
315
350
  //#region src/index.ts
316
- function v(e, t) {
317
- return new _[t.provider](e);
351
+ function b(e, t) {
352
+ return new y[t.provider](e);
318
353
  }
319
- async function y(e, t) {
354
+ async function x(e, t) {
320
355
  let n;
321
356
  try {
322
357
  n = e.getAccessToken();
@@ -338,4 +373,4 @@ async function y(e, t) {
338
373
  return n;
339
374
  }
340
375
  //#endregion
341
- export { f as DropboxAuthorizer, h as GoogleAuthorizer, g as MicrosoftAuthorizer, n as OAUTH2_AUTH_ERROR, t as OAUTH2_NEED_REFRESH, r as OAUTH2_UNAUTHORIZED, l as OAuth2Authorizer, _ as OAuth2Authorizers, i as OAuth2Error, y as ensureAccessToken, v as getAuthorizer };
376
+ export { m as DropboxAuthorizer, _ as GoogleAuthorizer, v as MicrosoftAuthorizer, n as OAUTH2_AUTH_ERROR, t as OAUTH2_NEED_REFRESH, r as OAUTH2_UNAUTHORIZED, d as OAuth2Authorizer, y as OAuth2Authorizers, i as OAuth2Error, x as ensureAccessToken, b as getAuthorizer };
@@ -1,4 +1,4 @@
1
- import type { OAuth2AuthorizerOptions, TokenData } from "../types";
1
+ import type { IdTokenClaims, OAuth2AuthorizerOptions, TokenData } from "../types";
2
2
  export declare abstract class OAuth2Authorizer {
3
3
  protected options: OAuth2AuthorizerOptions;
4
4
  abstract buildAuthUrl(): Promise<string>;
@@ -6,9 +6,11 @@ export declare abstract class OAuth2Authorizer {
6
6
  abstract refreshToken(): Promise<string>;
7
7
  protected _accessToken: TokenData | null;
8
8
  protected _refreshToken: TokenData | null;
9
+ protected _idToken: string | null;
9
10
  session: {
10
11
  state: string;
11
12
  codeVerifier: string;
13
+ nonce?: string;
12
14
  } | undefined;
13
15
  constructor(options: OAuth2AuthorizerOptions, initialData?: {
14
16
  accessToken?: TokenData;
@@ -16,6 +18,7 @@ export declare abstract class OAuth2Authorizer {
16
18
  session?: {
17
19
  state: string;
18
20
  codeVerifier: string;
21
+ nonce?: string;
19
22
  };
20
23
  });
21
24
  protected _getValidToken(value?: TokenData | null): string | undefined;
@@ -25,4 +28,5 @@ export declare abstract class OAuth2Authorizer {
25
28
  getRefreshToken(): string;
26
29
  setAccessToken(value?: TokenData | null): void;
27
30
  setRefreshToken(value?: TokenData | null): void;
31
+ getClaims(): IdTokenClaims;
28
32
  }
@@ -1,3 +1,4 @@
1
+ import type { IdTokenClaims } from "../types.ts";
1
2
  import { OAuth2Authorizer } from "./base.ts";
2
3
  export declare class DropboxAuthorizer extends OAuth2Authorizer {
3
4
  /**
@@ -9,4 +10,5 @@ export declare class DropboxAuthorizer extends OAuth2Authorizer {
9
10
  buildAuthUrl(): Promise<string>;
10
11
  finishAuth(url: URL): Promise<string>;
11
12
  refreshToken(): Promise<string>;
13
+ getClaims(): IdTokenClaims;
12
14
  }
@@ -11,6 +11,7 @@ export declare class GoogleAuthorizer extends OAuth2Authorizer {
11
11
  "drive.appdata": string;
12
12
  imap: string;
13
13
  };
14
+ protected get isOidc(): boolean;
14
15
  buildAuthUrl(): Promise<string>;
15
16
  finishAuth(url: URL): Promise<string>;
16
17
  refreshToken(): Promise<string>;
@@ -8,6 +8,7 @@ export declare class MicrosoftAuthorizer extends OAuth2Authorizer {
8
8
  imap: string;
9
9
  onedrive: string;
10
10
  };
11
+ protected get isOidc(): boolean;
11
12
  private oauth2Url;
12
13
  buildAuthUrl(): Promise<string>;
13
14
  finishAuth(url: URL): Promise<string>;
package/dist/types.d.ts CHANGED
@@ -4,6 +4,20 @@ export interface TokenData {
4
4
  expiresAt?: number;
5
5
  scope?: string;
6
6
  }
7
+ export interface IdTokenClaims {
8
+ iss?: string;
9
+ sub?: string;
10
+ aud?: string;
11
+ exp?: number;
12
+ iat?: number;
13
+ nonce?: string;
14
+ email?: string;
15
+ name?: string;
16
+ picture?: string;
17
+ given_name?: string;
18
+ family_name?: string;
19
+ email_verified?: boolean;
20
+ }
7
21
  export interface OAuth2Config {
8
22
  clientId: string;
9
23
  /** clientSecret may be absent for client-side apps. */
@@ -11,6 +25,10 @@ export interface OAuth2Config {
11
25
  redirectUrl: string;
12
26
  scope?: string;
13
27
  provider?: {
28
+ google?: {
29
+ accessType?: string;
30
+ prompt?: string;
31
+ };
14
32
  microsoft?: {
15
33
  /**
16
34
  * Must match the account type of the application registered in https://portal.azure.com/.
package/dist/util.d.ts CHANGED
@@ -7,6 +7,8 @@ export declare function getState(): string;
7
7
  * and a maximum length of 128 characters.
8
8
  */
9
9
  export declare function getCodeVerifier(): string;
10
+ export declare function getNonce(): string;
11
+ export declare function decodeJwtPayload(token: string): Record<string, unknown>;
10
12
  export declare function getCodeChallenge(codeVerifier: string): Promise<{
11
13
  codeChallenge: string;
12
14
  codeChallengeMethod: string;
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@usync/oauth2",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
+ "description": "OAuth2 support for provider-based authorization flows and token management.",
4
5
  "license": "ISC",
5
6
  "repository": {
6
7
  "type": "git",