@atcute/oauth-browser-client 1.0.25 → 2.0.0-next.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/LICENSE +12 -15
- package/README.md +35 -222
- package/dist/agents/exchange.d.ts +10 -5
- package/dist/agents/exchange.js +13 -1
- package/dist/agents/exchange.js.map +1 -1
- package/dist/agents/server-agent.js +3 -3
- package/dist/agents/server-agent.js.map +1 -1
- package/dist/agents/sessions.js.map +1 -1
- package/dist/agents/user-agent.js +1 -2
- package/dist/agents/user-agent.js.map +1 -1
- package/dist/dpop.d.ts +2 -2
- package/dist/dpop.js +20 -10
- package/dist/dpop.js.map +1 -1
- package/dist/environment.d.ts +9 -2
- package/dist/environment.js +3 -0
- package/dist/environment.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/resolvers.d.ts +3 -45
- package/dist/resolvers.js +36 -118
- package/dist/resolvers.js.map +1 -1
- package/lib/agents/exchange.ts +22 -7
- package/lib/agents/server-agent.ts +3 -3
- package/lib/agents/sessions.ts +5 -5
- package/lib/agents/user-agent.ts +1 -2
- package/lib/dpop.ts +30 -18
- package/lib/environment.ts +14 -2
- package/lib/index.ts +0 -1
- package/lib/resolvers.ts +42 -139
- package/package.json +6 -5
package/dist/environment.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../lib/environment.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../lib/environment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,eAAe,CAAC;AAExE,MAAM,CAAC,IAAI,SAAiB,CAAC;AAC7B,MAAM,CAAC,IAAI,YAAoB,CAAC;AAEhC,MAAM,CAAC,IAAI,QAAuB,CAAC;AAEnC,MAAM,CAAC,IAAI,cAA8B,CAAC;AAC1C,MAAM,CAAC,IAAI,mBAAwC,CAAC;AAuBpD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAA8B,EAAE,EAAE;IAChE,CAAC,EAAE,cAAc,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,CAAC;IACpD,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1E,QAAQ,GAAG,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,IAAI,cAAc,EAAE,CAAC,CAAC;AACjF,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA8B,MAAM,kBAAkB,CAAC;AAE9E,cAAc,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA8B,MAAM,kBAAkB,CAAC;AAE9E,cAAc,aAAa,CAAC;AAE5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
|
package/dist/resolvers.d.ts
CHANGED
|
@@ -1,52 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Did } from '@atcute/lexicons';
|
|
1
|
+
import type { ActorIdentifier } from '@atcute/lexicons';
|
|
3
2
|
import type { IdentityMetadata } from './types/identity.js';
|
|
4
|
-
import type { AuthorizationServerMetadata
|
|
5
|
-
|
|
6
|
-
* Resolves domain handles into DID identifiers, by requesting Bluesky's AppView
|
|
7
|
-
* for identity resolution.
|
|
8
|
-
* @param handle Domain handle to resolve
|
|
9
|
-
* @returns DID identifier resolved from the domain handle
|
|
10
|
-
*/
|
|
11
|
-
export declare const resolveHandle: (handle: string) => Promise<Did>;
|
|
12
|
-
/**
|
|
13
|
-
* Get DID documents of did:plc (via plc.directory) and did:web identifiers
|
|
14
|
-
* @param did DID identifier we're seeking DID doc from
|
|
15
|
-
* @returns Retrieved DID document
|
|
16
|
-
*/
|
|
17
|
-
export declare const getDidDocument: (did: Did) => Promise<DidDocument>;
|
|
18
|
-
/**
|
|
19
|
-
* Get OAuth protected resource metadata from a host
|
|
20
|
-
* @param host URL of the host
|
|
21
|
-
* @returns Retrieved protected resource metadata
|
|
22
|
-
*/
|
|
23
|
-
export declare const getProtectedResourceMetadata: (host: string) => Promise<ProtectedResourceMetadata>;
|
|
24
|
-
/**
|
|
25
|
-
* Get OAuth authorization server metadata from a host
|
|
26
|
-
* @param host URL of the host
|
|
27
|
-
* @returns Retrieved authorization server metadata
|
|
28
|
-
*/
|
|
29
|
-
export declare const getAuthorizationServerMetadata: (host: string) => Promise<AuthorizationServerMetadata>;
|
|
30
|
-
/**
|
|
31
|
-
* Resolve handle domains or DID identifiers to get their PDS and its authorization server metadata
|
|
32
|
-
* @param ident Handle domain or DID identifier to resolve
|
|
33
|
-
* @returns Resolved PDS and authorization server metadata
|
|
34
|
-
*/
|
|
35
|
-
export declare const resolveFromIdentity: (ident: string) => Promise<{
|
|
3
|
+
import type { AuthorizationServerMetadata } from './types/server.js';
|
|
4
|
+
export declare const resolveFromIdentifier: (ident: ActorIdentifier) => Promise<{
|
|
36
5
|
identity: IdentityMetadata;
|
|
37
6
|
metadata: AuthorizationServerMetadata;
|
|
38
7
|
}>;
|
|
39
|
-
/**
|
|
40
|
-
* Request authorization server metadata from a PDS
|
|
41
|
-
* @param host URL of the host
|
|
42
|
-
* @returns Resolved authorization server metadata
|
|
43
|
-
*/
|
|
44
8
|
export declare const resolveFromService: (host: string) => Promise<{
|
|
45
9
|
metadata: AuthorizationServerMetadata;
|
|
46
10
|
}>;
|
|
47
|
-
/**
|
|
48
|
-
* Request authorization server metadata from its protected resource metadata
|
|
49
|
-
* @param input URL of the host whose authorization server is delegated
|
|
50
|
-
* @returns Resolved authorization server metadata
|
|
51
|
-
*/
|
|
52
|
-
export declare const getMetadataFromResourceServer: (input: string) => Promise<AuthorizationServerMetadata>;
|
package/dist/resolvers.js
CHANGED
|
@@ -1,72 +1,49 @@
|
|
|
1
1
|
import { getPdsEndpoint } from '@atcute/identity';
|
|
2
2
|
import { isDid } from '@atcute/lexicons/syntax';
|
|
3
|
-
import {
|
|
3
|
+
import { didDocumentResolver, handleResolver } from './environment.js';
|
|
4
4
|
import { ResolverError } from './errors.js';
|
|
5
5
|
import { extractContentType } from './utils/response.js';
|
|
6
6
|
import { isValidUrl } from './utils/strings.js';
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @param handle Domain handle to resolve
|
|
12
|
-
* @returns DID identifier resolved from the domain handle
|
|
13
|
-
*/
|
|
14
|
-
export const resolveHandle = async (handle) => {
|
|
15
|
-
const url = DEFAULT_APPVIEW_URL + `/xrpc/com.atproto.identity.resolveHandle` + `?handle=${handle}`;
|
|
16
|
-
const response = await fetch(url);
|
|
17
|
-
if (response.status === 400) {
|
|
18
|
-
throw new ResolverError(`domain handle not found`);
|
|
7
|
+
export const resolveFromIdentifier = async (ident) => {
|
|
8
|
+
let did;
|
|
9
|
+
if (isDid(ident)) {
|
|
10
|
+
did = ident;
|
|
19
11
|
}
|
|
20
|
-
else
|
|
21
|
-
|
|
12
|
+
else {
|
|
13
|
+
const resolved = await handleResolver.resolve(ident);
|
|
14
|
+
did = resolved;
|
|
15
|
+
}
|
|
16
|
+
const doc = await didDocumentResolver.resolve(did);
|
|
17
|
+
const pds = getPdsEndpoint(doc);
|
|
18
|
+
if (!pds) {
|
|
19
|
+
throw new ResolverError(`missing pds endpoint`);
|
|
22
20
|
}
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
return {
|
|
22
|
+
identity: {
|
|
23
|
+
id: did,
|
|
24
|
+
raw: ident,
|
|
25
|
+
pds: new URL(pds),
|
|
26
|
+
},
|
|
27
|
+
metadata: await getMetadataFromResourceServer(pds),
|
|
28
|
+
};
|
|
25
29
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
export const getDidDocument = async (did) => {
|
|
32
|
-
const colon_index = did.indexOf(':', 4);
|
|
33
|
-
const type = did.slice(4, colon_index);
|
|
34
|
-
const ident = did.slice(colon_index + 1);
|
|
35
|
-
// 2. retrieve their DID documents
|
|
36
|
-
let doc;
|
|
37
|
-
if (type === 'plc') {
|
|
38
|
-
const response = await fetch(`https://plc.directory/${did}`);
|
|
39
|
-
if (response.status === 404) {
|
|
40
|
-
throw new ResolverError(`did not found in directory`);
|
|
41
|
-
}
|
|
42
|
-
else if (!response.ok) {
|
|
43
|
-
throw new ResolverError(`directory is unreachable`);
|
|
44
|
-
}
|
|
45
|
-
const json = await response.json();
|
|
46
|
-
doc = json;
|
|
30
|
+
export const resolveFromService = async (host) => {
|
|
31
|
+
try {
|
|
32
|
+
const metadata = await getMetadataFromResourceServer(host);
|
|
33
|
+
return { metadata };
|
|
47
34
|
}
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err instanceof ResolverError) {
|
|
37
|
+
try {
|
|
38
|
+
const metadata = await getAuthorizationServerMetadata(host);
|
|
39
|
+
return { metadata };
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
55
42
|
}
|
|
56
|
-
|
|
57
|
-
doc = json;
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
throw new ResolverError(`unsupported did method`);
|
|
43
|
+
throw err;
|
|
61
44
|
}
|
|
62
|
-
return doc;
|
|
63
45
|
};
|
|
64
|
-
|
|
65
|
-
* Get OAuth protected resource metadata from a host
|
|
66
|
-
* @param host URL of the host
|
|
67
|
-
* @returns Retrieved protected resource metadata
|
|
68
|
-
*/
|
|
69
|
-
export const getProtectedResourceMetadata = async (host) => {
|
|
46
|
+
const getProtectedResourceMetadata = async (host) => {
|
|
70
47
|
const url = new URL(`/.well-known/oauth-protected-resource`, host);
|
|
71
48
|
const response = await fetch(url, {
|
|
72
49
|
redirect: 'manual',
|
|
@@ -83,12 +60,7 @@ export const getProtectedResourceMetadata = async (host) => {
|
|
|
83
60
|
}
|
|
84
61
|
return metadata;
|
|
85
62
|
};
|
|
86
|
-
|
|
87
|
-
* Get OAuth authorization server metadata from a host
|
|
88
|
-
* @param host URL of the host
|
|
89
|
-
* @returns Retrieved authorization server metadata
|
|
90
|
-
*/
|
|
91
|
-
export const getAuthorizationServerMetadata = async (host) => {
|
|
63
|
+
const getAuthorizationServerMetadata = async (host) => {
|
|
92
64
|
const url = new URL(`/.well-known/oauth-authorization-server`, host);
|
|
93
65
|
const response = await fetch(url, {
|
|
94
66
|
redirect: 'manual',
|
|
@@ -119,61 +91,7 @@ export const getAuthorizationServerMetadata = async (host) => {
|
|
|
119
91
|
}
|
|
120
92
|
return metadata;
|
|
121
93
|
};
|
|
122
|
-
|
|
123
|
-
* Resolve handle domains or DID identifiers to get their PDS and its authorization server metadata
|
|
124
|
-
* @param ident Handle domain or DID identifier to resolve
|
|
125
|
-
* @returns Resolved PDS and authorization server metadata
|
|
126
|
-
*/
|
|
127
|
-
export const resolveFromIdentity = async (ident) => {
|
|
128
|
-
let did;
|
|
129
|
-
if (isDid(ident)) {
|
|
130
|
-
did = ident;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
const resolved = await resolveHandle(ident);
|
|
134
|
-
did = resolved;
|
|
135
|
-
}
|
|
136
|
-
const doc = await getDidDocument(did);
|
|
137
|
-
const pds = getPdsEndpoint(doc);
|
|
138
|
-
if (!pds) {
|
|
139
|
-
throw new ResolverError(`missing pds endpoint`);
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
identity: {
|
|
143
|
-
id: did,
|
|
144
|
-
raw: ident,
|
|
145
|
-
pds: new URL(pds),
|
|
146
|
-
},
|
|
147
|
-
metadata: await getMetadataFromResourceServer(pds),
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Request authorization server metadata from a PDS
|
|
152
|
-
* @param host URL of the host
|
|
153
|
-
* @returns Resolved authorization server metadata
|
|
154
|
-
*/
|
|
155
|
-
export const resolveFromService = async (host) => {
|
|
156
|
-
try {
|
|
157
|
-
const metadata = await getMetadataFromResourceServer(host);
|
|
158
|
-
return { metadata };
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
if (err instanceof ResolverError) {
|
|
162
|
-
try {
|
|
163
|
-
const metadata = await getAuthorizationServerMetadata(host);
|
|
164
|
-
return { metadata };
|
|
165
|
-
}
|
|
166
|
-
catch { }
|
|
167
|
-
}
|
|
168
|
-
throw err;
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
/**
|
|
172
|
-
* Request authorization server metadata from its protected resource metadata
|
|
173
|
-
* @param input URL of the host whose authorization server is delegated
|
|
174
|
-
* @returns Resolved authorization server metadata
|
|
175
|
-
*/
|
|
176
|
-
export const getMetadataFromResourceServer = async (input) => {
|
|
94
|
+
const getMetadataFromResourceServer = async (input) => {
|
|
177
95
|
const rs_metadata = await getProtectedResourceMetadata(input);
|
|
178
96
|
if (rs_metadata.authorization_servers?.length !== 1) {
|
|
179
97
|
throw new ResolverError(`expected exactly one authorization server in the listing`);
|
package/dist/resolvers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.js","sourceRoot":"","sources":["../lib/resolvers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resolvers.js","sourceRoot":"","sources":["../lib/resolvers.ts"],"names":[],"mappings":"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"}
|
package/lib/agents/exchange.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
2
|
|
|
3
|
+
import type { ActorIdentifier } from '@atcute/lexicons';
|
|
4
|
+
|
|
3
5
|
import { createES256Key } from '../dpop.js';
|
|
4
6
|
import { CLIENT_ID, database, REDIRECT_URI } from '../environment.js';
|
|
5
7
|
import { AuthorizationError, LoginError } from '../errors.js';
|
|
@@ -8,12 +10,16 @@ import type { AuthorizationServerMetadata } from '../types/server.js';
|
|
|
8
10
|
import type { Session } from '../types/token.js';
|
|
9
11
|
import { generatePKCE } from '../utils/runtime.js';
|
|
10
12
|
|
|
13
|
+
import { resolveFromIdentifier, resolveFromService } from '../resolvers.js';
|
|
11
14
|
import { OAuthServerAgent } from './server-agent.js';
|
|
12
15
|
import { storeSession } from './sessions.js';
|
|
13
16
|
|
|
17
|
+
export type AuthorizeTargetOptions =
|
|
18
|
+
| { type: 'account'; identifier: ActorIdentifier }
|
|
19
|
+
| { type: 'pds'; serviceUrl: string };
|
|
20
|
+
|
|
14
21
|
export interface AuthorizeOptions {
|
|
15
|
-
|
|
16
|
-
identity?: IdentityMetadata;
|
|
22
|
+
target: AuthorizeTargetOptions;
|
|
17
23
|
scope: string;
|
|
18
24
|
}
|
|
19
25
|
|
|
@@ -22,11 +28,20 @@ export interface AuthorizeOptions {
|
|
|
22
28
|
* @param options
|
|
23
29
|
* @returns URL to redirect the user for authorization
|
|
24
30
|
*/
|
|
25
|
-
export const createAuthorizationUrl = async ({
|
|
26
|
-
metadata
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
export const createAuthorizationUrl = async ({ target, scope }: AuthorizeOptions): Promise<URL> => {
|
|
32
|
+
let resolved: { identity?: IdentityMetadata; metadata: AuthorizationServerMetadata };
|
|
33
|
+
switch (target.type) {
|
|
34
|
+
case 'account': {
|
|
35
|
+
resolved = await resolveFromIdentifier(target.identifier);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case 'pds': {
|
|
39
|
+
resolved = await resolveFromService(target.serviceUrl);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { identity, metadata } = resolved;
|
|
44
|
+
|
|
30
45
|
const state = nanoid(24);
|
|
31
46
|
|
|
32
47
|
const pkce = await generatePKCE();
|
|
@@ -3,7 +3,7 @@ import type { Did } from '@atcute/lexicons';
|
|
|
3
3
|
import { createDPoPFetch } from '../dpop.js';
|
|
4
4
|
import { CLIENT_ID, REDIRECT_URI } from '../environment.js';
|
|
5
5
|
import { FetchResponseError, OAuthResponseError, TokenRefreshError } from '../errors.js';
|
|
6
|
-
import {
|
|
6
|
+
import { resolveFromIdentifier } from '../resolvers.js';
|
|
7
7
|
import type { DPoPKey } from '../types/dpop.js';
|
|
8
8
|
import type { OAuthParResponse } from '../types/par.js';
|
|
9
9
|
import type { PersistedAuthorizationServerMetadata } from '../types/server.js';
|
|
@@ -17,7 +17,7 @@ export class OAuthServerAgent {
|
|
|
17
17
|
|
|
18
18
|
constructor(metadata: PersistedAuthorizationServerMetadata, dpopKey: DPoPKey) {
|
|
19
19
|
this.#metadata = metadata;
|
|
20
|
-
this.#fetch = createDPoPFetch(
|
|
20
|
+
this.#fetch = createDPoPFetch(dpopKey, true);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
async request(
|
|
@@ -124,7 +124,7 @@ export class OAuthServerAgent {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
const token = this.#processTokenResponse(res);
|
|
127
|
-
const resolved = await
|
|
127
|
+
const resolved = await resolveFromIdentifier(sub as Did);
|
|
128
128
|
|
|
129
129
|
if (resolved.metadata.issuer !== this.#metadata.issuer) {
|
|
130
130
|
throw new TypeError(`issuer mismatch; got ${resolved.metadata.issuer}`);
|
package/lib/agents/sessions.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface SessionGetOptions {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
type PendingItem<V> = Promise<{ value: V; isFresh: boolean }>;
|
|
17
|
-
const pending = new Map<Did, PendingItem<Session
|
|
17
|
+
const pending = new Map<Did, Promise<PendingItem<Session>>>();
|
|
18
18
|
|
|
19
19
|
export const getSession = async (sub: Did, options?: SessionGetOptions): Promise<Session> => {
|
|
20
20
|
options?.signal?.throwIfAborted();
|
|
@@ -32,7 +32,7 @@ export const getSession = async (sub: Did, options?: SessionGetOptions): Promise
|
|
|
32
32
|
// pending.set() call. Because of the "single threaded" nature of
|
|
33
33
|
// JavaScript, the pending item will be set before the next iteration of the
|
|
34
34
|
// while loop.
|
|
35
|
-
let previousExecutionFlow: PendingItem<Session
|
|
35
|
+
let previousExecutionFlow: Promise<PendingItem<Session>> | undefined;
|
|
36
36
|
while ((previousExecutionFlow = pending.get(sub))) {
|
|
37
37
|
try {
|
|
38
38
|
const { isFresh, value } = await previousExecutionFlow;
|
|
@@ -48,7 +48,7 @@ export const getSession = async (sub: Did, options?: SessionGetOptions): Promise
|
|
|
48
48
|
options?.signal?.throwIfAborted();
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const run = async (): PendingItem<Session
|
|
51
|
+
const run = async (): Promise<PendingItem<Session>> => {
|
|
52
52
|
const storedSession = database.sessions.get(sub);
|
|
53
53
|
|
|
54
54
|
if (storedSession && allowStored(storedSession)) {
|
|
@@ -65,10 +65,10 @@ export const getSession = async (sub: Did, options?: SessionGetOptions): Promise
|
|
|
65
65
|
return { isFresh: true, value: newSession };
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
let promise: PendingItem<Session
|
|
68
|
+
let promise: Promise<PendingItem<Session>>;
|
|
69
69
|
|
|
70
70
|
if (locks) {
|
|
71
|
-
promise = locks.request(`atcute-oauth:${sub}`, run);
|
|
71
|
+
promise = locks.request<PendingItem<Session>>(`atcute-oauth:${sub}`, run as any);
|
|
72
72
|
} else {
|
|
73
73
|
promise = run();
|
|
74
74
|
}
|
package/lib/agents/user-agent.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { FetchHandlerObject } from '@atcute/client';
|
|
|
2
2
|
import type { Did } from '@atcute/lexicons';
|
|
3
3
|
|
|
4
4
|
import { createDPoPFetch } from '../dpop.js';
|
|
5
|
-
import { CLIENT_ID } from '../environment.js';
|
|
6
5
|
import type { Session } from '../types/token.js';
|
|
7
6
|
|
|
8
7
|
import { OAuthServerAgent } from './server-agent.js';
|
|
@@ -13,7 +12,7 @@ export class OAuthUserAgent implements FetchHandlerObject {
|
|
|
13
12
|
#getSessionPromise: Promise<Session> | undefined;
|
|
14
13
|
|
|
15
14
|
constructor(public session: Session) {
|
|
16
|
-
this.#fetch = createDPoPFetch(
|
|
15
|
+
this.#fetch = createDPoPFetch(session.dpopKey, false);
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
get sub(): Did {
|
package/lib/dpop.ts
CHANGED
|
@@ -23,24 +23,24 @@ export const createES256Key = async (): Promise<DPoPKey> => {
|
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export const createDPoPSignage = (
|
|
26
|
+
export const createDPoPSignage = (dpopKey: DPoPKey) => {
|
|
27
27
|
const headerString = dpopKey.jwt;
|
|
28
|
-
const keyPromise = crypto.subtle.importKey(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
const keyPromise = crypto.subtle.importKey(
|
|
29
|
+
'pkcs8',
|
|
30
|
+
fromBase64Url(dpopKey.key) as Uint8Array<ArrayBuffer>,
|
|
31
|
+
ES256_ALG,
|
|
32
|
+
true,
|
|
33
|
+
['sign'],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const constructPayload = (htm: string, htu: string, nonce: string | undefined, ath: string | undefined) => {
|
|
36
37
|
const payload = {
|
|
37
|
-
|
|
38
|
+
ath: ath,
|
|
39
|
+
htm: htm,
|
|
40
|
+
htu: htu,
|
|
38
41
|
iat: Math.floor(Date.now() / 1_000),
|
|
39
42
|
jti: nanoid(24),
|
|
40
|
-
htm: method,
|
|
41
|
-
htu: htu,
|
|
42
43
|
nonce: nonce,
|
|
43
|
-
ath: ath,
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
return toBase64Url(encodeUtf8(JSON.stringify(payload)));
|
|
@@ -52,7 +52,7 @@ export const createDPoPSignage = (issuer: string, dpopKey: DPoPKey) => {
|
|
|
52
52
|
const signed = await crypto.subtle.sign(
|
|
53
53
|
{ name: 'ECDSA', hash: { name: 'SHA-256' } },
|
|
54
54
|
await keyPromise,
|
|
55
|
-
encodeUtf8(headerString + '.' + payloadString)
|
|
55
|
+
encodeUtf8(headerString + '.' + payloadString) as Uint8Array<ArrayBuffer>,
|
|
56
56
|
);
|
|
57
57
|
|
|
58
58
|
const signatureString = toBase64Url(new Uint8Array(signed));
|
|
@@ -61,14 +61,14 @@ export const createDPoPSignage = (issuer: string, dpopKey: DPoPKey) => {
|
|
|
61
61
|
};
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
export const createDPoPFetch = (
|
|
64
|
+
export const createDPoPFetch = (dpopKey: DPoPKey, isAuthServer?: boolean): typeof fetch => {
|
|
65
65
|
const nonces = database.dpopNonces;
|
|
66
66
|
const pending = database.inflightDpop;
|
|
67
67
|
|
|
68
|
-
const sign = createDPoPSignage(
|
|
68
|
+
const sign = createDPoPSignage(dpopKey);
|
|
69
69
|
|
|
70
70
|
return async (input, init) => {
|
|
71
|
-
const request
|
|
71
|
+
const request = new Request(input, init);
|
|
72
72
|
|
|
73
73
|
const authorizationHeader = request.headers.get('authorization');
|
|
74
74
|
const ath = authorizationHeader?.startsWith('DPoP ')
|
|
@@ -173,7 +173,19 @@ export const createDPoPFetch = (issuer: string, dpopKey: DPoPKey, isAuthServer?:
|
|
|
173
173
|
const nextRequest = new Request(input, init);
|
|
174
174
|
nextRequest.headers.set('dpop', nextProof);
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
const retryResponse = await fetch(nextRequest);
|
|
177
|
+
|
|
178
|
+
// Check if the server returned another new nonce in the retry response
|
|
179
|
+
const retryNonce = retryResponse.headers.get('dpop-nonce');
|
|
180
|
+
if (retryNonce !== null && retryNonce !== nextNonce) {
|
|
181
|
+
try {
|
|
182
|
+
nonces.set(origin, retryNonce);
|
|
183
|
+
} catch {
|
|
184
|
+
// Ignore write errors
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return retryResponse;
|
|
177
189
|
}
|
|
178
190
|
};
|
|
179
191
|
};
|
package/lib/environment.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { DidDocumentResolver, HandleResolver } from '@atcute/identity-resolver';
|
|
2
|
+
|
|
1
3
|
import { createOAuthDatabase, type OAuthDatabase } from './store/db.js';
|
|
2
4
|
|
|
3
5
|
export let CLIENT_ID: string;
|
|
@@ -5,9 +7,17 @@ export let REDIRECT_URI: string;
|
|
|
5
7
|
|
|
6
8
|
export let database: OAuthDatabase;
|
|
7
9
|
|
|
10
|
+
export let handleResolver: HandleResolver;
|
|
11
|
+
export let didDocumentResolver: DidDocumentResolver;
|
|
12
|
+
|
|
8
13
|
export interface ConfigureOAuthOptions {
|
|
14
|
+
/** used to resolve handles into DIDs */
|
|
15
|
+
handleResolver: HandleResolver;
|
|
16
|
+
/** used to resolve DIDs into DID documents */
|
|
17
|
+
didDocumentResolver: DidDocumentResolver;
|
|
18
|
+
|
|
9
19
|
/**
|
|
10
|
-
*
|
|
20
|
+
* client metadata, necessary to drive the whole request
|
|
11
21
|
*/
|
|
12
22
|
metadata: {
|
|
13
23
|
client_id: string;
|
|
@@ -15,13 +25,15 @@ export interface ConfigureOAuthOptions {
|
|
|
15
25
|
};
|
|
16
26
|
|
|
17
27
|
/**
|
|
18
|
-
*
|
|
28
|
+
* name that will be used as prefix for storage keys needed to persist authentication.
|
|
19
29
|
* @default "atcute-oauth"
|
|
20
30
|
*/
|
|
21
31
|
storageName?: string;
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
export const configureOAuth = (options: ConfigureOAuthOptions) => {
|
|
35
|
+
({ handleResolver, didDocumentResolver } = options);
|
|
25
36
|
({ client_id: CLIENT_ID, redirect_uri: REDIRECT_URI } = options.metadata);
|
|
37
|
+
|
|
26
38
|
database = createOAuthDatabase({ name: options.storageName ?? 'atcute-oauth' });
|
|
27
39
|
};
|