@atproto/oauth-client 0.1.0
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 +20 -0
- package/LICENSE.txt +7 -0
- package/README.md +124 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/fetch-dpop.d.ts +21 -0
- package/dist/fetch-dpop.d.ts.map +1 -0
- package/dist/fetch-dpop.js +149 -0
- package/dist/fetch-dpop.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +2 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +33 -0
- package/dist/lock.js.map +1 -0
- package/dist/oauth-agent.d.ts +29 -0
- package/dist/oauth-agent.d.ts.map +1 -0
- package/dist/oauth-agent.js +138 -0
- package/dist/oauth-agent.js.map +1 -0
- package/dist/oauth-authorization-server-metadata-resolver.d.ts +15 -0
- package/dist/oauth-authorization-server-metadata-resolver.d.ts.map +1 -0
- package/dist/oauth-authorization-server-metadata-resolver.js +56 -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 +28 -0
- package/dist/oauth-callback-error.js.map +1 -0
- package/dist/oauth-client.d.ts +78 -0
- package/dist/oauth-client.d.ts.map +1 -0
- package/dist/oauth-client.js +278 -0
- package/dist/oauth-client.js.map +1 -0
- package/dist/oauth-protected-resource-metadata-resolver.d.ts +15 -0
- package/dist/oauth-protected-resource-metadata-resolver.d.ts.map +1 -0
- package/dist/oauth-protected-resource-metadata-resolver.js +58 -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 +17 -0
- package/dist/oauth-resolver-error.js.map +1 -0
- package/dist/oauth-resolver.d.ts +62 -0
- package/dist/oauth-resolver.d.ts.map +1 -0
- package/dist/oauth-resolver.js +73 -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 +48 -0
- package/dist/oauth-response-error.js.map +1 -0
- package/dist/oauth-server-agent.d.ts +51 -0
- package/dist/oauth-server-agent.d.ts.map +1 -0
- package/dist/oauth-server-agent.js +228 -0
- package/dist/oauth-server-agent.js.map +1 -0
- package/dist/oauth-server-factory.d.ts +20 -0
- package/dist/oauth-server-factory.d.ts.map +1 -0
- package/dist/oauth-server-factory.js +53 -0
- package/dist/oauth-server-factory.js.map +1 -0
- package/dist/refresh-error.d.ts +7 -0
- package/dist/refresh-error.d.ts.map +1 -0
- package/dist/refresh-error.js +16 -0
- package/dist/refresh-error.js.map +1 -0
- package/dist/runtime-implementation.d.ts +12 -0
- package/dist/runtime-implementation.d.ts.map +1 -0
- package/dist/runtime-implementation.js +3 -0
- package/dist/runtime-implementation.js.map +1 -0
- package/dist/runtime.d.ts +35 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +185 -0
- package/dist/runtime.js.map +1 -0
- package/dist/session-getter.d.ts +30 -0
- package/dist/session-getter.d.ts.map +1 -0
- package/dist/session-getter.js +149 -0
- package/dist/session-getter.js.map +1 -0
- package/dist/types.d.ts +1580 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +9 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +35 -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 +46 -0
- package/dist/validate-client-metadata.js.map +1 -0
- package/package.json +46 -0
- package/src/constants.ts +4 -0
- package/src/fetch-dpop.ts +235 -0
- package/src/index.ts +18 -0
- package/src/lock.ts +34 -0
- package/src/oauth-agent.ts +150 -0
- package/src/oauth-authorization-server-metadata-resolver.ts +98 -0
- package/src/oauth-callback-error.ts +16 -0
- package/src/oauth-client.ts +440 -0
- package/src/oauth-protected-resource-metadata-resolver.ts +102 -0
- package/src/oauth-resolver-error.ts +12 -0
- package/src/oauth-resolver.ts +111 -0
- package/src/oauth-response-error.ts +31 -0
- package/src/oauth-server-agent.ts +275 -0
- package/src/oauth-server-factory.ts +41 -0
- package/src/refresh-error.ts +9 -0
- package/src/runtime-implementation.ts +17 -0
- package/src/runtime.ts +211 -0
- package/src/session-getter.ts +182 -0
- package/src/types.ts +26 -0
- package/src/util.ts +51 -0
- package/src/validate-client-metadata.ts +61 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @atproto/oauth-client
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2482](https://github.com/bluesky-social/atproto/pull/2482) [`a8d6c1123`](https://github.com/bluesky-social/atproto/commit/a8d6c112359f5c4c0cfbe2df63443ed275f2a646) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add OAuth provider capability & support for DPoP signed tokens
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`a8d6c1123`](https://github.com/bluesky-social/atproto/commit/a8d6c112359f5c4c0cfbe2df63443ed275f2a646)]:
|
|
12
|
+
- @atproto-labs/simple-store-memory@0.1.0
|
|
13
|
+
- @atproto-labs/identity-resolver@0.1.0
|
|
14
|
+
- @atproto-labs/handle-resolver@0.1.0
|
|
15
|
+
- @atproto-labs/did-resolver@0.1.0
|
|
16
|
+
- @atproto-labs/simple-store@0.1.0
|
|
17
|
+
- @atproto/oauth-types@0.1.0
|
|
18
|
+
- @atproto-labs/fetch@0.1.0
|
|
19
|
+
- @atproto/jwk@0.1.0
|
|
20
|
+
- @atproto/did@0.1.0
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Dual MIT/Apache-2.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2024 Bluesky PBC, and Contributors
|
|
4
|
+
|
|
5
|
+
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
|
+
|
|
7
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @atproto/oauth-client: atproto flavoured OAuth client
|
|
2
|
+
|
|
3
|
+
Core library for implementing ATPROTO OAuth clients.
|
|
4
|
+
|
|
5
|
+
For a browser specific implementation, see `@atproto/oauth-client-browser`.
|
|
6
|
+
For a node specific implementation, see `@atproto/oauth-client-node`.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { OAuthClient } from '@atproto/oauth-client'
|
|
10
|
+
import { JoseKey } from '@atproto/jwk-jose' // NodeJS/Browser only
|
|
11
|
+
|
|
12
|
+
const client = new OAuthClient({
|
|
13
|
+
handleResolver: 'https://bsky.social', // On node, you should use a DNS based resolver
|
|
14
|
+
responseMode: 'query', // or "fragment" or "form_post" (for backend clients only)
|
|
15
|
+
clientMetadata: {
|
|
16
|
+
// These must be the same metadata as the one exposed on the
|
|
17
|
+
// "/.well-known/oauth-client-metadata" endpoint (except when using a
|
|
18
|
+
// loopback client)
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
runtimeImplementation: {
|
|
22
|
+
// A runtime specific implementation of the crypto operations needed by the
|
|
23
|
+
// OAuth client.
|
|
24
|
+
|
|
25
|
+
createKey(algs: string[]): Promise<Key> {
|
|
26
|
+
// algs is an ordered array of preferred algorithms (e.g. ['RS256', 'ES256'])
|
|
27
|
+
|
|
28
|
+
// Note, in browser environments, it is better to use non extractable keys
|
|
29
|
+
// to prevent leaking the private key. This can be done using the
|
|
30
|
+
// WebcryptoKey class from the "@atproto/jwk-webcrypto" package. The
|
|
31
|
+
// inconvenient of these keys (which is also what makes them stronger) is
|
|
32
|
+
// that the only way to persist them across browser reloads is to save
|
|
33
|
+
// them in the indexed DB.
|
|
34
|
+
return JoseKey.generate(algs)
|
|
35
|
+
},
|
|
36
|
+
getRandomValues(length: number): Uint8Array | PromiseLike<Uint8Array> {
|
|
37
|
+
// length is the number of bytes to generate
|
|
38
|
+
|
|
39
|
+
const bytes = new Uint8Array(byteLength)
|
|
40
|
+
crypto.getRandomValues(bytes)
|
|
41
|
+
return bytes
|
|
42
|
+
},
|
|
43
|
+
digest(
|
|
44
|
+
bytes: Uint8Array,
|
|
45
|
+
algorithm: { name: 'sha256' | 'sha384' | 'sha512' },
|
|
46
|
+
): Uint8Array | PromiseLike<Uint8Array> {
|
|
47
|
+
// sha256 is required. Unsupported algorithms should throw an error.
|
|
48
|
+
|
|
49
|
+
const buffer = await this.crypto.subtle.digest(
|
|
50
|
+
algorithm.name.startsWith('sha')
|
|
51
|
+
? `SHA-${algorithm.name.slice(-3)}`
|
|
52
|
+
: 'invalid',
|
|
53
|
+
bytes,
|
|
54
|
+
)
|
|
55
|
+
return new Uint8Array(buffer)
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
stateStore: {
|
|
60
|
+
// A store for saving state data while the user is being redirected to the
|
|
61
|
+
// authorization server.
|
|
62
|
+
|
|
63
|
+
set(key: string, internalState: InternalStateData): Promise<void> {
|
|
64
|
+
throw new Error('Not implemented')
|
|
65
|
+
},
|
|
66
|
+
get(key: string): Promise<InternalStateData | undefined> {
|
|
67
|
+
throw new Error('Not implemented')
|
|
68
|
+
},
|
|
69
|
+
del(key: string): Promise<void> {
|
|
70
|
+
throw new Error('Not implemented')
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
sessionStore: {
|
|
75
|
+
// A store for saving session data.
|
|
76
|
+
|
|
77
|
+
set(sub: string, session: Session): Promise<void> {
|
|
78
|
+
throw new Error('Not implemented')
|
|
79
|
+
},
|
|
80
|
+
get(sub: string): Promise<Session | undefined> {
|
|
81
|
+
throw new Error('Not implemented')
|
|
82
|
+
},
|
|
83
|
+
del(sub: string): Promise<void> {
|
|
84
|
+
throw new Error('Not implemented')
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
keyset: [
|
|
89
|
+
// For backend clients only, a list of private keys to use for signing
|
|
90
|
+
// credentials. These keys MUST correspond to the public keys exposed on the
|
|
91
|
+
// "jwks_uri" of the client metadata.
|
|
92
|
+
await JoseKey.fromImportable(process.env.PRIVATE_KEY_1),
|
|
93
|
+
await JoseKey.fromImportable(process.env.PRIVATE_KEY_2),
|
|
94
|
+
await JoseKey.fromImportable(process.env.PRIVATE_KEY_3),
|
|
95
|
+
],
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const url = await client.authorize('foo.bsky.team', {
|
|
101
|
+
state: '434321',
|
|
102
|
+
prompt: 'consent',
|
|
103
|
+
scope: 'email',
|
|
104
|
+
ui_locales: 'fr',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Make user visit "url". Then, once it was redirected to the callback URI, call:
|
|
108
|
+
|
|
109
|
+
const params = new URLSearchParams('code=...&state=...')
|
|
110
|
+
const result = await client.callback(params)
|
|
111
|
+
|
|
112
|
+
// Verify the state (e.g. to link to an internal user)
|
|
113
|
+
result.state === '434321'
|
|
114
|
+
|
|
115
|
+
// The authenticated user's identifier
|
|
116
|
+
result.agent.sub
|
|
117
|
+
|
|
118
|
+
// Make an authenticated request to the server. New credentials will be
|
|
119
|
+
// automatically fetched if needed (causing sessionStore.set() to be called).
|
|
120
|
+
await result.agent.request('/xrpc/foo.bar')
|
|
121
|
+
|
|
122
|
+
// revoke credentials on the server (causing sessionStore.del() to be called)
|
|
123
|
+
await result.agent.signOut()
|
|
124
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,UAAU,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,YAAY,GAAG,OAAO,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Fetch, FetchContext } from '@atproto-labs/fetch';
|
|
2
|
+
import { SimpleStore } from '@atproto-labs/simple-store';
|
|
3
|
+
import { Key } from '@atproto/jwk';
|
|
4
|
+
export type DpopFetchWrapperOptions<C = FetchContext> = {
|
|
5
|
+
key: Key;
|
|
6
|
+
iss: string;
|
|
7
|
+
nonces: SimpleStore<string, string>;
|
|
8
|
+
supportedAlgs?: string[];
|
|
9
|
+
sha256?: (input: string) => Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Is the intended server an authorization server (true) or a resource server
|
|
12
|
+
* (false)? Setting this may allow to avoid parsing the response body to
|
|
13
|
+
* determine the dpop-nonce.
|
|
14
|
+
*
|
|
15
|
+
* @default undefined
|
|
16
|
+
*/
|
|
17
|
+
isAuthServer?: boolean;
|
|
18
|
+
fetch?: Fetch<C>;
|
|
19
|
+
};
|
|
20
|
+
export declare function dpopFetchWrapper<C = FetchContext>({ key, iss, supportedAlgs, nonces, sha256, isAuthServer, fetch, }: DpopFetchWrapperOptions<C>): Fetch<C>;
|
|
21
|
+
//# sourceMappingURL=fetch-dpop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-dpop.d.ts","sourceRoot":"","sources":["../src/fetch-dpop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAwB,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAUlC,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,YAAY,IAAI;IACtD,GAAG,EAAE,GAAG,CAAA;IACR,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAE3C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;CACjB,CAAA;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,YAAY,EAAE,EACjD,GAAG,EACH,GAAG,EACH,aAAa,EACb,MAAM,EACN,MAAiE,EACjE,YAAY,EACZ,KAAwB,GACzB,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAwGvC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dpopFetchWrapper = void 0;
|
|
4
|
+
const fetch_1 = require("@atproto-labs/fetch");
|
|
5
|
+
const base64_1 = require("multiformats/bases/base64");
|
|
6
|
+
// "undefined" in non https environments or environments without crypto
|
|
7
|
+
const subtle = globalThis.crypto?.subtle;
|
|
8
|
+
const ReadableStream = globalThis.ReadableStream;
|
|
9
|
+
function dpopFetchWrapper({ key, iss, supportedAlgs, nonces, sha256 = typeof subtle !== 'undefined' ? subtleSha256 : undefined, isAuthServer, fetch = globalThis.fetch, }) {
|
|
10
|
+
if (!sha256) {
|
|
11
|
+
throw new TypeError(`crypto.subtle is not available in this environment. Please provide a sha256 function.`);
|
|
12
|
+
}
|
|
13
|
+
const alg = negotiateAlg(key, supportedAlgs);
|
|
14
|
+
return async function (input, init) {
|
|
15
|
+
if (!key.algorithms.includes(alg)) {
|
|
16
|
+
throw new TypeError(`Key does not support the algorithm ${alg}`);
|
|
17
|
+
}
|
|
18
|
+
const request = init == null && input instanceof Request
|
|
19
|
+
? input
|
|
20
|
+
: new Request(input, init);
|
|
21
|
+
const authorizationHeader = request.headers.get('Authorization');
|
|
22
|
+
const ath = authorizationHeader?.startsWith('DPoP ')
|
|
23
|
+
? await sha256(authorizationHeader.slice(5))
|
|
24
|
+
: undefined;
|
|
25
|
+
const { method, url } = request;
|
|
26
|
+
const { origin } = new URL(url);
|
|
27
|
+
let initNonce;
|
|
28
|
+
try {
|
|
29
|
+
initNonce = await nonces.get(origin);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Ignore get errors, we will just not send a nonce
|
|
33
|
+
}
|
|
34
|
+
const initProof = await buildProof(key, alg, iss, method, url, initNonce, ath);
|
|
35
|
+
request.headers.set('DPoP', initProof);
|
|
36
|
+
const initResponse = await fetch.call(this, request);
|
|
37
|
+
// Make sure the response body is consumed. Either by the caller (when the
|
|
38
|
+
// response is returned), of if an error is thrown (catch block).
|
|
39
|
+
const nextNonce = initResponse.headers.get('DPoP-Nonce');
|
|
40
|
+
if (!nextNonce || nextNonce === initNonce) {
|
|
41
|
+
// No nonce was returned or it is the same as the one we sent. No need to
|
|
42
|
+
// update the nonce store, or retry the request.
|
|
43
|
+
return initResponse;
|
|
44
|
+
}
|
|
45
|
+
// Store the fresh nonce for future requests
|
|
46
|
+
try {
|
|
47
|
+
await nonces.set(origin, nextNonce);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignore set errors
|
|
51
|
+
}
|
|
52
|
+
const shouldRetry = await isUseDpopNonceError(initResponse, isAuthServer);
|
|
53
|
+
if (!shouldRetry) {
|
|
54
|
+
// Not a "use_dpop_nonce" error, so there is no need to retry
|
|
55
|
+
return initResponse;
|
|
56
|
+
}
|
|
57
|
+
// If the input stream was already consumed, we cannot retry the request. A
|
|
58
|
+
// solution would be to clone() the request but that would bufferize the
|
|
59
|
+
// entire stream in memory which can lead to memory starvation. Instead, we
|
|
60
|
+
// will return the original response and let the calling code handle retries.
|
|
61
|
+
if (input === request) {
|
|
62
|
+
// The input request body was consumed. We cannot retry the request.
|
|
63
|
+
return initResponse;
|
|
64
|
+
}
|
|
65
|
+
if (ReadableStream && init?.body instanceof ReadableStream) {
|
|
66
|
+
// The init body was consumed. We cannot retry the request.
|
|
67
|
+
return initResponse;
|
|
68
|
+
}
|
|
69
|
+
// We will now retry the request with the fresh nonce.
|
|
70
|
+
// The initial response body must be consumed (see cancelBody's doc).
|
|
71
|
+
await (0, fetch_1.cancelBody)(initResponse, 'log');
|
|
72
|
+
const nextProof = await buildProof(key, alg, iss, method, url, nextNonce, ath);
|
|
73
|
+
const nextRequest = new Request(input, init);
|
|
74
|
+
nextRequest.headers.set('DPoP', nextProof);
|
|
75
|
+
return fetch.call(this, nextRequest);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
exports.dpopFetchWrapper = dpopFetchWrapper;
|
|
79
|
+
async function buildProof(key, alg, iss, htm, htu, nonce, ath) {
|
|
80
|
+
if (!key.bareJwk) {
|
|
81
|
+
throw new Error('Only asymmetric keys can be used as DPoP proofs');
|
|
82
|
+
}
|
|
83
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
84
|
+
return key.createJwt({
|
|
85
|
+
alg,
|
|
86
|
+
typ: 'dpop+jwt',
|
|
87
|
+
jwk: key.bareJwk,
|
|
88
|
+
}, {
|
|
89
|
+
iss,
|
|
90
|
+
iat: now,
|
|
91
|
+
exp: now + 10,
|
|
92
|
+
// Any collision will cause the request to be rejected by the server. no biggie.
|
|
93
|
+
jti: Math.random().toString(36).slice(2),
|
|
94
|
+
htm,
|
|
95
|
+
htu,
|
|
96
|
+
nonce,
|
|
97
|
+
ath,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function isUseDpopNonceError(response, isAuthServer) {
|
|
101
|
+
// https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
|
102
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#name-resource-server-provided-no
|
|
103
|
+
if (isAuthServer === undefined || isAuthServer === false) {
|
|
104
|
+
if (response.status === 401) {
|
|
105
|
+
const wwwAuth = response.headers.get('WWW-Authenticate');
|
|
106
|
+
if (wwwAuth?.startsWith('DPoP')) {
|
|
107
|
+
return wwwAuth.includes('error="use_dpop_nonce"');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#name-authorization-server-provid
|
|
112
|
+
if (isAuthServer === undefined || isAuthServer === true) {
|
|
113
|
+
if (response.status === 400) {
|
|
114
|
+
try {
|
|
115
|
+
const json = await (0, fetch_1.peekJson)(response, 10 * 1024);
|
|
116
|
+
return typeof json === 'object' && json?.['error'] === 'use_dpop_nonce';
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Response too big (to be "use_dpop_nonce" error) or invalid JSON
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
function negotiateAlg(key, supportedAlgs) {
|
|
127
|
+
if (supportedAlgs) {
|
|
128
|
+
// Use order of supportedAlgs as preference
|
|
129
|
+
const alg = supportedAlgs.find((a) => key.algorithms.includes(a));
|
|
130
|
+
if (alg)
|
|
131
|
+
return alg;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const [alg] = key.algorithms;
|
|
135
|
+
if (alg)
|
|
136
|
+
return alg;
|
|
137
|
+
}
|
|
138
|
+
throw new Error('Key does not match any alg supported by the server');
|
|
139
|
+
}
|
|
140
|
+
async function subtleSha256(input) {
|
|
141
|
+
if (subtle == null) {
|
|
142
|
+
throw new Error(`crypto.subtle is not available in this environment. Please provide a sha256 function.`);
|
|
143
|
+
}
|
|
144
|
+
const bytes = new TextEncoder().encode(input);
|
|
145
|
+
const digest = await subtle.digest('SHA-256', bytes);
|
|
146
|
+
const digestBytes = new Uint8Array(digest);
|
|
147
|
+
return base64_1.base64url.baseEncode(digestBytes);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=fetch-dpop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-dpop.js","sourceRoot":"","sources":["../src/fetch-dpop.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { FetchError, FetchRequestError, FetchResponseError, } from '@atproto-labs/fetch';
|
|
2
|
+
export * from './oauth-agent.js';
|
|
3
|
+
export * from './oauth-authorization-server-metadata-resolver.js';
|
|
4
|
+
export * from './oauth-callback-error.js';
|
|
5
|
+
export * from './oauth-client.js';
|
|
6
|
+
export * from './oauth-protected-resource-metadata-resolver.js';
|
|
7
|
+
export * from './oauth-resolver-error.js';
|
|
8
|
+
export * from './oauth-response-error.js';
|
|
9
|
+
export * from './oauth-server-agent.js';
|
|
10
|
+
export * from './oauth-server-factory.js';
|
|
11
|
+
export * from './refresh-error.js';
|
|
12
|
+
export * from './runtime-implementation.js';
|
|
13
|
+
export * from './session-getter.js';
|
|
14
|
+
export * from './types.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,mDAAmD,CAAA;AACjE,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,iDAAiD,CAAA;AAC/D,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,oBAAoB,CAAA;AAClC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,qBAAqB,CAAA;AACnC,cAAc,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.FetchResponseError = exports.FetchRequestError = exports.FetchError = void 0;
|
|
18
|
+
var fetch_1 = require("@atproto-labs/fetch");
|
|
19
|
+
Object.defineProperty(exports, "FetchError", { enumerable: true, get: function () { return fetch_1.FetchError; } });
|
|
20
|
+
Object.defineProperty(exports, "FetchRequestError", { enumerable: true, get: function () { return fetch_1.FetchRequestError; } });
|
|
21
|
+
Object.defineProperty(exports, "FetchResponseError", { enumerable: true, get: function () { return fetch_1.FetchResponseError; } });
|
|
22
|
+
__exportStar(require("./oauth-agent.js"), exports);
|
|
23
|
+
__exportStar(require("./oauth-authorization-server-metadata-resolver.js"), exports);
|
|
24
|
+
__exportStar(require("./oauth-callback-error.js"), exports);
|
|
25
|
+
__exportStar(require("./oauth-client.js"), exports);
|
|
26
|
+
__exportStar(require("./oauth-protected-resource-metadata-resolver.js"), exports);
|
|
27
|
+
__exportStar(require("./oauth-resolver-error.js"), exports);
|
|
28
|
+
__exportStar(require("./oauth-response-error.js"), exports);
|
|
29
|
+
__exportStar(require("./oauth-server-agent.js"), exports);
|
|
30
|
+
__exportStar(require("./oauth-server-factory.js"), exports);
|
|
31
|
+
__exportStar(require("./refresh-error.js"), exports);
|
|
32
|
+
__exportStar(require("./runtime-implementation.js"), exports);
|
|
33
|
+
__exportStar(require("./session-getter.js"), exports);
|
|
34
|
+
__exportStar(require("./types.js"), exports);
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAI4B;AAH1B,mGAAA,UAAU,OAAA;AACV,0GAAA,iBAAiB,OAAA;AACjB,2GAAA,kBAAkB,OAAA;AAEpB,mDAAgC;AAChC,oFAAiE;AACjE,4DAAyC;AACzC,oDAAiC;AACjC,kFAA+D;AAC/D,4DAAyC;AACzC,4DAAyC;AACzC,0DAAuC;AACvC,4DAAyC;AACzC,qDAAkC;AAClC,8DAA2C;AAC3C,sDAAmC;AACnC,6CAA0B"}
|
package/dist/lock.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"AAsBA,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,GAC3B,OAAO,CAAC,CAAC,CAAC,CAQZ"}
|
package/dist/lock.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestLocalLock = void 0;
|
|
4
|
+
const locks = new Map();
|
|
5
|
+
function acquireLocalLock(name) {
|
|
6
|
+
return new Promise((resolveAcquire) => {
|
|
7
|
+
const prev = locks.get(name) ?? Promise.resolve();
|
|
8
|
+
const next = prev.then(() => {
|
|
9
|
+
return new Promise((resolveRelease) => {
|
|
10
|
+
const release = () => {
|
|
11
|
+
// Only delete the lock if it is still the current one
|
|
12
|
+
if (locks.get(name) === next)
|
|
13
|
+
locks.delete(name);
|
|
14
|
+
resolveRelease();
|
|
15
|
+
};
|
|
16
|
+
resolveAcquire(release);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
locks.set(name, next);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function requestLocalLock(name, fn) {
|
|
23
|
+
return acquireLocalLock(name).then(async (release) => {
|
|
24
|
+
try {
|
|
25
|
+
return await fn();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
release();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
exports.requestLocalLock = requestLocalLock;
|
|
33
|
+
//# sourceMappingURL=lock.js.map
|
package/dist/lock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":";;;AAAA,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAA;AAE/C,SAAS,gBAAgB,CAAC,IAAa;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YAC1B,OAAO,IAAI,OAAO,CAAO,CAAC,cAAc,EAAE,EAAE;gBAC1C,MAAM,OAAO,GAAG,GAAG,EAAE;oBACnB,sDAAsD;oBACtD,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI;wBAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;oBAEhD,cAAc,EAAE,CAAA;gBAClB,CAAC,CAAA;gBAED,cAAc,CAAC,OAAO,CAAC,CAAA;YACzB,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAgB,gBAAgB,CAC9B,IAAY,EACZ,EAA4B;IAE5B,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACnD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAXD,4CAWC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fetch } from '@atproto-labs/fetch';
|
|
2
|
+
import { JwtPayload } from '@atproto/jwk';
|
|
3
|
+
import { OAuthAuthorizationServerMetadata } from '@atproto/oauth-types';
|
|
4
|
+
import { OAuthServerAgent, TokenSet } from './oauth-server-agent.js';
|
|
5
|
+
import { SessionGetter } from './session-getter.js';
|
|
6
|
+
export declare class OAuthAgent {
|
|
7
|
+
readonly server: OAuthServerAgent;
|
|
8
|
+
readonly sub: string;
|
|
9
|
+
private readonly sessionGetter;
|
|
10
|
+
protected dpopFetch: Fetch<unknown>;
|
|
11
|
+
constructor(server: OAuthServerAgent, sub: string, sessionGetter: SessionGetter, fetch?: Fetch);
|
|
12
|
+
get serverMetadata(): Readonly<OAuthAuthorizationServerMetadata>;
|
|
13
|
+
refreshIfNeeded(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* @param refresh See {@link SessionGetter.getSession}
|
|
16
|
+
*/
|
|
17
|
+
protected getTokenSet(refresh?: boolean): Promise<TokenSet>;
|
|
18
|
+
getInfo(): Promise<{
|
|
19
|
+
userinfo?: JwtPayload;
|
|
20
|
+
expired?: boolean;
|
|
21
|
+
scope?: string;
|
|
22
|
+
iss: string;
|
|
23
|
+
aud: string;
|
|
24
|
+
sub: string;
|
|
25
|
+
}>;
|
|
26
|
+
signOut(): Promise<void>;
|
|
27
|
+
request(pathname: string, init?: RequestInit): Promise<Response>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=oauth-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-agent.d.ts","sourceRoot":"","sources":["../src/oauth-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAmB,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAA;AAGvE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAMnD,qBAAa,UAAU;aAIH,MAAM,EAAE,gBAAgB;aACxB,GAAG,EAAE,MAAM;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa;IALhC,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;gBAGjB,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,MAAM,EACV,aAAa,EAAE,aAAa,EAC7C,KAAK,GAAE,KAAwB;IAajC,IAAI,cAAc,IAAI,QAAQ,CAAC,gCAAgC,CAAC,CAE/D;IAEY,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7C;;OAEG;cACa,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAK3D,OAAO,IAAI,OAAO,CAAC;QACvB,QAAQ,CAAC,EAAE,UAAU,CAAA;QACrB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;KACZ,CAAC;IAkBI,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IASxB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;CAqDvE"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthAgent = void 0;
|
|
4
|
+
const fetch_1 = require("@atproto-labs/fetch");
|
|
5
|
+
const jwk_1 = require("@atproto/jwk");
|
|
6
|
+
const fetch_dpop_js_1 = require("./fetch-dpop.js");
|
|
7
|
+
const ReadableStream = globalThis.ReadableStream;
|
|
8
|
+
class OAuthAgent {
|
|
9
|
+
constructor(server, sub, sessionGetter, fetch = globalThis.fetch) {
|
|
10
|
+
Object.defineProperty(this, "server", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: server
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "sub", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: sub
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(this, "sessionGetter", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: sessionGetter
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "dpopFetch", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
});
|
|
34
|
+
this.dpopFetch = (0, fetch_dpop_js_1.dpopFetchWrapper)({
|
|
35
|
+
fetch: (0, fetch_1.bindFetch)(fetch),
|
|
36
|
+
iss: server.clientMetadata.client_id,
|
|
37
|
+
key: server.dpopKey,
|
|
38
|
+
supportedAlgs: server.serverMetadata.dpop_signing_alg_values_supported,
|
|
39
|
+
sha256: async (v) => server.runtime.sha256(v),
|
|
40
|
+
nonces: server.dpopNonces,
|
|
41
|
+
isAuthServer: false,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
get serverMetadata() {
|
|
45
|
+
return this.server.serverMetadata;
|
|
46
|
+
}
|
|
47
|
+
async refreshIfNeeded() {
|
|
48
|
+
await this.getTokenSet(undefined);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @param refresh See {@link SessionGetter.getSession}
|
|
52
|
+
*/
|
|
53
|
+
async getTokenSet(refresh) {
|
|
54
|
+
const { tokenSet } = await this.sessionGetter.getSession(this.sub, refresh);
|
|
55
|
+
return tokenSet;
|
|
56
|
+
}
|
|
57
|
+
async getInfo() {
|
|
58
|
+
const tokenSet = await this.getTokenSet();
|
|
59
|
+
return {
|
|
60
|
+
userinfo: tokenSet.id_token
|
|
61
|
+
? (0, jwk_1.unsafeDecodeJwt)(tokenSet.id_token).payload
|
|
62
|
+
: undefined,
|
|
63
|
+
expired: tokenSet.expires_at == null
|
|
64
|
+
? undefined
|
|
65
|
+
: new Date(tokenSet.expires_at).getTime() < Date.now() - 5e3,
|
|
66
|
+
scope: tokenSet.scope,
|
|
67
|
+
iss: tokenSet.iss,
|
|
68
|
+
aud: tokenSet.aud,
|
|
69
|
+
sub: tokenSet.sub,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async signOut() {
|
|
73
|
+
try {
|
|
74
|
+
const { tokenSet } = await this.sessionGetter.getSession(this.sub, false);
|
|
75
|
+
await this.server.revoke(tokenSet.access_token);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await this.sessionGetter.delStored(this.sub);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async request(pathname, init) {
|
|
82
|
+
// This will try and refresh the token if it is known to be expired
|
|
83
|
+
const tokenSet = await this.getTokenSet(undefined);
|
|
84
|
+
const initialUrl = new URL(pathname, tokenSet.aud);
|
|
85
|
+
const initialAuth = `${tokenSet.token_type} ${tokenSet.access_token}`;
|
|
86
|
+
const headers = new Headers(init?.headers);
|
|
87
|
+
headers.set('Authorization', initialAuth);
|
|
88
|
+
const initialResponse = await this.dpopFetch(initialUrl, {
|
|
89
|
+
...init,
|
|
90
|
+
headers,
|
|
91
|
+
});
|
|
92
|
+
// If the token is not expired, we don't need to refresh it
|
|
93
|
+
if (!isTokenExpiredResponse(initialResponse)) {
|
|
94
|
+
return initialResponse;
|
|
95
|
+
}
|
|
96
|
+
let tokenSetFresh;
|
|
97
|
+
try {
|
|
98
|
+
// "true" here will cause the token to be refreshed
|
|
99
|
+
tokenSetFresh = await this.getTokenSet(true);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return initialResponse;
|
|
103
|
+
}
|
|
104
|
+
// The stream was already consumed. We cannot retry the request. A solution
|
|
105
|
+
// would be to tee() the input stream but that would bufferize the entire
|
|
106
|
+
// stream in memory which can lead to memory starvation. Instead, we will
|
|
107
|
+
// return the original response and let the calling code handle retries.
|
|
108
|
+
if (ReadableStream && init?.body instanceof ReadableStream) {
|
|
109
|
+
return initialResponse;
|
|
110
|
+
}
|
|
111
|
+
const finalAuth = `${tokenSetFresh.token_type} ${tokenSetFresh.access_token}`;
|
|
112
|
+
const finalUrl = new URL(pathname, tokenSetFresh.aud);
|
|
113
|
+
headers.set('Authorization', finalAuth);
|
|
114
|
+
const finalResponse = await this.dpopFetch(finalUrl, { ...init, headers });
|
|
115
|
+
// There is no need to keep the session in the store if the token is expired
|
|
116
|
+
// and there is no way to refresh it.
|
|
117
|
+
if (isTokenExpiredResponse(finalResponse)) {
|
|
118
|
+
// TODO: Is there a "softer" way to handle this, e.g. by marking the
|
|
119
|
+
// session as "expired" and allow the user to trigger a new login?
|
|
120
|
+
await this.sessionGetter.delStored(this.sub);
|
|
121
|
+
}
|
|
122
|
+
return finalResponse;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.OAuthAgent = OAuthAgent;
|
|
126
|
+
/**
|
|
127
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3}
|
|
128
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc9449#name-resource-server-provided-no}
|
|
129
|
+
*/
|
|
130
|
+
function isTokenExpiredResponse(response) {
|
|
131
|
+
if (response.status !== 401)
|
|
132
|
+
return false;
|
|
133
|
+
const wwwAuth = response.headers.get('WWW-Authenticate');
|
|
134
|
+
return (wwwAuth != null &&
|
|
135
|
+
(wwwAuth.startsWith('Bearer ') || wwwAuth.startsWith('DPoP ')) &&
|
|
136
|
+
wwwAuth.includes('error="invalid_token"'));
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=oauth-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-agent.js","sourceRoot":"","sources":["../src/oauth-agent.ts"],"names":[],"mappings":"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"}
|