@atproto/oauth-client 0.5.14 → 0.6.1

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/oauth-authorization-server-metadata-resolver.d.ts +1 -1
  3. package/dist/oauth-authorization-server-metadata-resolver.d.ts.map +1 -1
  4. package/dist/oauth-authorization-server-metadata-resolver.js +1 -1
  5. package/dist/oauth-authorization-server-metadata-resolver.js.map +1 -1
  6. package/dist/oauth-client.d.ts +7 -8
  7. package/dist/oauth-client.d.ts.map +1 -1
  8. package/dist/oauth-client.js +27 -26
  9. package/dist/oauth-client.js.map +1 -1
  10. package/dist/oauth-protected-resource-metadata-resolver.d.ts +3 -3
  11. package/dist/oauth-protected-resource-metadata-resolver.d.ts.map +1 -1
  12. package/dist/oauth-protected-resource-metadata-resolver.js +4 -0
  13. package/dist/oauth-protected-resource-metadata-resolver.js.map +1 -1
  14. package/dist/oauth-resolver.d.ts +1 -1
  15. package/dist/oauth-resolver.d.ts.map +1 -1
  16. package/dist/oauth-resolver.js +3 -0
  17. package/dist/oauth-resolver.js.map +1 -1
  18. package/dist/oauth-server-factory.d.ts +1 -1
  19. package/dist/oauth-server-factory.d.ts.map +1 -1
  20. package/dist/oauth-server-factory.js +0 -7
  21. package/dist/oauth-server-factory.js.map +1 -1
  22. package/dist/oauth-session.d.ts.map +1 -1
  23. package/dist/oauth-session.js +1 -4
  24. package/dist/oauth-session.js.map +1 -1
  25. package/dist/session-getter.d.ts +16 -21
  26. package/dist/session-getter.d.ts.map +1 -1
  27. package/dist/session-getter.js +65 -60
  28. package/dist/session-getter.js.map +1 -1
  29. package/dist/state-store.d.ts +13 -3
  30. package/dist/state-store.d.ts.map +1 -1
  31. package/dist/state-store.js.map +1 -1
  32. package/dist/util.d.ts +0 -10
  33. package/dist/util.d.ts.map +1 -1
  34. package/dist/util.js +1 -64
  35. package/dist/util.js.map +1 -1
  36. package/package.json +11 -11
  37. package/src/oauth-authorization-server-metadata-resolver.ts +2 -2
  38. package/src/oauth-client.ts +47 -50
  39. package/src/oauth-protected-resource-metadata-resolver.ts +9 -4
  40. package/src/oauth-resolver.ts +5 -1
  41. package/src/oauth-server-factory.ts +2 -16
  42. package/src/oauth-session.ts +1 -4
  43. package/src/session-getter.ts +85 -102
  44. package/src/state-store.ts +13 -3
  45. package/src/util.ts +0 -67
package/dist/util.js CHANGED
@@ -1,17 +1,6 @@
1
1
  "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
- };
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.CustomEventTarget = exports.CustomEvent = exports.ifString = void 0;
3
+ exports.ifString = void 0;
15
4
  exports.contentMime = contentMime;
16
5
  exports.combineSignals = combineSignals;
17
6
  const ifString = (v) => (typeof v === 'string' ? v : undefined);
@@ -19,58 +8,6 @@ exports.ifString = ifString;
19
8
  function contentMime(headers) {
20
9
  return headers.get('content-type')?.split(';')[0].trim();
21
10
  }
22
- /**
23
- * Ponyfill for `CustomEvent` constructor.
24
- */
25
- exports.CustomEvent = globalThis.CustomEvent ??
26
- (() => {
27
- var _CustomEvent_detail;
28
- class CustomEvent extends Event {
29
- constructor(type, options) {
30
- if (!arguments.length)
31
- throw new TypeError('type argument is required');
32
- super(type, options);
33
- _CustomEvent_detail.set(this, void 0);
34
- __classPrivateFieldSet(this, _CustomEvent_detail, options?.detail ?? null, "f");
35
- }
36
- get detail() {
37
- return __classPrivateFieldGet(this, _CustomEvent_detail, "f");
38
- }
39
- }
40
- _CustomEvent_detail = new WeakMap();
41
- Object.defineProperties(CustomEvent.prototype, {
42
- [Symbol.toStringTag]: {
43
- writable: false,
44
- enumerable: false,
45
- configurable: true,
46
- value: 'CustomEvent',
47
- },
48
- detail: {
49
- enumerable: true,
50
- },
51
- });
52
- return CustomEvent;
53
- })();
54
- class CustomEventTarget {
55
- constructor() {
56
- Object.defineProperty(this, "eventTarget", {
57
- enumerable: true,
58
- configurable: true,
59
- writable: true,
60
- value: new EventTarget()
61
- });
62
- }
63
- addEventListener(type, callback, options) {
64
- this.eventTarget.addEventListener(type, callback, options);
65
- }
66
- removeEventListener(type, callback, options) {
67
- this.eventTarget.removeEventListener(type, callback, options);
68
- }
69
- dispatchCustomEvent(type, detail, init) {
70
- return this.eventTarget.dispatchEvent(new exports.CustomEvent(type, { ...init, detail }));
71
- }
72
- }
73
- exports.CustomEventTarget = CustomEventTarget;
74
11
  function combineSignals(signals) {
75
12
  const controller = new DisposableAbortController();
76
13
  const onAbort = function (_event) {
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["export type Awaitable<T> = T | PromiseLike<T>\nexport type Simplify<T> = { [K in keyof T]: T[K] } & NonNullable<unknown>\n\nexport const ifString = <V>(v: V) => (typeof v === 'string' ? v : undefined)\n\nexport function contentMime(headers: Headers): string | undefined {\n return headers.get('content-type')?.split(';')[0]!.trim()\n}\n\n/**\n * Ponyfill for `CustomEvent` constructor.\n */\nexport const CustomEvent: typeof globalThis.CustomEvent =\n globalThis.CustomEvent ??\n (() => {\n class CustomEvent<T> extends Event {\n #detail: T | null\n constructor(type: string, options?: CustomEventInit<T>) {\n if (!arguments.length) throw new TypeError('type argument is required')\n super(type, options)\n this.#detail = options?.detail ?? null\n }\n get detail() {\n return this.#detail\n }\n }\n\n Object.defineProperties(CustomEvent.prototype, {\n [Symbol.toStringTag]: {\n writable: false,\n enumerable: false,\n configurable: true,\n value: 'CustomEvent',\n },\n detail: {\n enumerable: true,\n },\n })\n\n return CustomEvent\n })()\n\nexport class CustomEventTarget<EventDetailMap extends Record<string, unknown>> {\n readonly eventTarget = new EventTarget()\n\n addEventListener<T extends Extract<keyof EventDetailMap, string>>(\n type: T,\n callback: (event: CustomEvent<EventDetailMap[T]>) => void,\n options?: AddEventListenerOptions | boolean,\n ): void {\n this.eventTarget.addEventListener(type, callback as EventListener, options)\n }\n\n removeEventListener<T extends Extract<keyof EventDetailMap, string>>(\n type: T,\n callback: (event: CustomEvent<EventDetailMap[T]>) => void,\n options?: EventListenerOptions | boolean,\n ): void {\n this.eventTarget.removeEventListener(\n type,\n callback as EventListener,\n options,\n )\n }\n\n dispatchCustomEvent<T extends Extract<keyof EventDetailMap, string>>(\n type: T,\n detail: EventDetailMap[T],\n init?: EventInit,\n ): boolean {\n return this.eventTarget.dispatchEvent(\n new CustomEvent(type, { ...init, detail }),\n )\n }\n}\n\nexport function combineSignals(\n signals: readonly (AbortSignal | undefined)[],\n): AbortController & Disposable {\n const controller = new DisposableAbortController()\n\n const onAbort = function (this: AbortSignal, _event: Event) {\n const reason = new Error('This operation was aborted', {\n cause: this.reason,\n })\n\n controller.abort(reason)\n }\n\n try {\n for (const sig of signals) {\n if (sig) {\n sig.throwIfAborted()\n sig.addEventListener('abort', onAbort, { signal: controller.signal })\n }\n }\n\n return controller\n } catch (err) {\n controller.abort(err)\n throw err\n }\n}\n\n/**\n * Allows using {@link AbortController} with the `using` keyword, in order to\n * automatically abort them once the execution block ends.\n */\nclass DisposableAbortController extends AbortController implements Disposable {\n [Symbol.dispose]() {\n this.abort(new Error('AbortController was disposed'))\n }\n}\n"]}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAKA,kCAEC;AAED,wCA0BC;AAhCM,MAAM,QAAQ,GAAG,CAAI,CAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAA/D,QAAA,QAAQ,YAAuD;AAE5E,SAAgB,WAAW,CAAC,OAAgB;IAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;AAC3D,CAAC;AAED,SAAgB,cAAc,CAC5B,OAA6C;IAE7C,MAAM,UAAU,GAAG,IAAI,yBAAyB,EAAE,CAAA;IAElD,MAAM,OAAO,GAAG,UAA6B,MAAa;QACxD,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,4BAA4B,EAAE;YACrD,KAAK,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC,CAAA;QAEF,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1B,CAAC,CAAA;IAED,IAAI,CAAC;QACH,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,GAAG,EAAE,CAAC;gBACR,GAAG,CAAC,cAAc,EAAE,CAAA;gBACpB,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAA;YACvE,CAAC;QACH,CAAC;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACrB,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,yBAA0B,SAAQ,eAAe;IACrD,CAAC,MAAM,CAAC,OAAO,CAAC;QACd,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAA;IACvD,CAAC;CACF","sourcesContent":["export type Awaitable<T> = T | PromiseLike<T>\nexport type Simplify<T> = { [K in keyof T]: T[K] } & NonNullable<unknown>\n\nexport const ifString = <V>(v: V) => (typeof v === 'string' ? v : undefined)\n\nexport function contentMime(headers: Headers): string | undefined {\n return headers.get('content-type')?.split(';')[0]!.trim()\n}\n\nexport function combineSignals(\n signals: readonly (AbortSignal | undefined)[],\n): AbortController & Disposable {\n const controller = new DisposableAbortController()\n\n const onAbort = function (this: AbortSignal, _event: Event) {\n const reason = new Error('This operation was aborted', {\n cause: this.reason,\n })\n\n controller.abort(reason)\n }\n\n try {\n for (const sig of signals) {\n if (sig) {\n sig.throwIfAborted()\n sig.addEventListener('abort', onAbort, { signal: controller.signal })\n }\n }\n\n return controller\n } catch (err) {\n controller.abort(err)\n throw err\n }\n}\n\n/**\n * Allows using {@link AbortController} with the `using` keyword, in order to\n * automatically abort them once the execution block ends.\n */\nclass DisposableAbortController extends AbortController implements Disposable {\n [Symbol.dispose]() {\n this.abort(new Error('AbortController was disposed'))\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-client",
3
- "version": "0.5.14",
3
+ "version": "0.6.1",
4
4
  "license": "MIT",
5
5
  "description": "OAuth client for ATPROTO PDS. This package serves as common base for environment-specific implementations (NodeJS, Browser, React-Native).",
6
6
  "keywords": [
@@ -28,16 +28,16 @@
28
28
  "core-js": "^3",
29
29
  "multiformats": "^9.9.0",
30
30
  "zod": "^3.23.8",
31
- "@atproto-labs/did-resolver": "0.2.6",
32
- "@atproto-labs/fetch": "0.2.3",
33
- "@atproto-labs/handle-resolver": "0.3.6",
34
- "@atproto-labs/identity-resolver": "0.3.6",
35
- "@atproto-labs/simple-store": "0.3.0",
36
- "@atproto-labs/simple-store-memory": "0.1.4",
37
- "@atproto/did": "0.3.0",
38
- "@atproto/jwk": "0.6.0",
39
- "@atproto/oauth-types": "0.6.2",
40
- "@atproto/xrpc": "0.7.7"
31
+ "@atproto-labs/did-resolver": "^0.2.6",
32
+ "@atproto-labs/fetch": "^0.2.3",
33
+ "@atproto-labs/handle-resolver": "^0.3.6",
34
+ "@atproto-labs/identity-resolver": "^0.3.6",
35
+ "@atproto-labs/simple-store": "^0.3.0",
36
+ "@atproto-labs/simple-store-memory": "^0.1.4",
37
+ "@atproto/did": "^0.3.0",
38
+ "@atproto/jwk": "^0.6.0",
39
+ "@atproto/oauth-types": "^0.6.3",
40
+ "@atproto/xrpc": "^0.7.7"
41
41
  },
42
42
  "devDependencies": {
43
43
  "typescript": "^5.6.3"
@@ -49,10 +49,10 @@ export class OAuthAuthorizationServerMetadataResolver extends CachedGetter<
49
49
  }
50
50
 
51
51
  async get(
52
- input: string,
52
+ input: URL | string,
53
53
  options?: GetCachedOptions,
54
54
  ): Promise<OAuthAuthorizationServerMetadata> {
55
- const issuer = oauthIssuerIdentifierSchema.parse(input)
55
+ const issuer = oauthIssuerIdentifierSchema.parse(String(input))
56
56
  if (!this.allowHttpIssuer && issuer.startsWith('http:')) {
57
57
  throw new TypeError(
58
58
  'Unsecure issuer URL protocol only allowed in development and test environments',
@@ -42,36 +42,38 @@ import { OAuthSession } from './oauth-session.js'
42
42
  import { RuntimeImplementation } from './runtime-implementation.js'
43
43
  import { Runtime } from './runtime.js'
44
44
  import {
45
- SessionEventMap,
46
45
  SessionGetter,
46
+ SessionHooks,
47
47
  SessionStore,
48
+ isExpectedSessionError,
48
49
  } from './session-getter.js'
49
50
  import { InternalStateData, StateStore } from './state-store.js'
50
51
  import { AuthorizeOptions, CallbackOptions, ClientMetadata } from './types.js'
51
- import { CustomEventTarget } from './util.js'
52
52
  import { validateClientMetadata } from './validate-client-metadata.js'
53
53
 
54
54
  // Export all types needed to construct OAuthClientOptions
55
- export {
56
- type AuthorizationServerMetadataCache,
57
- type DidCache,
58
- type DpopNonceCache,
59
- type Fetch,
60
- type HandleCache,
61
- type HandleResolver,
62
- type InternalStateData,
63
- Key,
64
- Keyset,
65
- type OAuthClientMetadata,
66
- type OAuthClientMetadataInput,
67
- type OAuthResponseMode,
68
- type ProtectedResourceMetadataCache,
69
- type RuntimeImplementation,
70
- type SessionStore,
71
- type StateStore,
55
+ export type {
56
+ AuthorizationServerMetadataCache,
57
+ CreateIdentityResolverOptions,
58
+ DidCache,
59
+ DpopNonceCache,
60
+ Fetch,
61
+ HandleCache,
62
+ HandleResolver,
63
+ InternalStateData,
64
+ OAuthClientMetadata,
65
+ OAuthClientMetadataInput,
66
+ OAuthResponseMode,
67
+ ProtectedResourceMetadataCache,
68
+ RuntimeImplementation,
69
+ SessionHooks,
70
+ SessionStore,
71
+ StateStore,
72
72
  }
73
73
 
74
- export type OAuthClientOptions = CreateIdentityResolverOptions & {
74
+ export { Key, Keyset }
75
+
76
+ export type OAuthClientOptions = {
75
77
  // Config
76
78
  responseMode: OAuthResponseMode
77
79
  clientMetadata: Readonly<OAuthClientMetadataInput>
@@ -102,9 +104,8 @@ export type OAuthClientOptions = CreateIdentityResolverOptions & {
102
104
  // Services
103
105
  runtimeImplementation: RuntimeImplementation
104
106
  fetch?: Fetch
105
- }
106
-
107
- export type OAuthClientEventMap = SessionEventMap
107
+ } & CreateIdentityResolverOptions &
108
+ SessionHooks
108
109
 
109
110
  export type OAuthClientFetchMetadataOptions = {
110
111
  clientId: OAuthClientIdDiscoverable
@@ -112,7 +113,7 @@ export type OAuthClientFetchMetadataOptions = {
112
113
  signal?: AbortSignal
113
114
  }
114
115
 
115
- export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
116
+ export class OAuthClient {
116
117
  static async fetchMetadata({
117
118
  clientId,
118
119
  fetch = globalThis.fetch,
@@ -181,8 +182,6 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
181
182
  keyset,
182
183
  } = options
183
184
 
184
- super()
185
-
186
185
  this.keyset = keyset
187
186
  ? keyset instanceof Keyset
188
187
  ? keyset
@@ -215,21 +214,13 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
215
214
  dpopNonceCache,
216
215
  )
217
216
 
217
+ this.stateStore = stateStore
218
218
  this.sessionGetter = new SessionGetter(
219
219
  sessionStore,
220
220
  this.serverFactory,
221
221
  this.runtime,
222
+ options,
222
223
  )
223
- this.stateStore = stateStore
224
-
225
- // Proxy sessionGetter events
226
- for (const type of ['deleted', 'updated'] as const) {
227
- this.sessionGetter.addEventListener(type, (event) => {
228
- if (!this.dispatchCustomEvent(type, event.detail)) {
229
- event.preventDefault()
230
- }
231
- })
232
- }
233
224
  }
234
225
 
235
226
  // Exposed as public API for convenience
@@ -411,8 +402,7 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
411
402
 
412
403
  const server = await this.serverFactory.fromIssuer(
413
404
  stateData.iss,
414
- // Using the literal 'legacy' if the authMethod is not defined (because stateData was created through an old version of this lib)
415
- stateData.authMethod ?? 'legacy',
405
+ stateData.authMethod,
416
406
  stateData.dpopKey,
417
407
  )
418
408
 
@@ -446,6 +436,15 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
446
436
  stateData.verifier,
447
437
  options?.redirect_uri ?? server.clientMetadata.redirect_uris[0],
448
438
  )
439
+
440
+ // We revoke any existing session first to avoid leaving orphaned sessions
441
+ // on the AS.
442
+ try {
443
+ await this.revoke(tokenSet.sub)
444
+ } catch {
445
+ // No existing session, or failed to get it. This is fine.
446
+ }
447
+
449
448
  try {
450
449
  await this.sessionGetter.setStored(tokenSet.sub, {
451
450
  dpopKey: stateData.dpopKey,
@@ -472,7 +471,7 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
472
471
  * Load a stored session. This will refresh the token only if needed (about to
473
472
  * expire) by default.
474
473
  *
475
- * @param refresh See {@link SessionGetter.getSession}
474
+ * @see {@link SessionGetter.restore}
476
475
  */
477
476
  async restore(
478
477
  sub: string,
@@ -481,11 +480,8 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
481
480
  // sub arg is lightly typed for convenience of library user
482
481
  assertAtprotoDid(sub)
483
482
 
484
- const {
485
- dpopKey,
486
- authMethod = 'legacy',
487
- tokenSet,
488
- } = await this.sessionGetter.getSession(sub, refresh)
483
+ const { dpopKey, authMethod, tokenSet } =
484
+ await this.sessionGetter.getSession(sub, refresh)
489
485
 
490
486
  try {
491
487
  const server = await this.serverFactory.fromIssuer(
@@ -512,14 +508,15 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
512
508
  // sub arg is lightly typed for convenience of library user
513
509
  assertAtprotoDid(sub)
514
510
 
515
- const {
516
- dpopKey,
517
- authMethod = 'legacy',
518
- tokenSet,
519
- } = await this.sessionGetter.get(sub, {
520
- allowStale: true,
511
+ const res = await this.sessionGetter.getSession(sub, false).catch((err) => {
512
+ if (isExpectedSessionError(err)) return null
513
+ throw err
521
514
  })
522
515
 
516
+ if (!res) return
517
+
518
+ const { dpopKey, authMethod, tokenSet } = res
519
+
523
520
  // NOT using `;(await this.restore(sub, false)).signOut()` because we want
524
521
  // the tokens to be deleted even if it was not possible to fetch the issuer
525
522
  // data.
@@ -19,7 +19,7 @@ export type { GetCachedOptions, OAuthProtectedResourceMetadata }
19
19
 
20
20
  export type ProtectedResourceMetadataCache = SimpleStore<
21
21
  string,
22
- OAuthProtectedResourceMetadata
22
+ OAuthProtectedResourceMetadata | null
23
23
  >
24
24
 
25
25
  export type OAuthProtectedResourceMetadataResolverConfig = {
@@ -31,7 +31,7 @@ export type OAuthProtectedResourceMetadataResolverConfig = {
31
31
  */
32
32
  export class OAuthProtectedResourceMetadataResolver extends CachedGetter<
33
33
  string,
34
- OAuthProtectedResourceMetadata
34
+ OAuthProtectedResourceMetadata | null
35
35
  > {
36
36
  private readonly fetch: Fetch<unknown>
37
37
  private readonly allowHttpResource: boolean
@@ -50,7 +50,7 @@ export class OAuthProtectedResourceMetadataResolver extends CachedGetter<
50
50
  async get(
51
51
  resource: string | URL,
52
52
  options?: GetCachedOptions,
53
- ): Promise<OAuthProtectedResourceMetadata> {
53
+ ): Promise<OAuthProtectedResourceMetadata | null> {
54
54
  const { protocol, origin } = new URL(resource)
55
55
 
56
56
  if (protocol !== 'https:' && protocol !== 'http:') {
@@ -71,7 +71,7 @@ export class OAuthProtectedResourceMetadataResolver extends CachedGetter<
71
71
  private async fetchMetadata(
72
72
  origin: string,
73
73
  options?: GetCachedOptions,
74
- ): Promise<OAuthProtectedResourceMetadata> {
74
+ ): Promise<OAuthProtectedResourceMetadata | null> {
75
75
  const url = new URL(`/.well-known/oauth-protected-resource`, origin)
76
76
  const request = new Request(url, {
77
77
  signal: options?.signal,
@@ -82,6 +82,11 @@ export class OAuthProtectedResourceMetadataResolver extends CachedGetter<
82
82
 
83
83
  const response = await this.fetch(request)
84
84
 
85
+ if (response.status === 404) {
86
+ await cancelBody(response, 'log')
87
+ return null
88
+ }
89
+
85
90
  // https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2
86
91
  if (response.status !== 200) {
87
92
  await cancelBody(response, 'log')
@@ -112,7 +112,7 @@ export class OAuthResolver {
112
112
  }
113
113
 
114
114
  public async getAuthorizationServerMetadata(
115
- issuer: string,
115
+ issuer: string | URL,
116
116
  options?: GetCachedOptions,
117
117
  ): Promise<OAuthAuthorizationServerMetadata> {
118
118
  try {
@@ -135,6 +135,10 @@ export class OAuthResolver {
135
135
  options,
136
136
  )
137
137
 
138
+ if (!rsMetadata) {
139
+ return this.getAuthorizationServerMetadata(pdsUrl, options)
140
+ }
141
+
138
142
  // ATPROTO requires one, and only one, authorization server entry
139
143
  if (rsMetadata.authorization_servers?.length !== 1) {
140
144
  throw new OAuthResolverError(
@@ -2,10 +2,7 @@ import { Key, Keyset } from '@atproto/jwk'
2
2
  import { OAuthAuthorizationServerMetadata } from '@atproto/oauth-types'
3
3
  import { Fetch } from '@atproto-labs/fetch'
4
4
  import { GetCachedOptions } from './oauth-authorization-server-metadata-resolver.js'
5
- import {
6
- ClientAuthMethod,
7
- negotiateClientAuthMethod,
8
- } from './oauth-client-auth.js'
5
+ import { ClientAuthMethod } from './oauth-client-auth.js'
9
6
  import { OAuthResolver } from './oauth-resolver.js'
10
7
  import { DpopNonceCache, OAuthServerAgent } from './oauth-server-agent.js'
11
8
  import { Runtime } from './runtime.js'
@@ -32,7 +29,7 @@ export class OAuthServerFactory {
32
29
  */
33
30
  async fromIssuer(
34
31
  issuer: string,
35
- authMethod: 'legacy' | ClientAuthMethod,
32
+ authMethod: ClientAuthMethod,
36
33
  dpopKey: Key,
37
34
  options?: GetCachedOptions,
38
35
  ) {
@@ -41,17 +38,6 @@ export class OAuthServerFactory {
41
38
  options,
42
39
  )
43
40
 
44
- if (authMethod === 'legacy') {
45
- // @NOTE Because we were previously not storing the authMethod in the
46
- // session data, we provide a backwards compatible implementation by
47
- // computing it here.
48
- authMethod = negotiateClientAuthMethod(
49
- serverMetadata,
50
- this.clientMetadata,
51
- this.keyset,
52
- )
53
- }
54
-
55
41
  return this.fromMetadata(serverMetadata, authMethod, dpopKey)
56
42
  }
57
43
 
@@ -58,10 +58,7 @@ export class OAuthSession {
58
58
  * if, and only if, they are (about to be) expired. Defaults to `undefined`.
59
59
  */
60
60
  protected async getTokenSet(refresh: boolean | 'auto'): Promise<TokenSet> {
61
- const { tokenSet } = await this.sessionGetter.get(this.sub, {
62
- noCache: refresh === true,
63
- allowStale: refresh === false,
64
- })
61
+ const { tokenSet } = await this.sessionGetter.getSession(this.sub, refresh)
65
62
 
66
63
  return tokenSet
67
64
  }