@usync/oauth2 0.1.4 → 0.1.5

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();
@@ -122,7 +139,7 @@ var l = class {
122
139
  }).forEach(([t, n]) => {
123
140
  n != null && e.append(t, n);
124
141
  });
125
- let n = await fetch(d, {
142
+ let n = await fetch(p, {
126
143
  method: "POST",
127
144
  body: e
128
145
  }), r = await n.json();
@@ -135,7 +152,10 @@ var l = class {
135
152
  expiresAt: Date.now() + r.expires_in * 1e3
136
153
  }), r.access_token;
137
154
  }
138
- }, p = "https://accounts.google.com/o/oauth2/v2/auth", m = "https://oauth2.googleapis.com/token", h = class extends l {
155
+ getClaims() {
156
+ throw new i(2, "OIDC not supported by Dropbox");
157
+ }
158
+ }, h = "https://accounts.google.com/o/oauth2/v2/auth", g = "https://oauth2.googleapis.com/token", _ = class extends d {
139
159
  static {
140
160
  this.Scopes = {
141
161
  account: "https://www.googleapis.com/auth/userinfo.profile",
@@ -143,19 +163,24 @@ var l = class {
143
163
  imap: "https://mail.google.com/"
144
164
  };
145
165
  }
166
+ get isOidc() {
167
+ return this.options.scope?.split(/\s+/).includes("openid") ?? !1;
168
+ }
146
169
  async buildAuthUrl() {
147
170
  this.session = {
148
171
  state: o(),
149
- codeVerifier: s()
172
+ codeVerifier: s(),
173
+ ...this.isOidc ? { nonce: c() } : {}
150
174
  };
151
- let { codeChallenge: e, codeChallengeMethod: t } = await c(this.session.codeVerifier), n = new URL(p);
175
+ let { codeChallenge: e, codeChallengeMethod: t } = await u(this.session.codeVerifier), n = new URL(h);
152
176
  return Object.entries({
153
- access_type: "offline",
177
+ access_type: this.options.provider?.google?.accessType,
154
178
  client_id: this.options.clientId,
155
179
  code_challenge: e,
156
180
  code_challenge_method: t,
157
181
  include_granted_scopes: "true",
158
- prompt: "consent",
182
+ nonce: this.session.nonce,
183
+ prompt: this.options.provider?.google?.prompt,
159
184
  redirect_uri: this.options.redirectUrl,
160
185
  response_type: "code",
161
186
  scope: this.options.scope,
@@ -179,7 +204,7 @@ var l = class {
179
204
  }).forEach(([e, t]) => {
180
205
  t != null && n.append(e, t);
181
206
  });
182
- let r = await fetch(m, {
207
+ let r = await fetch(g, {
183
208
  method: "POST",
184
209
  body: n
185
210
  }), a = await r.json();
@@ -188,13 +213,17 @@ var l = class {
188
213
  data: a
189
214
  };
190
215
  if (!a.refresh_token) throw new i(2, "Failed to get refresh_token");
191
- return this._updateRefreshToken({
216
+ if (this._updateRefreshToken({
192
217
  token: a.refresh_token,
193
218
  scope: a.scope
194
219
  }), this._updateAccessToken({
195
220
  token: a.access_token,
196
221
  expiresAt: Date.now() + a.expires_in * 1e3
197
- }), a.access_token;
222
+ }), this.isOidc) {
223
+ if (!a.id_token) throw new i(2, "OIDC enabled but no id_token returned");
224
+ this._idToken = a.id_token;
225
+ }
226
+ return a.access_token;
198
227
  }
199
228
  async refreshToken() {
200
229
  let e = new URLSearchParams(), t = this.getRefreshToken();
@@ -207,7 +236,7 @@ var l = class {
207
236
  }).forEach(([t, n]) => {
208
237
  n != null && e.append(t, n);
209
238
  });
210
- let n = await fetch(m, {
239
+ let n = await fetch(g, {
211
240
  method: "POST",
212
241
  body: e
213
242
  }), r = await n.json();
@@ -220,26 +249,31 @@ var l = class {
220
249
  expiresAt: Date.now() + r.expires_in * 1e3
221
250
  }), r.access_token;
222
251
  }
223
- }, g = class extends l {
252
+ }, v = class extends d {
224
253
  static {
225
254
  this.Scopes = {
226
255
  imap: "offline_access https://outlook.office.com/IMAP.AccessAsUser.All",
227
256
  onedrive: "openid profile Files.ReadWrite.AppFolder offline_access"
228
257
  };
229
258
  }
259
+ get isOidc() {
260
+ return this.options.scope?.split(/\s+/).includes("openid") ?? !1;
261
+ }
230
262
  oauth2Url(e) {
231
263
  return `https://login.microsoftonline.com/${this.options.provider?.microsoft?.accountType ?? "common"}/oauth2/v2.0/${e}`;
232
264
  }
233
265
  async buildAuthUrl() {
234
266
  this.session = {
235
267
  state: o(),
236
- codeVerifier: s()
268
+ codeVerifier: s(),
269
+ ...this.isOidc ? { nonce: c() } : {}
237
270
  };
238
- let { codeChallenge: e, codeChallengeMethod: t } = await c(this.session.codeVerifier), n = new URL(this.oauth2Url("authorize"));
271
+ let { codeChallenge: e, codeChallengeMethod: t } = await u(this.session.codeVerifier), n = new URL(this.oauth2Url("authorize"));
239
272
  return Object.entries({
240
273
  client_id: this.options.clientId,
241
274
  code_challenge: e,
242
275
  code_challenge_method: t,
276
+ nonce: this.session.nonce,
243
277
  redirect_uri: this.options.redirectUrl,
244
278
  response_mode: "query",
245
279
  response_type: "code",
@@ -274,13 +308,17 @@ var l = class {
274
308
  data: a
275
309
  };
276
310
  if (!a.refresh_token) throw new i(2, "Failed to get refresh_token");
277
- return this._updateRefreshToken({
311
+ if (this._updateRefreshToken({
278
312
  token: a.refresh_token,
279
313
  scope: a.scope
280
314
  }), this._updateAccessToken({
281
315
  token: a.access_token,
282
316
  expiresAt: Date.now() + a.expires_in * 1e3
283
- }), a.access_token;
317
+ }), this.isOidc) {
318
+ if (!a.id_token) throw new i(2, "OIDC enabled but no id_token returned");
319
+ this._idToken = a.id_token;
320
+ }
321
+ return a.access_token;
284
322
  }
285
323
  async refreshToken() {
286
324
  let e = new URLSearchParams(), t = this.getRefreshToken();
@@ -306,17 +344,17 @@ var l = class {
306
344
  expiresAt: Date.now() + r.expires_in * 1e3
307
345
  }), r.access_token;
308
346
  }
309
- }, _ = {
310
- dropbox: f,
311
- google: h,
312
- microsoft: g
347
+ }, y = {
348
+ dropbox: m,
349
+ google: _,
350
+ microsoft: v
313
351
  };
314
352
  //#endregion
315
353
  //#region src/index.ts
316
- function v(e, t) {
317
- return new _[t.provider](e);
354
+ function b(e, t) {
355
+ return new y[t.provider](e);
318
356
  }
319
- async function y(e, t) {
357
+ async function x(e, t) {
320
358
  let n;
321
359
  try {
322
360
  n = e.getAccessToken();
@@ -338,4 +376,4 @@ async function y(e, t) {
338
376
  return n;
339
377
  }
340
378
  //#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 };
379
+ 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,4 +1,5 @@
1
1
  import { OAuth2Authorizer } from "./base.ts";
2
+ import type { IdTokenClaims } from "../types.ts";
2
3
  export declare class DropboxAuthorizer extends OAuth2Authorizer {
3
4
  /**
4
5
  * Ref: https://www.dropbox.com/developers/documentation/http/documentation
@@ -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.5",
4
+ "description": "OAuth2 support for provider-based authorization flows and token management.",
4
5
  "license": "ISC",
5
6
  "repository": {
6
7
  "type": "git",