@bskyprism/atproto-oauth-client-cloudflare-workers 0.2.2

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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/lib/did-cache-kv.d.ts +18 -0
  4. package/lib/did-cache-kv.js +26 -0
  5. package/lib/did-resolver/did-cache-memory.d.ts +7 -0
  6. package/lib/did-resolver/did-cache-memory.js +10 -0
  7. package/lib/did-resolver/did-cache.d.ts +14 -0
  8. package/lib/did-resolver/did-cache.js +10 -0
  9. package/lib/did-resolver/did-method.d.ts +11 -0
  10. package/lib/did-resolver/did-method.js +1 -0
  11. package/lib/did-resolver/did-resolver-base.d.ts +9 -0
  12. package/lib/did-resolver/did-resolver-base.js +36 -0
  13. package/lib/did-resolver/did-resolver-common.d.ts +8 -0
  14. package/lib/did-resolver/did-resolver-common.js +11 -0
  15. package/lib/did-resolver/did-resolver.d.ts +6 -0
  16. package/lib/did-resolver/did-resolver.js +1 -0
  17. package/lib/did-resolver/index.d.ts +6 -0
  18. package/lib/did-resolver/index.js +7 -0
  19. package/lib/did-resolver/methods/plc.d.ts +43 -0
  20. package/lib/did-resolver/methods/plc.js +22 -0
  21. package/lib/did-resolver/methods/web.d.ts +43 -0
  22. package/lib/did-resolver/methods/web.js +42 -0
  23. package/lib/did-resolver/methods.d.ts +2 -0
  24. package/lib/did-resolver/methods.js +2 -0
  25. package/lib/did-resolver/util.d.ts +3 -0
  26. package/lib/did-resolver/util.js +1 -0
  27. package/lib/dpop-store.d.ts +21 -0
  28. package/lib/dpop-store.js +25 -0
  29. package/lib/handle-cache-kv.d.ts +17 -0
  30. package/lib/handle-cache-kv.js +31 -0
  31. package/lib/handle-resolver/atproto-doh-handle-resolver.d.ts +8 -0
  32. package/lib/handle-resolver/atproto-doh-handle-resolver.js +94 -0
  33. package/lib/handle-resolver/atproto-handle-resolver.d.ts +21 -0
  34. package/lib/handle-resolver/atproto-handle-resolver.js +46 -0
  35. package/lib/handle-resolver/cached-handle-resolver.d.ts +12 -0
  36. package/lib/handle-resolver/cached-handle-resolver.js +17 -0
  37. package/lib/handle-resolver/handle-resolver-error.d.ts +3 -0
  38. package/lib/handle-resolver/handle-resolver-error.js +6 -0
  39. package/lib/handle-resolver/index.d.ts +6 -0
  40. package/lib/handle-resolver/index.js +8 -0
  41. package/lib/handle-resolver/internal-resolvers/dns-handle-resolver.d.ts +11 -0
  42. package/lib/handle-resolver/internal-resolvers/dns-handle-resolver.js +28 -0
  43. package/lib/handle-resolver/internal-resolvers/well-known-handler-resolver.d.ts +17 -0
  44. package/lib/handle-resolver/internal-resolvers/well-known-handler-resolver.js +28 -0
  45. package/lib/handle-resolver/types.d.ts +25 -0
  46. package/lib/handle-resolver/types.js +10 -0
  47. package/lib/handle-resolver/xrpc-handle-resolver.d.ts +31 -0
  48. package/lib/handle-resolver/xrpc-handle-resolver.js +45 -0
  49. package/lib/handle-resolver.d.ts +20 -0
  50. package/lib/handle-resolver.js +19 -0
  51. package/lib/identity-resolver/atproto-identity-resolver.d.ts +20 -0
  52. package/lib/identity-resolver/atproto-identity-resolver.js +72 -0
  53. package/lib/identity-resolver/constants.d.ts +1 -0
  54. package/lib/identity-resolver/constants.js +1 -0
  55. package/lib/identity-resolver/identity-resolver-error.d.ts +3 -0
  56. package/lib/identity-resolver/identity-resolver-error.js +6 -0
  57. package/lib/identity-resolver/identity-resolver.d.ts +19 -0
  58. package/lib/identity-resolver/identity-resolver.js +1 -0
  59. package/lib/identity-resolver/index.d.ts +5 -0
  60. package/lib/identity-resolver/index.js +5 -0
  61. package/lib/identity-resolver/util.d.ts +12 -0
  62. package/lib/identity-resolver/util.js +35 -0
  63. package/lib/index.d.ts +7 -0
  64. package/lib/index.js +6 -0
  65. package/lib/oauth-client/atproto-token-response.d.ts +100 -0
  66. package/lib/oauth-client/atproto-token-response.js +15 -0
  67. package/lib/oauth-client/constants.d.ts +4 -0
  68. package/lib/oauth-client/constants.js +4 -0
  69. package/lib/oauth-client/errors/auth-method-unsatisfiable-error.d.ts +2 -0
  70. package/lib/oauth-client/errors/auth-method-unsatisfiable-error.js +2 -0
  71. package/lib/oauth-client/errors/token-invalid-error.d.ts +6 -0
  72. package/lib/oauth-client/errors/token-invalid-error.js +6 -0
  73. package/lib/oauth-client/errors/token-refresh-error.d.ts +6 -0
  74. package/lib/oauth-client/errors/token-refresh-error.js +6 -0
  75. package/lib/oauth-client/errors/token-revoked-error.d.ts +6 -0
  76. package/lib/oauth-client/errors/token-revoked-error.js +6 -0
  77. package/lib/oauth-client/fetch-dpop.d.ts +19 -0
  78. package/lib/oauth-client/fetch-dpop.js +176 -0
  79. package/lib/oauth-client/identity-resolver.d.ts +15 -0
  80. package/lib/oauth-client/identity-resolver.js +33 -0
  81. package/lib/oauth-client/index.d.ts +17 -0
  82. package/lib/oauth-client/index.js +17 -0
  83. package/lib/oauth-client/lock.d.ts +2 -0
  84. package/lib/oauth-client/lock.js +28 -0
  85. package/lib/oauth-client/oauth-authorization-server-metadata-resolver.d.ts +18 -0
  86. package/lib/oauth-client/oauth-authorization-server-metadata-resolver.js +53 -0
  87. package/lib/oauth-client/oauth-callback-error.d.ts +6 -0
  88. package/lib/oauth-client/oauth-callback-error.js +13 -0
  89. package/lib/oauth-client/oauth-client-auth.d.ts +22 -0
  90. package/lib/oauth-client/oauth-client-auth.js +127 -0
  91. package/lib/oauth-client/oauth-client.d.ts +311 -0
  92. package/lib/oauth-client/oauth-client.js +276 -0
  93. package/lib/oauth-client/oauth-protected-resource-metadata-resolver.d.ts +18 -0
  94. package/lib/oauth-client/oauth-protected-resource-metadata-resolver.js +49 -0
  95. package/lib/oauth-client/oauth-resolver-error.d.ts +6 -0
  96. package/lib/oauth-client/oauth-resolver-error.js +18 -0
  97. package/lib/oauth-client/oauth-resolver.d.ts +71 -0
  98. package/lib/oauth-client/oauth-resolver.js +117 -0
  99. package/lib/oauth-client/oauth-response-error.d.ts +10 -0
  100. package/lib/oauth-client/oauth-response-error.js +22 -0
  101. package/lib/oauth-client/oauth-server-agent.d.ts +54 -0
  102. package/lib/oauth-client/oauth-server-agent.js +250 -0
  103. package/lib/oauth-client/oauth-server-factory.d.ts +32 -0
  104. package/lib/oauth-client/oauth-server-factory.js +37 -0
  105. package/lib/oauth-client/oauth-session.d.ts +33 -0
  106. package/lib/oauth-client/oauth-session.js +122 -0
  107. package/lib/oauth-client/runtime-implementation.d.ts +16 -0
  108. package/lib/oauth-client/runtime-implementation.js +1 -0
  109. package/lib/oauth-client/runtime.d.ts +25 -0
  110. package/lib/oauth-client/runtime.js +99 -0
  111. package/lib/oauth-client/session-getter.d.ts +54 -0
  112. package/lib/oauth-client/session-getter.js +260 -0
  113. package/lib/oauth-client/state-store.d.ts +12 -0
  114. package/lib/oauth-client/state-store.js +1 -0
  115. package/lib/oauth-client/types.d.ts +1365 -0
  116. package/lib/oauth-client/types.js +8 -0
  117. package/lib/oauth-client/util.d.ts +25 -0
  118. package/lib/oauth-client/util.js +139 -0
  119. package/lib/oauth-client/validate-client-metadata.d.ts +4 -0
  120. package/lib/oauth-client/validate-client-metadata.js +68 -0
  121. package/lib/oauth-client.d.ts +27 -0
  122. package/lib/oauth-client.js +30 -0
  123. package/lib/resolve-txt-factory.d.ts +3 -0
  124. package/lib/resolve-txt-factory.js +80 -0
  125. package/lib/session-store-kv.d.ts +9 -0
  126. package/lib/session-store-kv.js +20 -0
  127. package/lib/state-store-kv.d.ts +9 -0
  128. package/lib/state-store-kv.js +20 -0
  129. package/lib/util.d.ts +18 -0
  130. package/lib/util.js +5 -0
  131. package/package.json +58 -0
@@ -0,0 +1,260 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { CachedGetter, } from "@atproto-labs/simple-store";
54
+ import { AuthMethodUnsatisfiableError } from "./errors/auth-method-unsatisfiable-error.js";
55
+ import { TokenInvalidError } from "./errors/token-invalid-error.js";
56
+ import { TokenRefreshError } from "./errors/token-refresh-error.js";
57
+ import { TokenRevokedError } from "./errors/token-revoked-error.js";
58
+ import { OAuthResponseError } from "./oauth-response-error.js";
59
+ import { CustomEventTarget, combineSignals, timeoutSignal } from "./util.js";
60
+ /**
61
+ * There are several advantages to wrapping the sessionStore in a (single)
62
+ * CachedGetter, the main of which is that the cached getter will ensure that at
63
+ * most one fresh call is ever being made. Another advantage, is that it
64
+ * contains the logic for reading from the cache which, if the cache is based on
65
+ * localStorage/indexedDB, will sync across multiple tabs (for a given sub).
66
+ */
67
+ export class SessionGetter extends CachedGetter {
68
+ constructor(sessionStore, serverFactory, runtime) {
69
+ super(async (sub, options, storedSession) => {
70
+ // There needs to be a previous session to be able to refresh. If
71
+ // storedSession is undefined, it means that the store does not contain
72
+ // a session for the given sub.
73
+ if (storedSession === undefined) {
74
+ // Because the session is not in the store, this.delStored() method
75
+ // will not be called by the CachedGetter class (because there is
76
+ // nothing to delete). This would typically happen if there is no
77
+ // synchronization mechanism between instances of this class. Let's
78
+ // make sure an event is dispatched here if this occurs.
79
+ const msg = "The session was deleted by another process";
80
+ const cause = new TokenRefreshError(sub, msg);
81
+ this.dispatchEvent("deleted", { sub, cause });
82
+ throw cause;
83
+ }
84
+ // From this point forward, throwing a TokenRefreshError will result in
85
+ // this.delStored() being called, resulting in an event being
86
+ // dispatched, even if the session was removed from the store through a
87
+ // concurrent access (which, normally, should not happen if a proper
88
+ // runtime lock was provided).
89
+ const { dpopKey, authMethod = "legacy", tokenSet } = storedSession;
90
+ if (sub !== tokenSet.sub) {
91
+ // Fool-proofing (e.g. against invalid session storage)
92
+ throw new TokenRefreshError(sub, "Stored session sub mismatch");
93
+ }
94
+ if (!tokenSet.refresh_token) {
95
+ throw new TokenRefreshError(sub, "No refresh token available");
96
+ }
97
+ // Since refresh tokens can only be used once, we might run into
98
+ // concurrency issues if multiple instances (e.g. browser tabs) are
99
+ // trying to refresh the same token simultaneously. The chances of this
100
+ // happening when multiple instances are started simultaneously is
101
+ // reduced by randomizing the expiry time (see isStale() below). The
102
+ // best solution is to use a mutex/lock to ensure that only one instance
103
+ // is refreshing the token at a time (runtime.usingLock) but that is not
104
+ // always possible. If no lock implementation is provided, we will use
105
+ // the store to check if a concurrent refresh occurred.
106
+ const server = await serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey);
107
+ // Because refresh tokens can only be used once, we must not use the
108
+ // "signal" to abort the refresh, or throw any abort error beyond this
109
+ // point. Any thrown error beyond this point will prevent the
110
+ // TokenGetter from obtaining, and storing, the new token set,
111
+ // effectively rendering the currently saved session unusable.
112
+ options?.signal?.throwIfAborted();
113
+ try {
114
+ const newTokenSet = await server.refresh(tokenSet);
115
+ if (sub !== newTokenSet.sub) {
116
+ // The server returned another sub. Was the tokenSet manipulated?
117
+ throw new TokenRefreshError(sub, "Token set sub mismatch");
118
+ }
119
+ return {
120
+ dpopKey,
121
+ tokenSet: newTokenSet,
122
+ authMethod: server.authMethod,
123
+ };
124
+ }
125
+ catch (cause) {
126
+ console.error(cause);
127
+ // If the refresh token is invalid, let's try to recover from
128
+ // concurrency issues, or make sure the session is deleted by throwing
129
+ // a TokenRefreshError.
130
+ if (cause instanceof OAuthResponseError &&
131
+ cause.status === 400 &&
132
+ cause.error === "invalid_grant") {
133
+ // In case there is no lock implementation in the runtime, we will
134
+ // wait for a short time to give the other concurrent instances a
135
+ // chance to finish their refreshing of the token. If a concurrent
136
+ // refresh did occur, we will pretend that this one succeeded.
137
+ if (!runtime.hasImplementationLock) {
138
+ await new Promise((r) => setTimeout(r, 1000));
139
+ const stored = await this.getStored(sub);
140
+ if (stored === undefined) {
141
+ // A concurrent refresh occurred and caused the session to be
142
+ // deleted (for a reason we can't know at this point).
143
+ // Using a distinct error message mainly for debugging
144
+ // purposes. Also, throwing a TokenRefreshError to trigger
145
+ // deletion through the deleteOnError callback.
146
+ const msg = "The session was deleted by another process";
147
+ throw new TokenRefreshError(sub, msg, { cause });
148
+ }
149
+ else if (stored.tokenSet.access_token !== tokenSet.access_token ||
150
+ stored.tokenSet.refresh_token !== tokenSet.refresh_token) {
151
+ // A concurrent refresh occurred. Pretend this one succeeded.
152
+ return stored;
153
+ }
154
+ else {
155
+ // There were no concurrent refresh. The token is (likely)
156
+ // simply no longer valid.
157
+ }
158
+ }
159
+ // Make sure the session gets deleted from the store
160
+ const msg = cause.errorDescription ?? "The session was revoked";
161
+ throw new TokenRefreshError(sub, msg, { cause });
162
+ }
163
+ throw cause;
164
+ }
165
+ }, sessionStore, {
166
+ isStale: (sub, { tokenSet }) => {
167
+ return (tokenSet.expires_at != null &&
168
+ new Date(tokenSet.expires_at).getTime() <
169
+ Date.now() +
170
+ // Add some lee way to ensure the token is not expired when it
171
+ // reaches the server.
172
+ 10e3 +
173
+ // Add some randomness to reduce the chances of multiple
174
+ // instances trying to refresh the token at the same.
175
+ 30e3 * Math.random());
176
+ },
177
+ onStoreError: async (err, sub, { tokenSet, dpopKey, authMethod = "legacy" }) => {
178
+ if (!(err instanceof AuthMethodUnsatisfiableError)) {
179
+ // If the error was an AuthMethodUnsatisfiableError, there is no
180
+ // point in trying to call `fromIssuer`.
181
+ try {
182
+ // If the token data cannot be stored, let's revoke it
183
+ const server = await serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey);
184
+ await server.revoke(tokenSet.refresh_token ?? tokenSet.access_token);
185
+ }
186
+ catch {
187
+ // Let the original error propagate
188
+ }
189
+ }
190
+ throw err;
191
+ },
192
+ deleteOnError: async (err) => err instanceof TokenRefreshError ||
193
+ err instanceof TokenRevokedError ||
194
+ err instanceof TokenInvalidError ||
195
+ err instanceof AuthMethodUnsatisfiableError,
196
+ });
197
+ this.runtime = runtime;
198
+ this.eventTarget = new CustomEventTarget();
199
+ }
200
+ addEventListener(type, callback, options) {
201
+ this.eventTarget.addEventListener(type, callback, options);
202
+ }
203
+ removeEventListener(type, callback, options) {
204
+ this.eventTarget.removeEventListener(type, callback, options);
205
+ }
206
+ dispatchEvent(type, detail) {
207
+ return this.eventTarget.dispatchCustomEvent(type, detail);
208
+ }
209
+ async setStored(sub, session) {
210
+ // Prevent tampering with the stored value
211
+ if (sub !== session.tokenSet.sub) {
212
+ throw new TypeError("Token set does not match the expected sub");
213
+ }
214
+ await super.setStored(sub, session);
215
+ this.dispatchEvent("updated", { sub, ...session });
216
+ }
217
+ async delStored(sub, cause) {
218
+ await super.delStored(sub, cause);
219
+ this.dispatchEvent("deleted", { sub, cause });
220
+ }
221
+ /**
222
+ * @param refresh When `true`, the credentials will be refreshed even if they
223
+ * are not expired. When `false`, the credentials will not be refreshed even
224
+ * if they are expired. When `undefined`, the credentials will be refreshed
225
+ * if, and only if, they are (about to be) expired. Defaults to `undefined`.
226
+ */
227
+ async getSession(sub, refresh) {
228
+ return this.get(sub, {
229
+ noCache: refresh === true,
230
+ allowStale: refresh === false,
231
+ });
232
+ }
233
+ async get(sub, options) {
234
+ const session = await this.runtime.usingLock(`@atproto-oauth-client-${sub}`, async () => {
235
+ const env_1 = { stack: [], error: void 0, hasError: false };
236
+ try {
237
+ // Make sure, even if there is no signal in the options, that the
238
+ // request will be cancelled after at most 30 seconds.
239
+ const signal = __addDisposableResource(env_1, timeoutSignal(30e3, options), false);
240
+ const abortController = __addDisposableResource(env_1, combineSignals([options?.signal, signal]), false);
241
+ return await super.get(sub, {
242
+ ...options,
243
+ signal: abortController.signal,
244
+ });
245
+ }
246
+ catch (e_1) {
247
+ env_1.error = e_1;
248
+ env_1.hasError = true;
249
+ }
250
+ finally {
251
+ __disposeResources(env_1);
252
+ }
253
+ });
254
+ if (sub !== session.tokenSet.sub) {
255
+ // Fool-proofing (e.g. against invalid session storage)
256
+ throw new Error("Token set does not match the expected sub");
257
+ }
258
+ return session;
259
+ }
260
+ }
@@ -0,0 +1,12 @@
1
+ import { Key } from "@atproto/jwk";
2
+ import { SimpleStore } from "@atproto-labs/simple-store";
3
+ import { ClientAuthMethod } from "./oauth-client-auth.js";
4
+ export type InternalStateData = {
5
+ iss: string;
6
+ dpopKey: Key;
7
+ /** @note optional for legacy reasons */
8
+ authMethod?: ClientAuthMethod;
9
+ verifier?: string;
10
+ appState?: string;
11
+ };
12
+ export type StateStore = SimpleStore<string, InternalStateData>;
@@ -0,0 +1 @@
1
+ export {};