@atproto/oauth-client 0.0.0-spaces-alpha-20260818021221
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/CHANGELOG.md +810 -0
- package/LICENSE.txt +7 -0
- package/README.md +387 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors/auth-method-unsatisfiable-error.d.ts +3 -0
- package/dist/errors/auth-method-unsatisfiable-error.d.ts.map +1 -0
- package/dist/errors/auth-method-unsatisfiable-error.js +3 -0
- package/dist/errors/auth-method-unsatisfiable-error.js.map +1 -0
- package/dist/errors/token-invalid-error.d.ts +7 -0
- package/dist/errors/token-invalid-error.d.ts.map +1 -0
- package/dist/errors/token-invalid-error.js +7 -0
- package/dist/errors/token-invalid-error.js.map +1 -0
- package/dist/errors/token-refresh-error.d.ts +7 -0
- package/dist/errors/token-refresh-error.d.ts.map +1 -0
- package/dist/errors/token-refresh-error.js +7 -0
- package/dist/errors/token-refresh-error.js.map +1 -0
- package/dist/errors/token-revoked-error.d.ts +7 -0
- package/dist/errors/token-revoked-error.d.ts.map +1 -0
- package/dist/errors/token-revoked-error.js +7 -0
- package/dist/errors/token-revoked-error.js.map +1 -0
- package/dist/fetch-dpop.d.ts +20 -0
- package/dist/fetch-dpop.d.ts.map +1 -0
- package/dist/fetch-dpop.js +176 -0
- package/dist/fetch-dpop.js.map +1 -0
- package/dist/identity-resolver.d.ts +2 -0
- package/dist/identity-resolver.d.ts.map +1 -0
- package/dist/identity-resolver.js +21 -0
- package/dist/identity-resolver.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +3 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +29 -0
- package/dist/lock.js.map +1 -0
- package/dist/oauth-authorization-server-metadata-resolver.d.ts +25 -0
- package/dist/oauth-authorization-server-metadata-resolver.d.ts.map +1 -0
- package/dist/oauth-authorization-server-metadata-resolver.js +54 -0
- package/dist/oauth-authorization-server-metadata-resolver.js.map +1 -0
- package/dist/oauth-callback-error.d.ts +7 -0
- package/dist/oauth-callback-error.d.ts.map +1 -0
- package/dist/oauth-callback-error.js +14 -0
- package/dist/oauth-callback-error.js.map +1 -0
- package/dist/oauth-client-auth.d.ts +23 -0
- package/dist/oauth-client-auth.d.ts.map +1 -0
- package/dist/oauth-client-auth.js +126 -0
- package/dist/oauth-client-auth.js.map +1 -0
- package/dist/oauth-client.d.ts +338 -0
- package/dist/oauth-client.d.ts.map +1 -0
- package/dist/oauth-client.js +274 -0
- package/dist/oauth-client.js.map +1 -0
- package/dist/oauth-protected-resource-metadata-resolver.d.ts +25 -0
- package/dist/oauth-protected-resource-metadata-resolver.d.ts.map +1 -0
- package/dist/oauth-protected-resource-metadata-resolver.js +54 -0
- package/dist/oauth-protected-resource-metadata-resolver.js.map +1 -0
- package/dist/oauth-resolver-error.d.ts +7 -0
- package/dist/oauth-resolver-error.d.ts.map +1 -0
- package/dist/oauth-resolver-error.js +19 -0
- package/dist/oauth-resolver-error.js.map +1 -0
- package/dist/oauth-resolver.d.ts +73 -0
- package/dist/oauth-resolver.d.ts.map +1 -0
- package/dist/oauth-resolver.js +99 -0
- package/dist/oauth-resolver.js.map +1 -0
- package/dist/oauth-response-error.d.ts +11 -0
- package/dist/oauth-response-error.d.ts.map +1 -0
- package/dist/oauth-response-error.js +22 -0
- package/dist/oauth-response-error.js.map +1 -0
- package/dist/oauth-server-agent.d.ts +55 -0
- package/dist/oauth-server-agent.d.ts.map +1 -0
- package/dist/oauth-server-agent.js +189 -0
- package/dist/oauth-server-agent.js.map +1 -0
- package/dist/oauth-server-factory.d.ts +33 -0
- package/dist/oauth-server-factory.d.ts.map +1 -0
- package/dist/oauth-server-factory.js +31 -0
- package/dist/oauth-server-factory.js.map +1 -0
- package/dist/oauth-session.d.ts +34 -0
- package/dist/oauth-session.d.ts.map +1 -0
- package/dist/oauth-session.js +120 -0
- package/dist/oauth-session.js.map +1 -0
- package/dist/runtime-implementation.d.ts +17 -0
- package/dist/runtime-implementation.d.ts.map +1 -0
- package/dist/runtime-implementation.js +2 -0
- package/dist/runtime-implementation.js.map +1 -0
- package/dist/runtime.d.ts +26 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +100 -0
- package/dist/runtime.js.map +1 -0
- package/dist/session-getter.d.ts +44 -0
- package/dist/session-getter.d.ts.map +1 -0
- package/dist/session-getter.js +265 -0
- package/dist/session-getter.js.map +1 -0
- package/dist/state-store.d.ts +23 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +2 -0
- package/dist/state-store.js.map +1 -0
- package/dist/types.d.ts +430 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +20 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +68 -0
- package/dist/util.js.map +1 -0
- package/dist/validate-client-metadata.d.ts +5 -0
- package/dist/validate-client-metadata.d.ts.map +1 -0
- package/dist/validate-client-metadata.js +76 -0
- package/dist/validate-client-metadata.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Key, Keyset } from '@atproto/jwk';
|
|
2
|
+
import { oauthClientMetadataSchema, } from '@atproto/oauth-types';
|
|
3
|
+
import { assertAtprotoDid, } from '@atproto-labs/did-resolver';
|
|
4
|
+
import { HANDLE_INVALID } from '@atproto-labs/identity-resolver';
|
|
5
|
+
import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory';
|
|
6
|
+
import { FALLBACK_ALG } from './constants.js';
|
|
7
|
+
import { AuthMethodUnsatisfiableError } from './errors/auth-method-unsatisfiable-error.js';
|
|
8
|
+
import { TokenRevokedError } from './errors/token-revoked-error.js';
|
|
9
|
+
import { createIdentityResolver, } from './identity-resolver.js';
|
|
10
|
+
import { OAuthAuthorizationServerMetadataResolver, } from './oauth-authorization-server-metadata-resolver.js';
|
|
11
|
+
import { OAuthCallbackError } from './oauth-callback-error.js';
|
|
12
|
+
import { negotiateClientAuthMethod } from './oauth-client-auth.js';
|
|
13
|
+
import { OAuthProtectedResourceMetadataResolver, } from './oauth-protected-resource-metadata-resolver.js';
|
|
14
|
+
import { OAuthResolver } from './oauth-resolver.js';
|
|
15
|
+
import { OAuthServerFactory } from './oauth-server-factory.js';
|
|
16
|
+
import { OAuthSession } from './oauth-session.js';
|
|
17
|
+
import { Runtime } from './runtime.js';
|
|
18
|
+
import { SessionGetter, isExpectedSessionError, } from './session-getter.js';
|
|
19
|
+
import { validateClientMetadata } from './validate-client-metadata.js';
|
|
20
|
+
export { Key, Keyset };
|
|
21
|
+
export class OAuthClient {
|
|
22
|
+
static async fetchMetadata({ clientId, fetch = globalThis.fetch, signal, }) {
|
|
23
|
+
signal?.throwIfAborted();
|
|
24
|
+
const request = new Request(clientId, {
|
|
25
|
+
redirect: 'error',
|
|
26
|
+
signal: signal,
|
|
27
|
+
});
|
|
28
|
+
const response = await fetch(request);
|
|
29
|
+
if (response.status !== 200) {
|
|
30
|
+
response.body?.cancel?.();
|
|
31
|
+
throw new TypeError(`Failed to fetch client metadata: ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
// https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-4.1
|
|
34
|
+
const mime = response.headers.get('content-type')?.split(';')[0].trim();
|
|
35
|
+
if (mime !== 'application/json') {
|
|
36
|
+
response.body?.cancel?.();
|
|
37
|
+
throw new TypeError(`Invalid client metadata content type: ${mime}`);
|
|
38
|
+
}
|
|
39
|
+
const json = await response.json();
|
|
40
|
+
signal?.throwIfAborted();
|
|
41
|
+
return oauthClientMetadataSchema.parse(json);
|
|
42
|
+
}
|
|
43
|
+
constructor(options) {
|
|
44
|
+
const { stateStore, sessionStore, dpopNonceCache = new SimpleStoreMemory({ ttl: 60e3, max: 100 }), authorizationServerMetadataCache = new SimpleStoreMemory({
|
|
45
|
+
ttl: 60e3,
|
|
46
|
+
max: 100,
|
|
47
|
+
}), protectedResourceMetadataCache = new SimpleStoreMemory({
|
|
48
|
+
ttl: 60e3,
|
|
49
|
+
max: 100,
|
|
50
|
+
}), responseMode, clientMetadata, runtimeImplementation, keyset, } = options;
|
|
51
|
+
this.keyset = keyset
|
|
52
|
+
? keyset instanceof Keyset
|
|
53
|
+
? keyset
|
|
54
|
+
: new Keyset(keyset)
|
|
55
|
+
: undefined;
|
|
56
|
+
this.clientMetadata = validateClientMetadata(clientMetadata, this.keyset);
|
|
57
|
+
this.responseMode = responseMode;
|
|
58
|
+
this.runtime = new Runtime(runtimeImplementation);
|
|
59
|
+
this.fetch = options.fetch ?? globalThis.fetch;
|
|
60
|
+
this.oauthResolver = new OAuthResolver(createIdentityResolver(options), new OAuthProtectedResourceMetadataResolver(protectedResourceMetadataCache, this.fetch, { allowHttpResource: options.allowHttp }), new OAuthAuthorizationServerMetadataResolver(authorizationServerMetadataCache, this.fetch, { allowHttpIssuer: options.allowHttp }));
|
|
61
|
+
this.serverFactory = new OAuthServerFactory(this.clientMetadata, this.runtime, this.oauthResolver, this.fetch, this.keyset, dpopNonceCache);
|
|
62
|
+
this.stateStore = stateStore;
|
|
63
|
+
this.sessionGetter = new SessionGetter(sessionStore, this.serverFactory, this.runtime, options);
|
|
64
|
+
}
|
|
65
|
+
// Exposed as public API for convenience
|
|
66
|
+
get identityResolver() {
|
|
67
|
+
return this.oauthResolver.identityResolver;
|
|
68
|
+
}
|
|
69
|
+
get jwks() {
|
|
70
|
+
return this.keyset?.publicJwks ?? { keys: [] };
|
|
71
|
+
}
|
|
72
|
+
async authorize(input, { signal, ...options } = {}) {
|
|
73
|
+
const redirectUri = options?.redirect_uri ?? this.clientMetadata.redirect_uris[0];
|
|
74
|
+
if (!this.clientMetadata.redirect_uris.includes(redirectUri)) {
|
|
75
|
+
// The server will enforce this, but let's catch it early
|
|
76
|
+
throw new TypeError('Invalid redirect_uri');
|
|
77
|
+
}
|
|
78
|
+
const { identityInfo, metadata } = await this.oauthResolver.resolve(input, {
|
|
79
|
+
signal,
|
|
80
|
+
});
|
|
81
|
+
const pkce = await this.runtime.generatePKCE();
|
|
82
|
+
const dpopKey = await this.runtime.generateKey(metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG]);
|
|
83
|
+
const authMethod = negotiateClientAuthMethod(metadata, this.clientMetadata, this.keyset);
|
|
84
|
+
const state = await this.runtime.generateNonce();
|
|
85
|
+
await this.stateStore.set(state, {
|
|
86
|
+
iss: metadata.issuer,
|
|
87
|
+
dpopKey,
|
|
88
|
+
authMethod,
|
|
89
|
+
verifier: pkce.verifier,
|
|
90
|
+
appState: options?.state,
|
|
91
|
+
});
|
|
92
|
+
const parameters = {
|
|
93
|
+
...options,
|
|
94
|
+
client_id: this.clientMetadata.client_id,
|
|
95
|
+
redirect_uri: redirectUri,
|
|
96
|
+
code_challenge: pkce.challenge,
|
|
97
|
+
code_challenge_method: pkce.method,
|
|
98
|
+
state,
|
|
99
|
+
login_hint: identityInfo
|
|
100
|
+
? identityInfo.handle !== HANDLE_INVALID
|
|
101
|
+
? identityInfo.handle
|
|
102
|
+
: identityInfo.did
|
|
103
|
+
: undefined,
|
|
104
|
+
response_mode: this.responseMode,
|
|
105
|
+
response_type: 'code',
|
|
106
|
+
scope: options?.scope ?? this.clientMetadata.scope,
|
|
107
|
+
};
|
|
108
|
+
const authorizationUrl = new URL(metadata.authorization_endpoint);
|
|
109
|
+
// Since the user will be redirected to the authorization_endpoint url using
|
|
110
|
+
// a browser, we need to make sure that the url is valid.
|
|
111
|
+
if (authorizationUrl.protocol !== 'https:' &&
|
|
112
|
+
authorizationUrl.protocol !== 'http:') {
|
|
113
|
+
throw new TypeError(`Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`);
|
|
114
|
+
}
|
|
115
|
+
if (metadata.pushed_authorization_request_endpoint) {
|
|
116
|
+
const server = await this.serverFactory.fromMetadata(metadata, authMethod, dpopKey);
|
|
117
|
+
const parResponse = await server.request('pushed_authorization_request', parameters);
|
|
118
|
+
authorizationUrl.searchParams.set('client_id', this.clientMetadata.client_id);
|
|
119
|
+
authorizationUrl.searchParams.set('request_uri', parResponse.request_uri);
|
|
120
|
+
return authorizationUrl;
|
|
121
|
+
}
|
|
122
|
+
else if (metadata.require_pushed_authorization_requests) {
|
|
123
|
+
throw new Error('Server requires pushed authorization requests (PAR) but no PAR endpoint is available');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
127
|
+
if (value)
|
|
128
|
+
authorizationUrl.searchParams.set(key, String(value));
|
|
129
|
+
}
|
|
130
|
+
// Length of the URL that will be sent to the server
|
|
131
|
+
const urlLength = authorizationUrl.pathname.length + authorizationUrl.search.length;
|
|
132
|
+
if (urlLength < 2048) {
|
|
133
|
+
return authorizationUrl;
|
|
134
|
+
}
|
|
135
|
+
else if (!metadata.pushed_authorization_request_endpoint) {
|
|
136
|
+
throw new Error('Login URL too long');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw new Error('Server does not support pushed authorization requests (PAR)');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* This method allows the client to proactively revoke the request_uri it
|
|
143
|
+
* created through PAR.
|
|
144
|
+
*/
|
|
145
|
+
async abortRequest(authorizeUrl) {
|
|
146
|
+
const requestUri = authorizeUrl.searchParams.get('request_uri');
|
|
147
|
+
if (!requestUri)
|
|
148
|
+
return;
|
|
149
|
+
// @NOTE This is not implemented here because, 1) the request server should
|
|
150
|
+
// invalidate the request_uri after some delay anyways, and 2) I am not sure
|
|
151
|
+
// that the revocation endpoint is even supposed to support this (and I
|
|
152
|
+
// don't want to spend the time checking now).
|
|
153
|
+
// @TODO investigate actual necessity & feasibility of this feature
|
|
154
|
+
}
|
|
155
|
+
async callback(params, options = {}) {
|
|
156
|
+
const responseJwt = params.get('response');
|
|
157
|
+
if (responseJwt != null) {
|
|
158
|
+
// https://openid.net/specs/oauth-v2-jarm.html
|
|
159
|
+
throw new OAuthCallbackError(params, 'JARM not supported');
|
|
160
|
+
}
|
|
161
|
+
const issuerParam = params.get('iss');
|
|
162
|
+
const stateParam = params.get('state');
|
|
163
|
+
const errorParam = params.get('error');
|
|
164
|
+
const codeParam = params.get('code');
|
|
165
|
+
if (!stateParam) {
|
|
166
|
+
throw new OAuthCallbackError(params, 'Missing "state" parameter');
|
|
167
|
+
}
|
|
168
|
+
const stateData = await this.stateStore.get(stateParam);
|
|
169
|
+
if (stateData) {
|
|
170
|
+
// Prevent any kind of replay
|
|
171
|
+
await this.stateStore.del(stateParam);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
throw new OAuthCallbackError(params, `Unknown authorization session "${stateParam}"`);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
if (errorParam != null) {
|
|
178
|
+
throw new OAuthCallbackError(params, undefined, stateData.appState);
|
|
179
|
+
}
|
|
180
|
+
if (!codeParam) {
|
|
181
|
+
throw new OAuthCallbackError(params, 'Missing "code" query param', stateData.appState);
|
|
182
|
+
}
|
|
183
|
+
const server = await this.serverFactory.fromIssuer(stateData.iss, stateData.authMethod, stateData.dpopKey);
|
|
184
|
+
if (issuerParam != null) {
|
|
185
|
+
if (!server.issuer) {
|
|
186
|
+
throw new OAuthCallbackError(params, 'Issuer not found in metadata', stateData.appState);
|
|
187
|
+
}
|
|
188
|
+
if (server.issuer !== issuerParam) {
|
|
189
|
+
throw new OAuthCallbackError(params, 'Issuer mismatch', stateData.appState);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (server.serverMetadata.authorization_response_iss_parameter_supported) {
|
|
193
|
+
throw new OAuthCallbackError(params, 'iss missing from the response', stateData.appState);
|
|
194
|
+
}
|
|
195
|
+
const tokenSet = await server.exchangeCode(codeParam, stateData.verifier, options?.redirect_uri ?? server.clientMetadata.redirect_uris[0]);
|
|
196
|
+
// We revoke any existing session first to avoid leaving orphaned sessions
|
|
197
|
+
// on the AS.
|
|
198
|
+
try {
|
|
199
|
+
await this.revoke(tokenSet.sub);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// No existing session, or failed to get it. This is fine.
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
await this.sessionGetter.setStored(tokenSet.sub, {
|
|
206
|
+
dpopKey: stateData.dpopKey,
|
|
207
|
+
authMethod: server.authMethod,
|
|
208
|
+
tokenSet,
|
|
209
|
+
});
|
|
210
|
+
const session = this.createSession(server, tokenSet.sub);
|
|
211
|
+
return { session, state: stateData.appState ?? null };
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
await server.revoke(tokenSet.refresh_token || tokenSet.access_token);
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
// Make sure, whatever the underlying error, that the appState is
|
|
220
|
+
// available in the calling code
|
|
221
|
+
throw OAuthCallbackError.from(err, params, stateData.appState);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Load a stored session. This will refresh the token only if needed (about to
|
|
226
|
+
* expire) by default.
|
|
227
|
+
*
|
|
228
|
+
* @see {@link SessionGetter.restore}
|
|
229
|
+
*/
|
|
230
|
+
async restore(sub, refresh = 'auto') {
|
|
231
|
+
// sub arg is lightly typed for convenience of library user
|
|
232
|
+
assertAtprotoDid(sub);
|
|
233
|
+
const { dpopKey, authMethod, tokenSet } = await this.sessionGetter.getSession(sub, refresh);
|
|
234
|
+
try {
|
|
235
|
+
const server = await this.serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey, {
|
|
236
|
+
noCache: refresh === true,
|
|
237
|
+
allowStale: refresh === false,
|
|
238
|
+
});
|
|
239
|
+
return this.createSession(server, sub);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
if (err instanceof AuthMethodUnsatisfiableError) {
|
|
243
|
+
await this.sessionGetter.delStored(sub, err);
|
|
244
|
+
}
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async revoke(sub) {
|
|
249
|
+
// sub arg is lightly typed for convenience of library user
|
|
250
|
+
assertAtprotoDid(sub);
|
|
251
|
+
const res = await this.sessionGetter.getSession(sub, false).catch((err) => {
|
|
252
|
+
if (isExpectedSessionError(err))
|
|
253
|
+
return null;
|
|
254
|
+
throw err;
|
|
255
|
+
});
|
|
256
|
+
if (!res)
|
|
257
|
+
return;
|
|
258
|
+
const { dpopKey, authMethod, tokenSet } = res;
|
|
259
|
+
// NOT using `;(await this.restore(sub, false)).signOut()` because we want
|
|
260
|
+
// the tokens to be deleted even if it was not possible to fetch the issuer
|
|
261
|
+
// data.
|
|
262
|
+
try {
|
|
263
|
+
const server = await this.serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey);
|
|
264
|
+
await server.revoke(tokenSet.access_token);
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
await this.sessionGetter.delStored(sub, new TokenRevokedError(sub));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
createSession(server, sub) {
|
|
271
|
+
return new OAuthSession(server, sub, this.sessionGetter, this.fetch);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=oauth-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../src/oauth-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { Key, Keyset } from '@atproto/jwk'\nimport {\n type OAuthAuthorizationRequestParameters,\n type OAuthClientIdDiscoverable,\n type OAuthClientMetadata,\n type OAuthClientMetadataInput,\n type OAuthResponseMode,\n oauthClientMetadataSchema,\n} from '@atproto/oauth-types'\nimport {\n type AtprotoDid,\n type DidCache,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n type DidResolverCommonOptions,\n assertAtprotoDid,\n} from '@atproto-labs/did-resolver'\nimport type { Fetch } from '@atproto-labs/fetch'\nimport type { HandleCache, HandleResolver } from '@atproto-labs/handle-resolver'\nimport { HANDLE_INVALID } from '@atproto-labs/identity-resolver'\nimport { SimpleStoreMemory } from '@atproto-labs/simple-store-memory'\nimport { FALLBACK_ALG } from './constants.js'\nimport { AuthMethodUnsatisfiableError } from './errors/auth-method-unsatisfiable-error.js'\nimport { TokenRevokedError } from './errors/token-revoked-error.js'\nimport {\n type CreateIdentityResolverOptions,\n createIdentityResolver,\n} from './identity-resolver.js'\nimport {\n type AuthorizationServerMetadataCache,\n OAuthAuthorizationServerMetadataResolver,\n} from './oauth-authorization-server-metadata-resolver.js'\nimport { OAuthCallbackError } from './oauth-callback-error.js'\nimport { negotiateClientAuthMethod } from './oauth-client-auth.js'\nimport {\n OAuthProtectedResourceMetadataResolver,\n type ProtectedResourceMetadataCache,\n} from './oauth-protected-resource-metadata-resolver.js'\nimport { OAuthResolver } from './oauth-resolver.js'\nimport type { DpopNonceCache, OAuthServerAgent } from './oauth-server-agent.js'\nimport { OAuthServerFactory } from './oauth-server-factory.js'\nimport { OAuthSession } from './oauth-session.js'\nimport type { RuntimeImplementation } from './runtime-implementation.js'\nimport { Runtime } from './runtime.js'\nimport {\n SessionGetter,\n type SessionGetterOptions,\n type SessionStore,\n isExpectedSessionError,\n} from './session-getter.js'\nimport type { InternalStateData, StateStore } from './state-store.js'\nimport type {\n AuthorizeOptions,\n CallbackOptions,\n ClientMetadata,\n} from './types.js'\nimport { validateClientMetadata } from './validate-client-metadata.js'\n\n// Export all types needed to construct OAuthClientOptions\nexport type {\n AuthorizationServerMetadataCache,\n CreateIdentityResolverOptions,\n DidCache,\n DpopNonceCache,\n Fetch,\n HandleCache,\n HandleResolver,\n InternalStateData,\n OAuthClientMetadata,\n OAuthClientMetadataInput,\n OAuthResponseMode,\n ProtectedResourceMetadataCache,\n RuntimeImplementation,\n SessionGetterOptions,\n SessionStore,\n StateStore,\n}\n\nexport { Key, Keyset }\n\nexport type OAuthClientOptions = {\n // Config\n responseMode: OAuthResponseMode\n clientMetadata: Readonly<OAuthClientMetadataInput>\n keyset?: Keyset | Iterable<Key | undefined | null | false>\n /**\n * Determines if the client will allow communicating with the OAuth Servers\n * (Authorization & Resource), or to retrieve \"did:web\" documents, over\n * unsafe HTTP connections. It is recommended to set this to `true` only for\n * development purposes.\n *\n * @note This does not affect the identity resolution mechanism, which will\n * allow HTTP connections to the PLC Directory (if the provided directory url\n * is \"http:\" based).\n * @default false\n * @see {@link OAuthProtectedResourceMetadataResolver.allowHttpResource}\n * @see {@link OAuthAuthorizationServerMetadataResolver.allowHttpIssuer}\n * @see {@link DidResolverCommonOptions.allowHttp}\n */\n allowHttp?: boolean\n\n // Stores\n stateStore: StateStore\n sessionStore: SessionStore\n authorizationServerMetadataCache?: AuthorizationServerMetadataCache\n protectedResourceMetadataCache?: ProtectedResourceMetadataCache\n dpopNonceCache?: DpopNonceCache\n\n // Services\n runtimeImplementation: RuntimeImplementation\n fetch?: Fetch\n} & CreateIdentityResolverOptions &\n SessionGetterOptions\n\nexport type OAuthClientFetchMetadataOptions = {\n clientId: OAuthClientIdDiscoverable\n fetch?: Fetch\n signal?: AbortSignal\n}\n\nexport class OAuthClient {\n static async fetchMetadata({\n clientId,\n fetch = globalThis.fetch,\n signal,\n }: OAuthClientFetchMetadataOptions) {\n signal?.throwIfAborted()\n\n const request = new Request(clientId, {\n redirect: 'error',\n signal: signal,\n })\n const response = await fetch(request)\n\n if (response.status !== 200) {\n response.body?.cancel?.()\n throw new TypeError(`Failed to fetch client metadata: ${response.status}`)\n }\n\n // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-4.1\n const mime = response.headers.get('content-type')?.split(';')[0].trim()\n if (mime !== 'application/json') {\n response.body?.cancel?.()\n throw new TypeError(`Invalid client metadata content type: ${mime}`)\n }\n\n const json: unknown = await response.json()\n\n signal?.throwIfAborted()\n\n return oauthClientMetadataSchema.parse(json)\n }\n\n // Config\n readonly clientMetadata: ClientMetadata\n readonly responseMode: OAuthResponseMode\n readonly keyset?: Keyset\n\n // Services\n readonly runtime: Runtime\n readonly fetch: Fetch\n readonly oauthResolver: OAuthResolver\n readonly serverFactory: OAuthServerFactory\n\n // Stores\n protected readonly sessionGetter: SessionGetter\n protected readonly stateStore: StateStore\n\n constructor(options: OAuthClientOptions) {\n const {\n stateStore,\n sessionStore,\n\n dpopNonceCache = new SimpleStoreMemory({ ttl: 60e3, max: 100 }),\n authorizationServerMetadataCache = new SimpleStoreMemory({\n ttl: 60e3,\n max: 100,\n }),\n protectedResourceMetadataCache = new SimpleStoreMemory({\n ttl: 60e3,\n max: 100,\n }),\n\n responseMode,\n clientMetadata,\n runtimeImplementation,\n keyset,\n } = options\n\n this.keyset = keyset\n ? keyset instanceof Keyset\n ? keyset\n : new Keyset(keyset)\n : undefined\n this.clientMetadata = validateClientMetadata(clientMetadata, this.keyset)\n this.responseMode = responseMode\n\n this.runtime = new Runtime(runtimeImplementation)\n this.fetch = options.fetch ?? globalThis.fetch\n this.oauthResolver = new OAuthResolver(\n createIdentityResolver(options),\n new OAuthProtectedResourceMetadataResolver(\n protectedResourceMetadataCache,\n this.fetch,\n { allowHttpResource: options.allowHttp },\n ),\n new OAuthAuthorizationServerMetadataResolver(\n authorizationServerMetadataCache,\n this.fetch,\n { allowHttpIssuer: options.allowHttp },\n ),\n )\n this.serverFactory = new OAuthServerFactory(\n this.clientMetadata,\n this.runtime,\n this.oauthResolver,\n this.fetch,\n this.keyset,\n dpopNonceCache,\n )\n\n this.stateStore = stateStore\n this.sessionGetter = new SessionGetter(\n sessionStore,\n this.serverFactory,\n this.runtime,\n options,\n )\n }\n\n // Exposed as public API for convenience\n get identityResolver() {\n return this.oauthResolver.identityResolver\n }\n\n get jwks() {\n return this.keyset?.publicJwks ?? ({ keys: [] as const } as const)\n }\n\n async authorize(\n input: string,\n { signal, ...options }: AuthorizeOptions = {},\n ): Promise<URL> {\n const redirectUri =\n options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]\n if (!this.clientMetadata.redirect_uris.includes(redirectUri)) {\n // The server will enforce this, but let's catch it early\n throw new TypeError('Invalid redirect_uri')\n }\n\n const { identityInfo, metadata } = await this.oauthResolver.resolve(input, {\n signal,\n })\n\n const pkce = await this.runtime.generatePKCE()\n const dpopKey = await this.runtime.generateKey(\n metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG],\n )\n\n const authMethod = negotiateClientAuthMethod(\n metadata,\n this.clientMetadata,\n this.keyset,\n )\n const state = await this.runtime.generateNonce()\n\n await this.stateStore.set(state, {\n iss: metadata.issuer,\n dpopKey,\n authMethod,\n verifier: pkce.verifier,\n appState: options?.state,\n })\n\n const parameters: OAuthAuthorizationRequestParameters = {\n ...options,\n\n client_id: this.clientMetadata.client_id,\n redirect_uri: redirectUri,\n code_challenge: pkce.challenge,\n code_challenge_method: pkce.method,\n state,\n login_hint: identityInfo\n ? identityInfo.handle !== HANDLE_INVALID\n ? identityInfo.handle\n : identityInfo.did\n : undefined,\n response_mode: this.responseMode,\n response_type: 'code' as const,\n scope: options?.scope ?? this.clientMetadata.scope,\n }\n\n const authorizationUrl = new URL(metadata.authorization_endpoint)\n\n // Since the user will be redirected to the authorization_endpoint url using\n // a browser, we need to make sure that the url is valid.\n if (\n authorizationUrl.protocol !== 'https:' &&\n authorizationUrl.protocol !== 'http:'\n ) {\n throw new TypeError(\n `Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`,\n )\n }\n\n if (metadata.pushed_authorization_request_endpoint) {\n const server = await this.serverFactory.fromMetadata(\n metadata,\n authMethod,\n dpopKey,\n )\n const parResponse = await server.request(\n 'pushed_authorization_request',\n parameters,\n )\n\n authorizationUrl.searchParams.set(\n 'client_id',\n this.clientMetadata.client_id,\n )\n authorizationUrl.searchParams.set('request_uri', parResponse.request_uri)\n return authorizationUrl\n } else if (metadata.require_pushed_authorization_requests) {\n throw new Error(\n 'Server requires pushed authorization requests (PAR) but no PAR endpoint is available',\n )\n } else {\n for (const [key, value] of Object.entries(parameters)) {\n if (value) authorizationUrl.searchParams.set(key, String(value))\n }\n\n // Length of the URL that will be sent to the server\n const urlLength =\n authorizationUrl.pathname.length + authorizationUrl.search.length\n if (urlLength < 2048) {\n return authorizationUrl\n } else if (!metadata.pushed_authorization_request_endpoint) {\n throw new Error('Login URL too long')\n }\n }\n\n throw new Error(\n 'Server does not support pushed authorization requests (PAR)',\n )\n }\n\n /**\n * This method allows the client to proactively revoke the request_uri it\n * created through PAR.\n */\n async abortRequest(authorizeUrl: URL) {\n const requestUri = authorizeUrl.searchParams.get('request_uri')\n if (!requestUri) return\n\n // @NOTE This is not implemented here because, 1) the request server should\n // invalidate the request_uri after some delay anyways, and 2) I am not sure\n // that the revocation endpoint is even supposed to support this (and I\n // don't want to spend the time checking now).\n\n // @TODO investigate actual necessity & feasibility of this feature\n }\n\n async callback(\n params: URLSearchParams,\n options: CallbackOptions = {},\n ): Promise<{\n session: OAuthSession\n state: string | null\n }> {\n const responseJwt = params.get('response')\n if (responseJwt != null) {\n // https://openid.net/specs/oauth-v2-jarm.html\n throw new OAuthCallbackError(params, 'JARM not supported')\n }\n\n const issuerParam = params.get('iss')\n const stateParam = params.get('state')\n const errorParam = params.get('error')\n const codeParam = params.get('code')\n\n if (!stateParam) {\n throw new OAuthCallbackError(params, 'Missing \"state\" parameter')\n }\n const stateData = await this.stateStore.get(stateParam)\n if (stateData) {\n // Prevent any kind of replay\n await this.stateStore.del(stateParam)\n } else {\n throw new OAuthCallbackError(\n params,\n `Unknown authorization session \"${stateParam}\"`,\n )\n }\n\n try {\n if (errorParam != null) {\n throw new OAuthCallbackError(params, undefined, stateData.appState)\n }\n\n if (!codeParam) {\n throw new OAuthCallbackError(\n params,\n 'Missing \"code\" query param',\n stateData.appState,\n )\n }\n\n const server = await this.serverFactory.fromIssuer(\n stateData.iss,\n stateData.authMethod,\n stateData.dpopKey,\n )\n\n if (issuerParam != null) {\n if (!server.issuer) {\n throw new OAuthCallbackError(\n params,\n 'Issuer not found in metadata',\n stateData.appState,\n )\n }\n if (server.issuer !== issuerParam) {\n throw new OAuthCallbackError(\n params,\n 'Issuer mismatch',\n stateData.appState,\n )\n }\n } else if (\n server.serverMetadata.authorization_response_iss_parameter_supported\n ) {\n throw new OAuthCallbackError(\n params,\n 'iss missing from the response',\n stateData.appState,\n )\n }\n\n const tokenSet = await server.exchangeCode(\n codeParam,\n stateData.verifier,\n options?.redirect_uri ?? server.clientMetadata.redirect_uris[0],\n )\n\n // We revoke any existing session first to avoid leaving orphaned sessions\n // on the AS.\n try {\n await this.revoke(tokenSet.sub)\n } catch {\n // No existing session, or failed to get it. This is fine.\n }\n\n try {\n await this.sessionGetter.setStored(tokenSet.sub, {\n dpopKey: stateData.dpopKey,\n authMethod: server.authMethod,\n tokenSet,\n })\n\n const session = this.createSession(server, tokenSet.sub)\n\n return { session, state: stateData.appState ?? null }\n } catch (err) {\n await server.revoke(tokenSet.refresh_token || tokenSet.access_token)\n\n throw err\n }\n } catch (err) {\n // Make sure, whatever the underlying error, that the appState is\n // available in the calling code\n throw OAuthCallbackError.from(err, params, stateData.appState)\n }\n }\n\n /**\n * Load a stored session. This will refresh the token only if needed (about to\n * expire) by default.\n *\n * @see {@link SessionGetter.restore}\n */\n async restore(\n sub: string,\n refresh: boolean | 'auto' = 'auto',\n ): Promise<OAuthSession> {\n // sub arg is lightly typed for convenience of library user\n assertAtprotoDid(sub)\n\n const { dpopKey, authMethod, tokenSet } =\n await this.sessionGetter.getSession(sub, refresh)\n\n try {\n const server = await this.serverFactory.fromIssuer(\n tokenSet.iss,\n authMethod,\n dpopKey,\n {\n noCache: refresh === true,\n allowStale: refresh === false,\n },\n )\n\n return this.createSession(server, sub)\n } catch (err) {\n if (err instanceof AuthMethodUnsatisfiableError) {\n await this.sessionGetter.delStored(sub, err)\n }\n\n throw err\n }\n }\n\n async revoke(sub: string) {\n // sub arg is lightly typed for convenience of library user\n assertAtprotoDid(sub)\n\n const res = await this.sessionGetter.getSession(sub, false).catch((err) => {\n if (isExpectedSessionError(err)) return null\n throw err\n })\n\n if (!res) return\n\n const { dpopKey, authMethod, tokenSet } = res\n\n // NOT using `;(await this.restore(sub, false)).signOut()` because we want\n // the tokens to be deleted even if it was not possible to fetch the issuer\n // data.\n try {\n const server = await this.serverFactory.fromIssuer(\n tokenSet.iss,\n authMethod,\n dpopKey,\n )\n await server.revoke(tokenSet.access_token)\n } finally {\n await this.sessionGetter.delStored(sub, new TokenRevokedError(sub))\n }\n }\n\n protected createSession(\n server: OAuthServerAgent,\n sub: AtprotoDid,\n ): OAuthSession {\n return new OAuthSession(server, sub, this.sessionGetter, this.fetch)\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type OAuthProtectedResourceMetadata } from '@atproto/oauth-types';
|
|
2
|
+
import { type Fetch } from '@atproto-labs/fetch';
|
|
3
|
+
import { CachedGetter, type GetCachedOptions, type SimpleStore, type StoreErrorHandler } from '@atproto-labs/simple-store';
|
|
4
|
+
export type { GetCachedOptions, OAuthProtectedResourceMetadata };
|
|
5
|
+
export type ProtectedResourceMetadataCache = SimpleStore<string, OAuthProtectedResourceMetadata | null>;
|
|
6
|
+
export type OAuthProtectedResourceMetadataResolverConfig = {
|
|
7
|
+
allowHttpResource?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Called when the metadata cache throws. The cache is best-effort: failures
|
|
10
|
+
* are logged (by default) and degrade to a cache miss + refetch, rather than
|
|
11
|
+
* being propagated.
|
|
12
|
+
*/
|
|
13
|
+
onCacheError?: StoreErrorHandler<string>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html}
|
|
17
|
+
*/
|
|
18
|
+
export declare class OAuthProtectedResourceMetadataResolver extends CachedGetter<string, OAuthProtectedResourceMetadata | null> {
|
|
19
|
+
private readonly fetch;
|
|
20
|
+
private readonly allowHttpResource;
|
|
21
|
+
constructor(cache: ProtectedResourceMetadataCache, fetch?: Fetch, config?: OAuthProtectedResourceMetadataResolverConfig);
|
|
22
|
+
get(resource: string | URL, options?: GetCachedOptions): Promise<OAuthProtectedResourceMetadata | null>;
|
|
23
|
+
private fetchMetadata;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=oauth-protected-resource-metadata-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-protected-resource-metadata-resolver.d.ts","sourceRoot":"","sources":["../src/oauth-protected-resource-metadata-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,8BAA8B,EAEpC,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,KAAK,EAIX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EAEvB,MAAM,4BAA4B,CAAA;AAGnC,YAAY,EAAE,gBAAgB,EAAE,8BAA8B,EAAE,CAAA;AAEhE,MAAM,MAAM,8BAA8B,GAAG,WAAW,CACtD,MAAM,EACN,8BAA8B,GAAG,IAAI,CACtC,CAAA;AAED,MAAM,MAAM,4CAA4C,GAAG;IACzD,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAE3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAA;CACzC,CAAA;AAED;;GAEG;AACH,qBAAa,sCAAuC,SAAQ,YAAY,CACtE,MAAM,EACN,8BAA8B,GAAG,IAAI,CACtC;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C,YACE,KAAK,EAAE,8BAA8B,EACrC,KAAK,GAAE,KAAwB,EAC/B,MAAM,CAAC,EAAE,4CAA4C,EAStD;IAEK,GAAG,CACP,QAAQ,EAAE,MAAM,GAAG,GAAG,EACtB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAgBhD;YAEa,aAAa;CAmD5B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { oauthProtectedResourceMetadataSchema, } from '@atproto/oauth-types';
|
|
2
|
+
import { FetchResponseError, bindFetch, cancelBody, } from '@atproto-labs/fetch';
|
|
3
|
+
import { CachedGetter, swallowStoreErrors, } from '@atproto-labs/simple-store';
|
|
4
|
+
import { contentMime } from './util.js';
|
|
5
|
+
/**
|
|
6
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html}
|
|
7
|
+
*/
|
|
8
|
+
export class OAuthProtectedResourceMetadataResolver extends CachedGetter {
|
|
9
|
+
constructor(cache, fetch = globalThis.fetch, config) {
|
|
10
|
+
super(async (origin, options) => this.fetchMetadata(origin, options), swallowStoreErrors(cache, config?.onCacheError));
|
|
11
|
+
this.fetch = bindFetch(fetch);
|
|
12
|
+
this.allowHttpResource = config?.allowHttpResource === true;
|
|
13
|
+
}
|
|
14
|
+
async get(resource, options) {
|
|
15
|
+
const { protocol, origin } = new URL(resource);
|
|
16
|
+
if (protocol !== 'https:' && protocol !== 'http:') {
|
|
17
|
+
throw new TypeError(`Invalid protected resource metadata URL protocol: ${protocol}`);
|
|
18
|
+
}
|
|
19
|
+
if (protocol === 'http:' && !this.allowHttpResource) {
|
|
20
|
+
throw new TypeError(`Unsecure resource metadata URL (${protocol}) only allowed in development and test environments`);
|
|
21
|
+
}
|
|
22
|
+
return super.get(origin, options);
|
|
23
|
+
}
|
|
24
|
+
async fetchMetadata(origin, options) {
|
|
25
|
+
const url = new URL(`/.well-known/oauth-protected-resource`, origin);
|
|
26
|
+
const request = new Request(url, {
|
|
27
|
+
signal: options?.signal,
|
|
28
|
+
headers: { accept: 'application/json' },
|
|
29
|
+
cache: options?.noCache ? 'no-cache' : undefined,
|
|
30
|
+
redirect: 'manual', // response must be 200 OK
|
|
31
|
+
});
|
|
32
|
+
const response = await this.fetch(request);
|
|
33
|
+
if (response.status === 404) {
|
|
34
|
+
await cancelBody(response, 'log');
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2
|
|
38
|
+
if (response.status !== 200) {
|
|
39
|
+
await cancelBody(response, 'log');
|
|
40
|
+
throw await FetchResponseError.from(response, `Unexpected status code ${response.status} for "${url}"`, undefined, { cause: request });
|
|
41
|
+
}
|
|
42
|
+
if (contentMime(response.headers) !== 'application/json') {
|
|
43
|
+
await cancelBody(response, 'log');
|
|
44
|
+
throw await FetchResponseError.from(response, `Unexpected content type for "${url}"`, undefined, { cause: request });
|
|
45
|
+
}
|
|
46
|
+
const metadata = oauthProtectedResourceMetadataSchema.parse(await response.json());
|
|
47
|
+
// https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3
|
|
48
|
+
if (metadata.resource !== origin) {
|
|
49
|
+
throw new TypeError(`Invalid issuer ${metadata.resource}`);
|
|
50
|
+
}
|
|
51
|
+
return metadata;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=oauth-protected-resource-metadata-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-protected-resource-metadata-resolver.js","sourceRoot":"","sources":["../src/oauth-protected-resource-metadata-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type OAuthProtectedResourceMetadata,\n oauthProtectedResourceMetadataSchema,\n} from '@atproto/oauth-types'\nimport {\n type Fetch,\n FetchResponseError,\n bindFetch,\n cancelBody,\n} from '@atproto-labs/fetch'\nimport {\n CachedGetter,\n type GetCachedOptions,\n type SimpleStore,\n type StoreErrorHandler,\n swallowStoreErrors,\n} from '@atproto-labs/simple-store'\nimport { contentMime } from './util.js'\n\nexport type { GetCachedOptions, OAuthProtectedResourceMetadata }\n\nexport type ProtectedResourceMetadataCache = SimpleStore<\n string,\n OAuthProtectedResourceMetadata | null\n>\n\nexport type OAuthProtectedResourceMetadataResolverConfig = {\n allowHttpResource?: boolean\n\n /**\n * Called when the metadata cache throws. The cache is best-effort: failures\n * are logged (by default) and degrade to a cache miss + refetch, rather than\n * being propagated.\n */\n onCacheError?: StoreErrorHandler<string>\n}\n\n/**\n * @see {@link https://www.rfc-editor.org/rfc/rfc9728.html}\n */\nexport class OAuthProtectedResourceMetadataResolver extends CachedGetter<\n string,\n OAuthProtectedResourceMetadata | null\n> {\n private readonly fetch: Fetch<unknown>\n private readonly allowHttpResource: boolean\n\n constructor(\n cache: ProtectedResourceMetadataCache,\n fetch: Fetch = globalThis.fetch,\n config?: OAuthProtectedResourceMetadataResolverConfig,\n ) {\n super(\n async (origin, options) => this.fetchMetadata(origin, options),\n swallowStoreErrors(cache, config?.onCacheError),\n )\n\n this.fetch = bindFetch(fetch)\n this.allowHttpResource = config?.allowHttpResource === true\n }\n\n async get(\n resource: string | URL,\n options?: GetCachedOptions,\n ): Promise<OAuthProtectedResourceMetadata | null> {\n const { protocol, origin } = new URL(resource)\n\n if (protocol !== 'https:' && protocol !== 'http:') {\n throw new TypeError(\n `Invalid protected resource metadata URL protocol: ${protocol}`,\n )\n }\n\n if (protocol === 'http:' && !this.allowHttpResource) {\n throw new TypeError(\n `Unsecure resource metadata URL (${protocol}) only allowed in development and test environments`,\n )\n }\n\n return super.get(origin, options)\n }\n\n private async fetchMetadata(\n origin: string,\n options?: GetCachedOptions,\n ): Promise<OAuthProtectedResourceMetadata | null> {\n const url = new URL(`/.well-known/oauth-protected-resource`, origin)\n const request = new Request(url, {\n signal: options?.signal,\n headers: { accept: 'application/json' },\n cache: options?.noCache ? 'no-cache' : undefined,\n redirect: 'manual', // response must be 200 OK\n })\n\n const response = await this.fetch(request)\n\n if (response.status === 404) {\n await cancelBody(response, 'log')\n return null\n }\n\n // https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2\n if (response.status !== 200) {\n await cancelBody(response, 'log')\n throw await FetchResponseError.from(\n response,\n `Unexpected status code ${response.status} for \"${url}\"`,\n undefined,\n { cause: request },\n )\n }\n\n if (contentMime(response.headers) !== 'application/json') {\n await cancelBody(response, 'log')\n throw await FetchResponseError.from(\n response,\n `Unexpected content type for \"${url}\"`,\n undefined,\n { cause: request },\n )\n }\n\n const metadata = oauthProtectedResourceMetadataSchema.parse(\n await response.json(),\n )\n\n // https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3\n if (metadata.resource !== origin) {\n throw new TypeError(`Invalid issuer ${metadata.resource}`)\n }\n\n return metadata\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-resolver-error.d.ts","sourceRoot":"","sources":["../src/oauth-resolver-error.ts"],"names":[],"mappings":"AAEA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAEzD;IAED,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAYhE;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ZodError } from 'zod';
|
|
2
|
+
export class OAuthResolverError extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
}
|
|
6
|
+
static from(cause, message) {
|
|
7
|
+
if (cause instanceof OAuthResolverError)
|
|
8
|
+
return cause;
|
|
9
|
+
const validationReason = cause instanceof ZodError
|
|
10
|
+
? `${cause.errors[0].path} ${cause.errors[0].message}`
|
|
11
|
+
: null;
|
|
12
|
+
const fullMessage = (message ?? `Unable to resolve identity`) +
|
|
13
|
+
(validationReason ? ` (${validationReason})` : '');
|
|
14
|
+
return new OAuthResolverError(fullMessage, {
|
|
15
|
+
cause,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=oauth-resolver-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-resolver-error.js","sourceRoot":"","sources":["../src/oauth-resolver-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAA;AAE9B,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,KAAc,EAAE,OAAgB;QAC1C,IAAI,KAAK,YAAY,kBAAkB;YAAE,OAAO,KAAK,CAAA;QACrD,MAAM,gBAAgB,GACpB,KAAK,YAAY,QAAQ;YACvB,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;YACtD,CAAC,CAAC,IAAI,CAAA;QACV,MAAM,WAAW,GACf,CAAC,OAAO,IAAI,4BAA4B,CAAC;YACzC,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,gBAAgB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACpD,OAAO,IAAI,kBAAkB,CAAC,WAAW,EAAE;YACzC,KAAK;SACN,CAAC,CAAA;IACJ,CAAC;CACF","sourcesContent":["import { ZodError } from 'zod'\n\nexport class OAuthResolverError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n }\n\n static from(cause: unknown, message?: string): OAuthResolverError {\n if (cause instanceof OAuthResolverError) return cause\n const validationReason =\n cause instanceof ZodError\n ? `${cause.errors[0].path} ${cause.errors[0].message}`\n : null\n const fullMessage =\n (message ?? `Unable to resolve identity`) +\n (validationReason ? ` (${validationReason})` : '')\n return new OAuthResolverError(fullMessage, {\n cause,\n })\n }\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type OAuthAuthorizationServerMetadata } from '@atproto/oauth-types';
|
|
2
|
+
import type { IdentityInfo, IdentityResolver, ResolveIdentityOptions } from '@atproto-labs/identity-resolver';
|
|
3
|
+
import type { GetCachedOptions, OAuthAuthorizationServerMetadataResolver } from './oauth-authorization-server-metadata-resolver.js';
|
|
4
|
+
import type { OAuthProtectedResourceMetadataResolver } from './oauth-protected-resource-metadata-resolver.js';
|
|
5
|
+
export type { GetCachedOptions };
|
|
6
|
+
export type ResolveOAuthOptions = GetCachedOptions & ResolveIdentityOptions;
|
|
7
|
+
export declare class OAuthResolver {
|
|
8
|
+
readonly identityResolver: IdentityResolver;
|
|
9
|
+
readonly protectedResourceMetadataResolver: OAuthProtectedResourceMetadataResolver;
|
|
10
|
+
readonly authorizationServerMetadataResolver: OAuthAuthorizationServerMetadataResolver;
|
|
11
|
+
constructor(identityResolver: IdentityResolver, protectedResourceMetadataResolver: OAuthProtectedResourceMetadataResolver, authorizationServerMetadataResolver: OAuthAuthorizationServerMetadataResolver);
|
|
12
|
+
/**
|
|
13
|
+
* @param input - A handle, DID, PDS URL or Entryway URL
|
|
14
|
+
*/
|
|
15
|
+
resolve(input: string, options?: ResolveOAuthOptions): Promise<{
|
|
16
|
+
identityInfo?: IdentityInfo;
|
|
17
|
+
metadata: OAuthAuthorizationServerMetadata;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* @note this method can be used to verify if a particular uri supports OAuth
|
|
21
|
+
* based sign-in (for compatibility with legacy implementation).
|
|
22
|
+
*/
|
|
23
|
+
resolveFromService(input: string, options?: ResolveOAuthOptions): Promise<{
|
|
24
|
+
metadata: OAuthAuthorizationServerMetadata;
|
|
25
|
+
}>;
|
|
26
|
+
resolveFromIdentity(input: string, options?: ResolveOAuthOptions): Promise<{
|
|
27
|
+
identityInfo: IdentityInfo;
|
|
28
|
+
metadata: OAuthAuthorizationServerMetadata;
|
|
29
|
+
pds: URL;
|
|
30
|
+
}>;
|
|
31
|
+
resolveIdentity(input: string, options?: ResolveIdentityOptions): Promise<IdentityInfo>;
|
|
32
|
+
getAuthorizationServerMetadata(issuer: string | URL, options?: GetCachedOptions): Promise<OAuthAuthorizationServerMetadata>;
|
|
33
|
+
getResourceServerMetadata(pdsUrl: string | URL, options?: GetCachedOptions): Promise<{
|
|
34
|
+
issuer: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}`;
|
|
35
|
+
claims_supported?: string[] | undefined;
|
|
36
|
+
claims_locales_supported?: string[] | undefined;
|
|
37
|
+
claims_parameter_supported?: boolean | undefined;
|
|
38
|
+
request_parameter_supported?: boolean | undefined;
|
|
39
|
+
request_uri_parameter_supported?: boolean | undefined;
|
|
40
|
+
require_request_uri_registration?: boolean | undefined;
|
|
41
|
+
scopes_supported?: string[] | undefined;
|
|
42
|
+
subject_types_supported?: string[] | undefined;
|
|
43
|
+
response_types_supported?: string[] | undefined;
|
|
44
|
+
response_modes_supported?: string[] | undefined;
|
|
45
|
+
grant_types_supported?: string[] | undefined;
|
|
46
|
+
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
|
|
47
|
+
ui_locales_supported?: string[] | undefined;
|
|
48
|
+
id_token_signing_alg_values_supported?: string[] | undefined;
|
|
49
|
+
display_values_supported?: string[] | undefined;
|
|
50
|
+
request_object_signing_alg_values_supported?: string[] | undefined;
|
|
51
|
+
authorization_response_iss_parameter_supported?: boolean | undefined;
|
|
52
|
+
authorization_details_types_supported?: string[] | undefined;
|
|
53
|
+
request_object_encryption_alg_values_supported?: string[] | undefined;
|
|
54
|
+
request_object_encryption_enc_values_supported?: string[] | undefined;
|
|
55
|
+
jwks_uri?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
56
|
+
authorization_endpoint: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}`;
|
|
57
|
+
token_endpoint: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}`;
|
|
58
|
+
token_endpoint_auth_methods_supported: string[];
|
|
59
|
+
token_endpoint_auth_signing_alg_values_supported?: string[] | undefined;
|
|
60
|
+
revocation_endpoint?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
61
|
+
introspection_endpoint?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
62
|
+
pushed_authorization_request_endpoint?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
63
|
+
require_pushed_authorization_requests?: boolean | undefined;
|
|
64
|
+
userinfo_endpoint?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
65
|
+
end_session_endpoint?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
66
|
+
registration_endpoint?: "http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}` | undefined;
|
|
67
|
+
dpop_signing_alg_values_supported?: string[] | undefined;
|
|
68
|
+
protected_resources?: ("http://127.0.0.1" | "http://localhost" | `http://127.0.0.1#${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `http://127.0.0.1?${string}` | `http://[::1]${string}` | `http://localhost#${string}` | `http://localhost/${string}` | `http://localhost:${string}` | `http://localhost?${string}` | `https://${string}`)[] | undefined;
|
|
69
|
+
client_id_metadata_document_supported?: boolean | undefined;
|
|
70
|
+
prompt_values_supported?: ("consent" | "create" | "login" | "none" | "select_account")[] | undefined;
|
|
71
|
+
}>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=oauth-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-resolver.d.ts","sourceRoot":"","sources":["../src/oauth-resolver.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gCAAgC,EAEtC,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EACV,gBAAgB,EAChB,wCAAwC,EACzC,MAAM,mDAAmD,CAAA;AAC1D,OAAO,KAAK,EAAE,sCAAsC,EAAE,MAAM,iDAAiD,CAAA;AAG7G,YAAY,EAAE,gBAAgB,EAAE,CAAA;AAChC,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG,sBAAsB,CAAA;AAE3E,qBAAa,aAAa;IAEtB,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB;IAC3C,QAAQ,CAAC,iCAAiC,EAAE,sCAAsC;IAClF,QAAQ,CAAC,mCAAmC,EAAE,wCAAwC;IAHxF,YACW,gBAAgB,EAAE,gBAAgB,EAClC,iCAAiC,EAAE,sCAAsC,EACzE,mCAAmC,EAAE,wCAAwC,EACpF;IAEJ;;OAEG;IACU,OAAO,CAClB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC;QACT,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,QAAQ,EAAE,gCAAgC,CAAA;KAC3C,CAAC,CAOD;IAED;;;OAGG;IACU,kBAAkB,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC;QACT,QAAQ,EAAE,gCAAgC,CAAA;KAC3C,CAAC,CAwBD;IAEY,mBAAmB,CAC9B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC;QACT,YAAY,EAAE,YAAY,CAAA;QAC1B,QAAQ,EAAE,gCAAgC,CAAA;QAC1C,GAAG,EAAE,GAAG,CAAA;KACT,CAAC,CAUD;IAEY,eAAe,CAC1B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,YAAY,CAAC,CASvB;IAEY,8BAA8B,CACzC,MAAM,EAAE,MAAM,GAAG,GAAG,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gCAAgC,CAAC,CAS3C;IAEY,yBAAyB,CACpC,MAAM,EAAE,MAAM,GAAG,GAAG,EACpB,OAAO,CAAC,EAAE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8C3B;CACF"}
|