@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.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/lib/did-cache-kv.d.ts +18 -0
- package/lib/did-cache-kv.js +26 -0
- package/lib/did-resolver/did-cache-memory.d.ts +7 -0
- package/lib/did-resolver/did-cache-memory.js +10 -0
- package/lib/did-resolver/did-cache.d.ts +14 -0
- package/lib/did-resolver/did-cache.js +10 -0
- package/lib/did-resolver/did-method.d.ts +11 -0
- package/lib/did-resolver/did-method.js +1 -0
- package/lib/did-resolver/did-resolver-base.d.ts +9 -0
- package/lib/did-resolver/did-resolver-base.js +36 -0
- package/lib/did-resolver/did-resolver-common.d.ts +8 -0
- package/lib/did-resolver/did-resolver-common.js +11 -0
- package/lib/did-resolver/did-resolver.d.ts +6 -0
- package/lib/did-resolver/did-resolver.js +1 -0
- package/lib/did-resolver/index.d.ts +6 -0
- package/lib/did-resolver/index.js +7 -0
- package/lib/did-resolver/methods/plc.d.ts +43 -0
- package/lib/did-resolver/methods/plc.js +22 -0
- package/lib/did-resolver/methods/web.d.ts +43 -0
- package/lib/did-resolver/methods/web.js +42 -0
- package/lib/did-resolver/methods.d.ts +2 -0
- package/lib/did-resolver/methods.js +2 -0
- package/lib/did-resolver/util.d.ts +3 -0
- package/lib/did-resolver/util.js +1 -0
- package/lib/dpop-store.d.ts +21 -0
- package/lib/dpop-store.js +25 -0
- package/lib/handle-cache-kv.d.ts +17 -0
- package/lib/handle-cache-kv.js +31 -0
- package/lib/handle-resolver/atproto-doh-handle-resolver.d.ts +8 -0
- package/lib/handle-resolver/atproto-doh-handle-resolver.js +94 -0
- package/lib/handle-resolver/atproto-handle-resolver.d.ts +21 -0
- package/lib/handle-resolver/atproto-handle-resolver.js +46 -0
- package/lib/handle-resolver/cached-handle-resolver.d.ts +12 -0
- package/lib/handle-resolver/cached-handle-resolver.js +17 -0
- package/lib/handle-resolver/handle-resolver-error.d.ts +3 -0
- package/lib/handle-resolver/handle-resolver-error.js +6 -0
- package/lib/handle-resolver/index.d.ts +6 -0
- package/lib/handle-resolver/index.js +8 -0
- package/lib/handle-resolver/internal-resolvers/dns-handle-resolver.d.ts +11 -0
- package/lib/handle-resolver/internal-resolvers/dns-handle-resolver.js +28 -0
- package/lib/handle-resolver/internal-resolvers/well-known-handler-resolver.d.ts +17 -0
- package/lib/handle-resolver/internal-resolvers/well-known-handler-resolver.js +28 -0
- package/lib/handle-resolver/types.d.ts +25 -0
- package/lib/handle-resolver/types.js +10 -0
- package/lib/handle-resolver/xrpc-handle-resolver.d.ts +31 -0
- package/lib/handle-resolver/xrpc-handle-resolver.js +45 -0
- package/lib/handle-resolver.d.ts +20 -0
- package/lib/handle-resolver.js +19 -0
- package/lib/identity-resolver/atproto-identity-resolver.d.ts +20 -0
- package/lib/identity-resolver/atproto-identity-resolver.js +72 -0
- package/lib/identity-resolver/constants.d.ts +1 -0
- package/lib/identity-resolver/constants.js +1 -0
- package/lib/identity-resolver/identity-resolver-error.d.ts +3 -0
- package/lib/identity-resolver/identity-resolver-error.js +6 -0
- package/lib/identity-resolver/identity-resolver.d.ts +19 -0
- package/lib/identity-resolver/identity-resolver.js +1 -0
- package/lib/identity-resolver/index.d.ts +5 -0
- package/lib/identity-resolver/index.js +5 -0
- package/lib/identity-resolver/util.d.ts +12 -0
- package/lib/identity-resolver/util.js +35 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +6 -0
- package/lib/oauth-client/atproto-token-response.d.ts +100 -0
- package/lib/oauth-client/atproto-token-response.js +15 -0
- package/lib/oauth-client/constants.d.ts +4 -0
- package/lib/oauth-client/constants.js +4 -0
- package/lib/oauth-client/errors/auth-method-unsatisfiable-error.d.ts +2 -0
- package/lib/oauth-client/errors/auth-method-unsatisfiable-error.js +2 -0
- package/lib/oauth-client/errors/token-invalid-error.d.ts +6 -0
- package/lib/oauth-client/errors/token-invalid-error.js +6 -0
- package/lib/oauth-client/errors/token-refresh-error.d.ts +6 -0
- package/lib/oauth-client/errors/token-refresh-error.js +6 -0
- package/lib/oauth-client/errors/token-revoked-error.d.ts +6 -0
- package/lib/oauth-client/errors/token-revoked-error.js +6 -0
- package/lib/oauth-client/fetch-dpop.d.ts +19 -0
- package/lib/oauth-client/fetch-dpop.js +176 -0
- package/lib/oauth-client/identity-resolver.d.ts +15 -0
- package/lib/oauth-client/identity-resolver.js +33 -0
- package/lib/oauth-client/index.d.ts +17 -0
- package/lib/oauth-client/index.js +17 -0
- package/lib/oauth-client/lock.d.ts +2 -0
- package/lib/oauth-client/lock.js +28 -0
- package/lib/oauth-client/oauth-authorization-server-metadata-resolver.d.ts +18 -0
- package/lib/oauth-client/oauth-authorization-server-metadata-resolver.js +53 -0
- package/lib/oauth-client/oauth-callback-error.d.ts +6 -0
- package/lib/oauth-client/oauth-callback-error.js +13 -0
- package/lib/oauth-client/oauth-client-auth.d.ts +22 -0
- package/lib/oauth-client/oauth-client-auth.js +127 -0
- package/lib/oauth-client/oauth-client.d.ts +311 -0
- package/lib/oauth-client/oauth-client.js +276 -0
- package/lib/oauth-client/oauth-protected-resource-metadata-resolver.d.ts +18 -0
- package/lib/oauth-client/oauth-protected-resource-metadata-resolver.js +49 -0
- package/lib/oauth-client/oauth-resolver-error.d.ts +6 -0
- package/lib/oauth-client/oauth-resolver-error.js +18 -0
- package/lib/oauth-client/oauth-resolver.d.ts +71 -0
- package/lib/oauth-client/oauth-resolver.js +117 -0
- package/lib/oauth-client/oauth-response-error.d.ts +10 -0
- package/lib/oauth-client/oauth-response-error.js +22 -0
- package/lib/oauth-client/oauth-server-agent.d.ts +54 -0
- package/lib/oauth-client/oauth-server-agent.js +250 -0
- package/lib/oauth-client/oauth-server-factory.d.ts +32 -0
- package/lib/oauth-client/oauth-server-factory.js +37 -0
- package/lib/oauth-client/oauth-session.d.ts +33 -0
- package/lib/oauth-client/oauth-session.js +122 -0
- package/lib/oauth-client/runtime-implementation.d.ts +16 -0
- package/lib/oauth-client/runtime-implementation.js +1 -0
- package/lib/oauth-client/runtime.d.ts +25 -0
- package/lib/oauth-client/runtime.js +99 -0
- package/lib/oauth-client/session-getter.d.ts +54 -0
- package/lib/oauth-client/session-getter.js +260 -0
- package/lib/oauth-client/state-store.d.ts +12 -0
- package/lib/oauth-client/state-store.js +1 -0
- package/lib/oauth-client/types.d.ts +1365 -0
- package/lib/oauth-client/types.js +8 -0
- package/lib/oauth-client/util.d.ts +25 -0
- package/lib/oauth-client/util.js +139 -0
- package/lib/oauth-client/validate-client-metadata.d.ts +4 -0
- package/lib/oauth-client/validate-client-metadata.js +68 -0
- package/lib/oauth-client.d.ts +27 -0
- package/lib/oauth-client.js +30 -0
- package/lib/resolve-txt-factory.d.ts +3 -0
- package/lib/resolve-txt-factory.js +80 -0
- package/lib/session-store-kv.d.ts +9 -0
- package/lib/session-store-kv.js +20 -0
- package/lib/state-store-kv.d.ts +9 -0
- package/lib/state-store-kv.js +20 -0
- package/lib/util.d.ts +18 -0
- package/lib/util.js +5 -0
- package/package.json +58 -0
|
@@ -0,0 +1,250 @@
|
|
|
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 { oauthParResponseSchema, } from "@atproto/oauth-types";
|
|
54
|
+
import { bindFetch, fetchJsonProcessor, } from "@atproto-labs/fetch";
|
|
55
|
+
import { atprotoTokenResponseSchema, } from "./atproto-token-response.js";
|
|
56
|
+
import { TokenRefreshError } from "./errors/token-refresh-error.js";
|
|
57
|
+
import { dpopFetchWrapper } from "./fetch-dpop.js";
|
|
58
|
+
import { createClientCredentialsFactory, } from "./oauth-client-auth.js";
|
|
59
|
+
import { OAuthResponseError } from "./oauth-response-error.js";
|
|
60
|
+
import { timeoutSignal } from "./util.js";
|
|
61
|
+
export class OAuthServerAgent {
|
|
62
|
+
/**
|
|
63
|
+
* @throws see {@link createClientCredentialsFactory}
|
|
64
|
+
*/
|
|
65
|
+
constructor(authMethod, dpopKey, serverMetadata, clientMetadata, dpopNonces, oauthResolver, runtime, keyset, fetch) {
|
|
66
|
+
this.authMethod = authMethod;
|
|
67
|
+
this.dpopKey = dpopKey;
|
|
68
|
+
this.serverMetadata = serverMetadata;
|
|
69
|
+
this.clientMetadata = clientMetadata;
|
|
70
|
+
this.dpopNonces = dpopNonces;
|
|
71
|
+
this.oauthResolver = oauthResolver;
|
|
72
|
+
this.runtime = runtime;
|
|
73
|
+
this.keyset = keyset;
|
|
74
|
+
this.clientCredentialsFactory = createClientCredentialsFactory(authMethod, serverMetadata, clientMetadata, runtime, keyset);
|
|
75
|
+
this.dpopFetch = dpopFetchWrapper({
|
|
76
|
+
fetch: bindFetch(fetch),
|
|
77
|
+
key: dpopKey,
|
|
78
|
+
supportedAlgs: serverMetadata.dpop_signing_alg_values_supported,
|
|
79
|
+
sha256: async (v) => runtime.sha256(v),
|
|
80
|
+
nonces: dpopNonces,
|
|
81
|
+
isAuthServer: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
get issuer() {
|
|
85
|
+
return this.serverMetadata.issuer;
|
|
86
|
+
}
|
|
87
|
+
async revoke(token) {
|
|
88
|
+
try {
|
|
89
|
+
await this.request("revocation", { token });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Don't care
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async exchangeCode(code, codeVerifier) {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const tokenResponse = await this.request("token", {
|
|
98
|
+
grant_type: "authorization_code",
|
|
99
|
+
redirect_uri: this.clientMetadata.redirect_uris[0],
|
|
100
|
+
code,
|
|
101
|
+
code_verifier: codeVerifier,
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
// /!\ IMPORTANT /!\
|
|
105
|
+
//
|
|
106
|
+
// The tokenResponse MUST always be valid before the "sub" it contains
|
|
107
|
+
// can be trusted (see Atproto's OAuth spec for details).
|
|
108
|
+
const aud = await this.verifyIssuer(tokenResponse.sub);
|
|
109
|
+
return {
|
|
110
|
+
aud,
|
|
111
|
+
sub: tokenResponse.sub,
|
|
112
|
+
iss: this.issuer,
|
|
113
|
+
scope: tokenResponse.scope,
|
|
114
|
+
refresh_token: tokenResponse.refresh_token,
|
|
115
|
+
access_token: tokenResponse.access_token,
|
|
116
|
+
token_type: tokenResponse.token_type,
|
|
117
|
+
expires_at: typeof tokenResponse.expires_in === "number"
|
|
118
|
+
? new Date(now + tokenResponse.expires_in * 1000).toISOString()
|
|
119
|
+
: undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
await this.revoke(tokenResponse.access_token);
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async refresh(tokenSet) {
|
|
128
|
+
if (!tokenSet.refresh_token) {
|
|
129
|
+
throw new TokenRefreshError(tokenSet.sub, "No refresh token available");
|
|
130
|
+
}
|
|
131
|
+
// /!\ IMPORTANT /!\
|
|
132
|
+
//
|
|
133
|
+
// The "sub" MUST be a DID, whose issuer authority is indeed the server we
|
|
134
|
+
// are trying to obtain credentials from. Note that we are doing this
|
|
135
|
+
// *before* we actually try to refresh the token:
|
|
136
|
+
// 1) To avoid unnecessary refresh
|
|
137
|
+
// 2) So that the refresh is the last async operation, ensuring as few
|
|
138
|
+
// async operations happen before the result gets a chance to be stored.
|
|
139
|
+
const aud = await this.verifyIssuer(tokenSet.sub);
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const tokenResponse = await this.request("token", {
|
|
142
|
+
grant_type: "refresh_token",
|
|
143
|
+
refresh_token: tokenSet.refresh_token,
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
aud,
|
|
147
|
+
sub: tokenSet.sub,
|
|
148
|
+
iss: this.issuer,
|
|
149
|
+
scope: tokenResponse.scope,
|
|
150
|
+
refresh_token: tokenResponse.refresh_token,
|
|
151
|
+
access_token: tokenResponse.access_token,
|
|
152
|
+
token_type: tokenResponse.token_type,
|
|
153
|
+
expires_at: typeof tokenResponse.expires_in === "number"
|
|
154
|
+
? new Date(now + tokenResponse.expires_in * 1000).toISOString()
|
|
155
|
+
: undefined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* VERY IMPORTANT ! Always call this to process token responses.
|
|
160
|
+
*
|
|
161
|
+
* Whenever an OAuth token response is received, we **MUST** verify that the
|
|
162
|
+
* "sub" is a DID, whose issuer authority is indeed the server we just
|
|
163
|
+
* obtained credentials from. This check is a critical step to actually be
|
|
164
|
+
* able to use the "sub" (DID) as being the actual user's identifier.
|
|
165
|
+
*
|
|
166
|
+
* @returns The user's PDS URL (the resource server for the user)
|
|
167
|
+
*/
|
|
168
|
+
async verifyIssuer(sub) {
|
|
169
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
170
|
+
try {
|
|
171
|
+
const signal = __addDisposableResource(env_1, timeoutSignal(10e3), false);
|
|
172
|
+
const resolved = await this.oauthResolver.resolveFromIdentity(sub, {
|
|
173
|
+
noCache: true,
|
|
174
|
+
allowStale: false,
|
|
175
|
+
signal,
|
|
176
|
+
});
|
|
177
|
+
if (this.issuer !== resolved.metadata.issuer) {
|
|
178
|
+
// Best case scenario; the user switched PDS. Worst case scenario; a bad
|
|
179
|
+
// actor is trying to impersonate a user. In any case, we must not allow
|
|
180
|
+
// this token to be used.
|
|
181
|
+
throw new TypeError("Issuer mismatch");
|
|
182
|
+
}
|
|
183
|
+
return resolved.pds.href;
|
|
184
|
+
}
|
|
185
|
+
catch (e_1) {
|
|
186
|
+
env_1.error = e_1;
|
|
187
|
+
env_1.hasError = true;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
__disposeResources(env_1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async request(endpoint, payload) {
|
|
194
|
+
const url = this.serverMetadata[`${endpoint}_endpoint`];
|
|
195
|
+
if (!url)
|
|
196
|
+
throw new Error(`No ${endpoint} endpoint available`);
|
|
197
|
+
const auth = await this.clientCredentialsFactory();
|
|
198
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-3.2.2
|
|
199
|
+
// https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
|
|
200
|
+
// https://datatracker.ietf.org/doc/html/rfc7662#section-2.1
|
|
201
|
+
// https://datatracker.ietf.org/doc/html/rfc9126#section-2
|
|
202
|
+
const { response, json } = await this.dpopFetch(url, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
...auth.headers,
|
|
206
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
207
|
+
},
|
|
208
|
+
body: wwwFormUrlEncode({ ...payload, ...auth.payload }),
|
|
209
|
+
}).then(fetchJsonProcessor());
|
|
210
|
+
if (response.ok) {
|
|
211
|
+
switch (endpoint) {
|
|
212
|
+
case "token":
|
|
213
|
+
return atprotoTokenResponseSchema.parse(json);
|
|
214
|
+
case "pushed_authorization_request":
|
|
215
|
+
return oauthParResponseSchema.parse(json);
|
|
216
|
+
default:
|
|
217
|
+
return json;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
throw new OAuthResponseError(response, json);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function wwwFormUrlEncode(payload) {
|
|
226
|
+
return new URLSearchParams(Object.entries(payload)
|
|
227
|
+
.filter(entryHasDefinedValue)
|
|
228
|
+
.map(stringifyEntryValue)).toString();
|
|
229
|
+
}
|
|
230
|
+
function entryHasDefinedValue(entry) {
|
|
231
|
+
return entry[1] !== undefined;
|
|
232
|
+
}
|
|
233
|
+
function stringifyEntryValue(entry) {
|
|
234
|
+
const name = entry[0];
|
|
235
|
+
const value = entry[1];
|
|
236
|
+
switch (typeof value) {
|
|
237
|
+
case "string":
|
|
238
|
+
return [name, value];
|
|
239
|
+
case "number":
|
|
240
|
+
case "boolean":
|
|
241
|
+
return [name, String(value)];
|
|
242
|
+
default: {
|
|
243
|
+
const enc = JSON.stringify(value);
|
|
244
|
+
if (enc === undefined) {
|
|
245
|
+
throw new Error(`Unsupported value type for ${name}: ${String(value)}`);
|
|
246
|
+
}
|
|
247
|
+
return [name, enc];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Key, Keyset } from "@atproto/jwk";
|
|
2
|
+
import { OAuthAuthorizationServerMetadata } from "@atproto/oauth-types";
|
|
3
|
+
import { Fetch } from "@atproto-labs/fetch";
|
|
4
|
+
import { GetCachedOptions } from "./oauth-authorization-server-metadata-resolver.js";
|
|
5
|
+
import { ClientAuthMethod } from "./oauth-client-auth.js";
|
|
6
|
+
import { OAuthResolver } from "./oauth-resolver.js";
|
|
7
|
+
import { DpopNonceCache, OAuthServerAgent } from "./oauth-server-agent.js";
|
|
8
|
+
import { Runtime } from "./runtime.js";
|
|
9
|
+
import { ClientMetadata } from "./types.js";
|
|
10
|
+
export declare class OAuthServerFactory {
|
|
11
|
+
readonly clientMetadata: ClientMetadata;
|
|
12
|
+
readonly runtime: Runtime;
|
|
13
|
+
readonly resolver: OAuthResolver;
|
|
14
|
+
readonly fetch: Fetch;
|
|
15
|
+
readonly keyset: Keyset | undefined;
|
|
16
|
+
readonly dpopNonceCache: DpopNonceCache;
|
|
17
|
+
constructor(clientMetadata: ClientMetadata, runtime: Runtime, resolver: OAuthResolver, fetch: Fetch, keyset: Keyset | undefined, dpopNonceCache: DpopNonceCache);
|
|
18
|
+
/**
|
|
19
|
+
* @param authMethod `undefined` means that we are restoring a session that
|
|
20
|
+
* was created before we started storing the `authMethod` in the session. In
|
|
21
|
+
* that case, we will use the first key from the keyset.
|
|
22
|
+
*
|
|
23
|
+
* Support for this might be removed in the future.
|
|
24
|
+
*
|
|
25
|
+
* @throws see {@link OAuthServerFactory.fromMetadata}
|
|
26
|
+
*/
|
|
27
|
+
fromIssuer(issuer: string, authMethod: "legacy" | ClientAuthMethod, dpopKey: Key, options?: GetCachedOptions): Promise<OAuthServerAgent>;
|
|
28
|
+
/**
|
|
29
|
+
* @throws see {@link OAuthServerAgent}
|
|
30
|
+
*/
|
|
31
|
+
fromMetadata(serverMetadata: OAuthAuthorizationServerMetadata, authMethod: ClientAuthMethod, dpopKey: Key): Promise<OAuthServerAgent>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { negotiateClientAuthMethod, } from "./oauth-client-auth.js";
|
|
2
|
+
import { OAuthServerAgent } from "./oauth-server-agent.js";
|
|
3
|
+
export class OAuthServerFactory {
|
|
4
|
+
constructor(clientMetadata, runtime, resolver, fetch, keyset, dpopNonceCache) {
|
|
5
|
+
this.clientMetadata = clientMetadata;
|
|
6
|
+
this.runtime = runtime;
|
|
7
|
+
this.resolver = resolver;
|
|
8
|
+
this.fetch = fetch;
|
|
9
|
+
this.keyset = keyset;
|
|
10
|
+
this.dpopNonceCache = dpopNonceCache;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* @param authMethod `undefined` means that we are restoring a session that
|
|
14
|
+
* was created before we started storing the `authMethod` in the session. In
|
|
15
|
+
* that case, we will use the first key from the keyset.
|
|
16
|
+
*
|
|
17
|
+
* Support for this might be removed in the future.
|
|
18
|
+
*
|
|
19
|
+
* @throws see {@link OAuthServerFactory.fromMetadata}
|
|
20
|
+
*/
|
|
21
|
+
async fromIssuer(issuer, authMethod, dpopKey, options) {
|
|
22
|
+
const serverMetadata = await this.resolver.getAuthorizationServerMetadata(issuer, options);
|
|
23
|
+
if (authMethod === "legacy") {
|
|
24
|
+
// @NOTE Because we were previously not storing the authMethod in the
|
|
25
|
+
// session data, we provide a backwards compatible implementation by
|
|
26
|
+
// computing it here.
|
|
27
|
+
authMethod = negotiateClientAuthMethod(serverMetadata, this.clientMetadata, this.keyset);
|
|
28
|
+
}
|
|
29
|
+
return this.fromMetadata(serverMetadata, authMethod, dpopKey);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @throws see {@link OAuthServerAgent}
|
|
33
|
+
*/
|
|
34
|
+
async fromMetadata(serverMetadata, authMethod, dpopKey) {
|
|
35
|
+
return new OAuthServerAgent(authMethod, dpopKey, serverMetadata, this.clientMetadata, this.dpopNonceCache, this.resolver, this.runtime, this.keyset, this.fetch);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AtprotoDid } from "@atproto/did";
|
|
2
|
+
import { OAuthAuthorizationServerMetadata } from "@atproto/oauth-types";
|
|
3
|
+
import { Fetch } from "@atproto-labs/fetch";
|
|
4
|
+
import { AtprotoScope } from "./atproto-token-response.js";
|
|
5
|
+
import { OAuthServerAgent, TokenSet } from "./oauth-server-agent.js";
|
|
6
|
+
import { SessionGetter } from "./session-getter.js";
|
|
7
|
+
export type TokenInfo = {
|
|
8
|
+
expiresAt?: Date;
|
|
9
|
+
expired?: boolean;
|
|
10
|
+
scope: AtprotoScope;
|
|
11
|
+
iss: string;
|
|
12
|
+
aud: string;
|
|
13
|
+
sub: AtprotoDid;
|
|
14
|
+
};
|
|
15
|
+
export declare class OAuthSession {
|
|
16
|
+
readonly server: OAuthServerAgent;
|
|
17
|
+
readonly sub: AtprotoDid;
|
|
18
|
+
private readonly sessionGetter;
|
|
19
|
+
protected dpopFetch: Fetch<unknown>;
|
|
20
|
+
constructor(server: OAuthServerAgent, sub: AtprotoDid, sessionGetter: SessionGetter, fetch?: Fetch);
|
|
21
|
+
get did(): AtprotoDid;
|
|
22
|
+
get serverMetadata(): Readonly<OAuthAuthorizationServerMetadata>;
|
|
23
|
+
/**
|
|
24
|
+
* @param refresh When `true`, the credentials will be refreshed even if they
|
|
25
|
+
* are not expired. When `false`, the credentials will not be refreshed even
|
|
26
|
+
* if they are expired. When `undefined`, the credentials will be refreshed
|
|
27
|
+
* if, and only if, they are (about to be) expired. Defaults to `undefined`.
|
|
28
|
+
*/
|
|
29
|
+
protected getTokenSet(refresh: boolean | "auto"): Promise<TokenSet>;
|
|
30
|
+
getTokenInfo(refresh?: boolean | "auto"): Promise<TokenInfo>;
|
|
31
|
+
signOut(): Promise<void>;
|
|
32
|
+
fetchHandler(pathname: string, init?: RequestInit): Promise<Response>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { bindFetch } from "@atproto-labs/fetch";
|
|
2
|
+
import { TokenInvalidError } from "./errors/token-invalid-error.js";
|
|
3
|
+
import { TokenRevokedError } from "./errors/token-revoked-error.js";
|
|
4
|
+
import { dpopFetchWrapper } from "./fetch-dpop.js";
|
|
5
|
+
const ReadableStream = globalThis.ReadableStream;
|
|
6
|
+
export class OAuthSession {
|
|
7
|
+
constructor(server, sub, sessionGetter, fetch = globalThis.fetch) {
|
|
8
|
+
this.server = server;
|
|
9
|
+
this.sub = sub;
|
|
10
|
+
this.sessionGetter = sessionGetter;
|
|
11
|
+
this.dpopFetch = dpopFetchWrapper({
|
|
12
|
+
fetch: bindFetch(fetch),
|
|
13
|
+
key: server.dpopKey,
|
|
14
|
+
supportedAlgs: server.serverMetadata.dpop_signing_alg_values_supported,
|
|
15
|
+
sha256: async (v) => server.runtime.sha256(v),
|
|
16
|
+
nonces: server.dpopNonces,
|
|
17
|
+
isAuthServer: false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
get did() {
|
|
21
|
+
return this.sub;
|
|
22
|
+
}
|
|
23
|
+
get serverMetadata() {
|
|
24
|
+
return this.server.serverMetadata;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @param refresh When `true`, the credentials will be refreshed even if they
|
|
28
|
+
* are not expired. When `false`, the credentials will not be refreshed even
|
|
29
|
+
* if they are expired. When `undefined`, the credentials will be refreshed
|
|
30
|
+
* if, and only if, they are (about to be) expired. Defaults to `undefined`.
|
|
31
|
+
*/
|
|
32
|
+
async getTokenSet(refresh) {
|
|
33
|
+
const { tokenSet } = await this.sessionGetter.get(this.sub, {
|
|
34
|
+
noCache: refresh === true,
|
|
35
|
+
allowStale: refresh === false,
|
|
36
|
+
});
|
|
37
|
+
return tokenSet;
|
|
38
|
+
}
|
|
39
|
+
async getTokenInfo(refresh = "auto") {
|
|
40
|
+
const tokenSet = await this.getTokenSet(refresh);
|
|
41
|
+
const expiresAt = tokenSet.expires_at == null ? undefined : new Date(tokenSet.expires_at);
|
|
42
|
+
return {
|
|
43
|
+
expiresAt,
|
|
44
|
+
get expired() {
|
|
45
|
+
return expiresAt == null
|
|
46
|
+
? undefined
|
|
47
|
+
: expiresAt.getTime() < Date.now() - 5e3;
|
|
48
|
+
},
|
|
49
|
+
scope: tokenSet.scope,
|
|
50
|
+
iss: tokenSet.iss,
|
|
51
|
+
aud: tokenSet.aud,
|
|
52
|
+
sub: tokenSet.sub,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async signOut() {
|
|
56
|
+
try {
|
|
57
|
+
const tokenSet = await this.getTokenSet(false);
|
|
58
|
+
await this.server.revoke(tokenSet.access_token);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await this.sessionGetter.delStored(this.sub, new TokenRevokedError(this.sub));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async fetchHandler(pathname, init) {
|
|
65
|
+
// This will try and refresh the token if it is known to be expired
|
|
66
|
+
const tokenSet = await this.getTokenSet("auto");
|
|
67
|
+
const initialUrl = new URL(pathname, tokenSet.aud);
|
|
68
|
+
const initialAuth = `${tokenSet.token_type} ${tokenSet.access_token}`;
|
|
69
|
+
const headers = new Headers(init?.headers);
|
|
70
|
+
headers.set("Authorization", initialAuth);
|
|
71
|
+
const initialResponse = await this.dpopFetch(initialUrl, {
|
|
72
|
+
...init,
|
|
73
|
+
headers,
|
|
74
|
+
});
|
|
75
|
+
// If the token is not expired, we don't need to refresh it
|
|
76
|
+
if (!isInvalidTokenResponse(initialResponse)) {
|
|
77
|
+
return initialResponse;
|
|
78
|
+
}
|
|
79
|
+
let tokenSetFresh;
|
|
80
|
+
try {
|
|
81
|
+
// Force a refresh
|
|
82
|
+
tokenSetFresh = await this.getTokenSet(true);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return initialResponse;
|
|
86
|
+
}
|
|
87
|
+
// The stream was already consumed. We cannot retry the request. A solution
|
|
88
|
+
// would be to tee() the input stream but that would bufferize the entire
|
|
89
|
+
// stream in memory which can lead to memory starvation. Instead, we will
|
|
90
|
+
// return the original response and let the calling code handle retries.
|
|
91
|
+
if (ReadableStream && init?.body instanceof ReadableStream) {
|
|
92
|
+
return initialResponse;
|
|
93
|
+
}
|
|
94
|
+
const finalAuth = `${tokenSetFresh.token_type} ${tokenSetFresh.access_token}`;
|
|
95
|
+
const finalUrl = new URL(pathname, tokenSetFresh.aud);
|
|
96
|
+
headers.set("Authorization", finalAuth);
|
|
97
|
+
const finalResponse = await this.dpopFetch(finalUrl, { ...init, headers });
|
|
98
|
+
// The token was successfully refreshed, but is still not accepted by the
|
|
99
|
+
// resource server. This might be due to the resource server not accepting
|
|
100
|
+
// credentials from the authorization server (e.g. because some migration
|
|
101
|
+
// occurred). Any ways, there is no point in keeping the session.
|
|
102
|
+
if (isInvalidTokenResponse(finalResponse)) {
|
|
103
|
+
// @TODO Is there a "softer" way to handle this, e.g. by marking the
|
|
104
|
+
// session as "expired" in the session store, allowing the user to trigger
|
|
105
|
+
// a new login (using login_hint)?
|
|
106
|
+
await this.sessionGetter.delStored(this.sub, new TokenInvalidError(this.sub));
|
|
107
|
+
}
|
|
108
|
+
return finalResponse;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3}
|
|
113
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc9449#name-resource-server-provided-no}
|
|
114
|
+
*/
|
|
115
|
+
function isInvalidTokenResponse(response) {
|
|
116
|
+
if (response.status !== 401)
|
|
117
|
+
return false;
|
|
118
|
+
const wwwAuth = response.headers.get("WWW-Authenticate");
|
|
119
|
+
return (wwwAuth != null &&
|
|
120
|
+
(wwwAuth.startsWith("Bearer ") || wwwAuth.startsWith("DPoP ")) &&
|
|
121
|
+
wwwAuth.includes('error="invalid_token"'));
|
|
122
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Key } from "@atproto/jwk";
|
|
2
|
+
import { Awaitable } from "./util.js";
|
|
3
|
+
export type { Key };
|
|
4
|
+
export type RuntimeKeyFactory = (algs: string[]) => Key | PromiseLike<Key>;
|
|
5
|
+
export type RuntimeRandomValues = (length: number) => Awaitable<Uint8Array>;
|
|
6
|
+
export type DigestAlgorithm = {
|
|
7
|
+
name: "sha256" | "sha384" | "sha512";
|
|
8
|
+
};
|
|
9
|
+
export type RuntimeDigest = (data: Uint8Array, alg: DigestAlgorithm) => Awaitable<Uint8Array>;
|
|
10
|
+
export type RuntimeLock = <T>(name: string, fn: () => Awaitable<T>) => Awaitable<T>;
|
|
11
|
+
export interface RuntimeImplementation {
|
|
12
|
+
createKey: RuntimeKeyFactory;
|
|
13
|
+
getRandomValues: RuntimeRandomValues;
|
|
14
|
+
digest: RuntimeDigest;
|
|
15
|
+
requestLock?: RuntimeLock;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Key } from "@atproto/jwk";
|
|
2
|
+
import { RuntimeImplementation, RuntimeLock } from "./runtime-implementation.js";
|
|
3
|
+
export declare class Runtime {
|
|
4
|
+
protected implementation: RuntimeImplementation;
|
|
5
|
+
readonly hasImplementationLock: boolean;
|
|
6
|
+
readonly usingLock: RuntimeLock;
|
|
7
|
+
constructor(implementation: RuntimeImplementation);
|
|
8
|
+
generateKey(algs: string[]): Promise<Key>;
|
|
9
|
+
sha256(text: string): Promise<string>;
|
|
10
|
+
generateNonce(length?: number): Promise<string>;
|
|
11
|
+
generatePKCE(byteLength?: number): Promise<{
|
|
12
|
+
verifier: string;
|
|
13
|
+
challenge: string;
|
|
14
|
+
method: "S256";
|
|
15
|
+
}>;
|
|
16
|
+
calculateJwkThumbprint(jwk: any): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7636#section-4.1}
|
|
19
|
+
* @note It is RECOMMENDED that the output of a suitable random number generator
|
|
20
|
+
* be used to create a 32-octet sequence. The octet sequence is then
|
|
21
|
+
* base64url-encoded to produce a 43-octet URL safe string to use as the code
|
|
22
|
+
* verifier.
|
|
23
|
+
*/
|
|
24
|
+
protected generateVerifier(byteLength?: number): Promise<string>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { base64url } from "multiformats/bases/base64";
|
|
2
|
+
import { requestLocalLock } from "./lock.js";
|
|
3
|
+
export class Runtime {
|
|
4
|
+
constructor(implementation) {
|
|
5
|
+
this.implementation = implementation;
|
|
6
|
+
const { requestLock } = implementation;
|
|
7
|
+
this.hasImplementationLock = requestLock != null;
|
|
8
|
+
this.usingLock =
|
|
9
|
+
requestLock?.bind(implementation) ||
|
|
10
|
+
// Falling back to a local lock
|
|
11
|
+
requestLocalLock;
|
|
12
|
+
}
|
|
13
|
+
async generateKey(algs) {
|
|
14
|
+
const algsSorted = Array.from(algs).sort(compareAlgos);
|
|
15
|
+
return this.implementation.createKey(algsSorted);
|
|
16
|
+
}
|
|
17
|
+
async sha256(text) {
|
|
18
|
+
const bytes = new TextEncoder().encode(text);
|
|
19
|
+
const digest = await this.implementation.digest(bytes, { name: "sha256" });
|
|
20
|
+
return base64url.baseEncode(digest);
|
|
21
|
+
}
|
|
22
|
+
async generateNonce(length = 16) {
|
|
23
|
+
const bytes = await this.implementation.getRandomValues(length);
|
|
24
|
+
return base64url.baseEncode(bytes);
|
|
25
|
+
}
|
|
26
|
+
async generatePKCE(byteLength) {
|
|
27
|
+
const verifier = await this.generateVerifier(byteLength);
|
|
28
|
+
return {
|
|
29
|
+
verifier,
|
|
30
|
+
challenge: await this.sha256(verifier),
|
|
31
|
+
method: "S256",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async calculateJwkThumbprint(jwk) {
|
|
35
|
+
const components = extractJktComponents(jwk);
|
|
36
|
+
const data = JSON.stringify(components);
|
|
37
|
+
return this.sha256(data);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7636#section-4.1}
|
|
41
|
+
* @note It is RECOMMENDED that the output of a suitable random number generator
|
|
42
|
+
* be used to create a 32-octet sequence. The octet sequence is then
|
|
43
|
+
* base64url-encoded to produce a 43-octet URL safe string to use as the code
|
|
44
|
+
* verifier.
|
|
45
|
+
*/
|
|
46
|
+
async generateVerifier(byteLength = 32) {
|
|
47
|
+
if (byteLength < 32 || byteLength > 96) {
|
|
48
|
+
throw new TypeError("Invalid code_verifier length");
|
|
49
|
+
}
|
|
50
|
+
const bytes = await this.implementation.getRandomValues(byteLength);
|
|
51
|
+
return base64url.baseEncode(bytes);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function extractJktComponents(jwk) {
|
|
55
|
+
const get = (field) => {
|
|
56
|
+
const value = jwk[field];
|
|
57
|
+
if (typeof value !== "string" || !value) {
|
|
58
|
+
throw new TypeError(`"${field}" Parameter missing or invalid`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
};
|
|
62
|
+
switch (jwk.kty) {
|
|
63
|
+
case "EC":
|
|
64
|
+
return { crv: get("crv"), kty: get("kty"), x: get("x"), y: get("y") };
|
|
65
|
+
case "OKP":
|
|
66
|
+
return { crv: get("crv"), kty: get("kty"), x: get("x") };
|
|
67
|
+
case "RSA":
|
|
68
|
+
return { e: get("e"), kty: get("kty"), n: get("n") };
|
|
69
|
+
case "oct":
|
|
70
|
+
return { k: get("k"), kty: get("kty") };
|
|
71
|
+
default:
|
|
72
|
+
throw new TypeError('"kty" (Key Type) Parameter missing or unsupported');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 256K > ES (256 > 384 > 512) > PS (256 > 384 > 512) > RS (256 > 384 > 512) > other (in original order)
|
|
77
|
+
*/
|
|
78
|
+
function compareAlgos(a, b) {
|
|
79
|
+
if (a === "ES256K")
|
|
80
|
+
return -1;
|
|
81
|
+
if (b === "ES256K")
|
|
82
|
+
return 1;
|
|
83
|
+
for (const prefix of ["ES", "PS", "RS"]) {
|
|
84
|
+
if (a.startsWith(prefix)) {
|
|
85
|
+
if (b.startsWith(prefix)) {
|
|
86
|
+
const aLen = parseInt(a.slice(2, 5));
|
|
87
|
+
const bLen = parseInt(b.slice(2, 5));
|
|
88
|
+
// Prefer shorter key lengths
|
|
89
|
+
return aLen - bLen;
|
|
90
|
+
}
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
else if (b.startsWith(prefix)) {
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Don't know how to compare, keep original order
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AtprotoDid } from "@atproto/did";
|
|
2
|
+
import { Key } from "@atproto/jwk";
|
|
3
|
+
import { CachedGetter, GetCachedOptions, SimpleStore } from "@atproto-labs/simple-store";
|
|
4
|
+
import { TokenInvalidError } from "./errors/token-invalid-error.js";
|
|
5
|
+
import { TokenRefreshError } from "./errors/token-refresh-error.js";
|
|
6
|
+
import { TokenRevokedError } from "./errors/token-revoked-error.js";
|
|
7
|
+
import { ClientAuthMethod } from "./oauth-client-auth.js";
|
|
8
|
+
import { TokenSet } from "./oauth-server-agent.js";
|
|
9
|
+
import { OAuthServerFactory } from "./oauth-server-factory.js";
|
|
10
|
+
import { Runtime } from "./runtime.js";
|
|
11
|
+
export type Session = {
|
|
12
|
+
dpopKey: Key;
|
|
13
|
+
/**
|
|
14
|
+
* Previous implementation of this lib did not define an `authMethod`
|
|
15
|
+
*/
|
|
16
|
+
authMethod?: ClientAuthMethod;
|
|
17
|
+
tokenSet: TokenSet;
|
|
18
|
+
};
|
|
19
|
+
export type SessionStore = SimpleStore<string, Session>;
|
|
20
|
+
export type SessionEventMap = {
|
|
21
|
+
updated: {
|
|
22
|
+
sub: string;
|
|
23
|
+
} & Session;
|
|
24
|
+
deleted: {
|
|
25
|
+
sub: string;
|
|
26
|
+
cause: TokenRefreshError | TokenRevokedError | TokenInvalidError | unknown;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export type SessionEventListener<T extends keyof SessionEventMap = keyof SessionEventMap> = (event: CustomEvent<SessionEventMap[T]>) => void;
|
|
30
|
+
/**
|
|
31
|
+
* There are several advantages to wrapping the sessionStore in a (single)
|
|
32
|
+
* CachedGetter, the main of which is that the cached getter will ensure that at
|
|
33
|
+
* most one fresh call is ever being made. Another advantage, is that it
|
|
34
|
+
* contains the logic for reading from the cache which, if the cache is based on
|
|
35
|
+
* localStorage/indexedDB, will sync across multiple tabs (for a given sub).
|
|
36
|
+
*/
|
|
37
|
+
export declare class SessionGetter extends CachedGetter<AtprotoDid, Session> {
|
|
38
|
+
private readonly runtime;
|
|
39
|
+
private readonly eventTarget;
|
|
40
|
+
constructor(sessionStore: SessionStore, serverFactory: OAuthServerFactory, runtime: Runtime);
|
|
41
|
+
addEventListener<T extends keyof SessionEventMap>(type: T, callback: SessionEventListener<T>, options?: AddEventListenerOptions | boolean): void;
|
|
42
|
+
removeEventListener<T extends keyof SessionEventMap>(type: T, callback: SessionEventListener<T>, options?: EventListenerOptions | boolean): void;
|
|
43
|
+
dispatchEvent<T extends keyof SessionEventMap>(type: T, detail: SessionEventMap[T]): boolean;
|
|
44
|
+
setStored(sub: string, session: Session): Promise<void>;
|
|
45
|
+
delStored(sub: AtprotoDid, cause?: unknown): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* @param refresh When `true`, the credentials will be refreshed even if they
|
|
48
|
+
* are not expired. When `false`, the credentials will not be refreshed even
|
|
49
|
+
* if they are expired. When `undefined`, the credentials will be refreshed
|
|
50
|
+
* if, and only if, they are (about to be) expired. Defaults to `undefined`.
|
|
51
|
+
*/
|
|
52
|
+
getSession(sub: AtprotoDid, refresh?: boolean): Promise<Session>;
|
|
53
|
+
get(sub: AtprotoDid, options?: GetCachedOptions): Promise<Session>;
|
|
54
|
+
}
|