@atproto/oauth-provider 0.18.4 → 0.19.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 +16 -0
- package/dist/account/account-manager.d.ts +11 -7
- package/dist/account/account-manager.d.ts.map +1 -1
- package/dist/account/account-manager.js +82 -19
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.d.ts +47 -13
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +4 -1
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/sign-up-input.d.ts +2 -2
- package/dist/errors/invalid-credentials-error.d.ts +9 -9
- package/dist/errors/invalid-credentials-error.d.ts.map +1 -1
- package/dist/errors/invalid-credentials-error.js +8 -8
- package/dist/errors/invalid-credentials-error.js.map +1 -1
- package/dist/lib/util/function.js +1 -1
- package/dist/lib/util/function.js.map +1 -1
- package/dist/oauth-hooks.d.ts +77 -4
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +15 -9
- package/dist/oauth-provider.js.map +1 -1
- package/dist/request/request-data.d.ts +4 -4
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-data.js +1 -1
- package/dist/request/request-data.js.map +1 -1
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +7 -4
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store.d.ts +1 -1
- package/dist/request/request-store.js.map +1 -1
- package/dist/result/authorization-result-authorize-page.d.ts +1 -1
- package/dist/result/authorization-result-authorize-page.js.map +1 -1
- package/dist/router/assets/send-authorization-page.js +1 -1
- package/dist/router/assets/send-authorization-page.js.map +1 -1
- package/dist/router/create-api-middleware.d.ts.map +1 -1
- package/dist/router/create-api-middleware.js +87 -51
- package/dist/router/create-api-middleware.js.map +1 -1
- package/dist/signer/access-token-payload.d.ts +15 -15
- package/dist/signer/access-token-payload.js +2 -2
- package/dist/signer/access-token-payload.js.map +1 -1
- package/dist/signer/api-token-payload.d.ts +15 -15
- package/dist/signer/api-token-payload.js +2 -2
- package/dist/signer/api-token-payload.js.map +1 -1
- package/dist/signer/signer.d.ts +6 -187
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/token-claims.d.ts +2 -1
- package/dist/token/token-claims.d.ts.map +1 -1
- package/dist/token/token-claims.js.map +1 -1
- package/dist/token/token-data.d.ts +3 -3
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-data.js.map +1 -1
- package/dist/token/token-manager.d.ts +3 -3
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +14 -14
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/token-store.d.ts +3 -3
- package/dist/token/token-store.d.ts.map +1 -1
- package/dist/token/token-store.js.map +1 -1
- package/dist/types/handle.d.ts +5 -1
- package/dist/types/handle.d.ts.map +1 -1
- package/dist/types/handle.js +9 -8
- package/dist/types/handle.js.map +1 -1
- package/package.json +10 -10
- package/src/account/account-manager.ts +123 -20
- package/src/account/account-store.ts +59 -11
- package/src/errors/invalid-credentials-error.ts +8 -8
- package/src/lib/util/function.ts +2 -2
- package/src/oauth-hooks.ts +85 -3
- package/src/oauth-provider.ts +17 -9
- package/src/request/request-data.ts +5 -5
- package/src/request/request-manager.ts +11 -4
- package/src/request/request-store.ts +1 -1
- package/src/result/authorization-result-authorize-page.ts +1 -1
- package/src/router/assets/send-authorization-page.ts +1 -1
- package/src/router/create-api-middleware.ts +128 -67
- package/src/signer/access-token-payload.ts +2 -2
- package/src/signer/api-token-payload.ts +2 -2
- package/src/signer/signer.ts +13 -4
- package/src/token/token-claims.ts +2 -1
- package/src/token/token-data.ts +3 -3
- package/src/token/token-manager.ts +15 -15
- package/src/token/token-store.ts +3 -3
- package/src/types/handle.ts +21 -16
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/oidc/sub.d.ts +0 -4
- package/dist/oidc/sub.d.ts.map +0 -1
- package/dist/oidc/sub.js +0 -3
- package/dist/oidc/sub.js.map +0 -1
- package/src/oidc/sub.ts +0 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
1
2
|
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
|
2
3
|
import { ClientAuth, ClientAuthLegacy } from '../client/client-auth.js'
|
|
3
4
|
import { ClientId } from '../client/client-id.js'
|
|
4
5
|
import { DeviceId } from '../device/device-id.js'
|
|
5
6
|
import { NonNullableKeys } from '../lib/util/type.js'
|
|
6
|
-
import { Sub } from '../oidc/sub.js'
|
|
7
7
|
import { Code } from './code.js'
|
|
8
8
|
|
|
9
9
|
export type {
|
|
@@ -12,8 +12,8 @@ export type {
|
|
|
12
12
|
ClientId,
|
|
13
13
|
Code,
|
|
14
14
|
DeviceId,
|
|
15
|
+
Did,
|
|
15
16
|
OAuthAuthorizationRequestParameters,
|
|
16
|
-
Sub,
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export type RequestData = {
|
|
@@ -22,15 +22,15 @@ export type RequestData = {
|
|
|
22
22
|
parameters: Readonly<OAuthAuthorizationRequestParameters>
|
|
23
23
|
expiresAt: Date
|
|
24
24
|
deviceId: DeviceId | null
|
|
25
|
-
|
|
25
|
+
did: Did | null
|
|
26
26
|
code: Code | null
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export type RequestDataAuthorized = NonNullableKeys<
|
|
30
30
|
RequestData,
|
|
31
|
-
'
|
|
31
|
+
'did' | 'deviceId'
|
|
32
32
|
>
|
|
33
33
|
|
|
34
34
|
export const isRequestDataAuthorized = (
|
|
35
35
|
data: RequestData,
|
|
36
|
-
): data is RequestDataAuthorized => data.
|
|
36
|
+
): data is RequestDataAuthorized => data.did !== null && data.deviceId !== null
|
|
@@ -78,7 +78,7 @@ export class RequestManager {
|
|
|
78
78
|
parameters,
|
|
79
79
|
expiresAt,
|
|
80
80
|
deviceId,
|
|
81
|
-
|
|
81
|
+
did: null,
|
|
82
82
|
code: null,
|
|
83
83
|
})
|
|
84
84
|
|
|
@@ -337,7 +337,7 @@ export class RequestManager {
|
|
|
337
337
|
const updates: UpdateRequestData = {}
|
|
338
338
|
|
|
339
339
|
try {
|
|
340
|
-
if (data.
|
|
340
|
+
if (data.did || data.code) {
|
|
341
341
|
// If an account was linked to the request, the next step is to exchange
|
|
342
342
|
// the code for a token.
|
|
343
343
|
throw new AccessDeniedError(
|
|
@@ -404,6 +404,13 @@ export class RequestManager {
|
|
|
404
404
|
let { parameters } = data
|
|
405
405
|
|
|
406
406
|
try {
|
|
407
|
+
if (account.deactivated) {
|
|
408
|
+
throw new AccessDeniedError(
|
|
409
|
+
parameters,
|
|
410
|
+
'This account has been deactivated',
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
407
414
|
if (data.expiresAt < new Date()) {
|
|
408
415
|
throw new AccessDeniedError(parameters, 'This request has expired')
|
|
409
416
|
}
|
|
@@ -419,7 +426,7 @@ export class RequestManager {
|
|
|
419
426
|
'This request was initiated from another device',
|
|
420
427
|
)
|
|
421
428
|
}
|
|
422
|
-
if (data.
|
|
429
|
+
if (data.did || data.code) {
|
|
423
430
|
throw new AccessDeniedError(
|
|
424
431
|
parameters,
|
|
425
432
|
'This request was already authorized',
|
|
@@ -453,7 +460,7 @@ export class RequestManager {
|
|
|
453
460
|
|
|
454
461
|
// Bind the request to the account, preventing it from being used again.
|
|
455
462
|
await this.store.updateRequest(requestId, {
|
|
456
|
-
|
|
463
|
+
did: account.did,
|
|
457
464
|
code,
|
|
458
465
|
// Allow the client to exchange the code for a token within the next 60 seconds.
|
|
459
466
|
expiresAt: new Date(Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT),
|
|
@@ -12,7 +12,7 @@ export type { Awaitable }
|
|
|
12
12
|
|
|
13
13
|
export type UpdateRequestData = Pick<
|
|
14
14
|
Partial<RequestData>,
|
|
15
|
-
'
|
|
15
|
+
'did' | 'code' | 'deviceId' | 'expiresAt' | 'parameters'
|
|
16
16
|
>
|
|
17
17
|
|
|
18
18
|
export type FoundRequestResult = {
|
|
@@ -33,7 +33,7 @@ export function sendAuthorizePageFactory(
|
|
|
33
33
|
loginHint: data.parameters.login_hint,
|
|
34
34
|
promptMode: data.parameters.prompt,
|
|
35
35
|
permissionSets: Object.fromEntries(data.permissionSets),
|
|
36
|
-
|
|
36
|
+
selectedDid: data.selectedDid,
|
|
37
37
|
},
|
|
38
38
|
__sessions: data.sessions,
|
|
39
39
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
2
|
import createHttpError from 'http-errors'
|
|
3
3
|
import { z } from 'zod'
|
|
4
|
+
import { Did, didSchema } from '@atproto/did'
|
|
4
5
|
import { signedJwtSchema } from '@atproto/jwk'
|
|
5
6
|
import {
|
|
6
7
|
API_ENDPOINT_PREFIX,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
OAuthResponseMode,
|
|
17
18
|
oauthRedirectUriSchema,
|
|
18
19
|
oauthResponseModeSchema,
|
|
20
|
+
oauthScopeSchema,
|
|
19
21
|
} from '@atproto/oauth-types'
|
|
20
22
|
import { signInDataSchema } from '../account/sign-in-data.js'
|
|
21
23
|
import { signUpInputSchema } from '../account/sign-up-input.js'
|
|
@@ -49,14 +51,13 @@ import { asArray } from '../lib/util/cast.js'
|
|
|
49
51
|
import { localeSchema } from '../lib/util/locale.js'
|
|
50
52
|
import type { Awaitable } from '../lib/util/type.js'
|
|
51
53
|
import type { OAuthProvider } from '../oauth-provider.js'
|
|
52
|
-
import { Sub, subSchema } from '../oidc/sub.js'
|
|
53
54
|
import { RequestUri, requestUriSchema } from '../request/request-uri.js'
|
|
54
55
|
import { AuthorizationRedirectParameters } from '../result/authorization-redirect-parameters.js'
|
|
55
56
|
import { tokenIdSchema } from '../token/token-id.js'
|
|
56
57
|
import { emailOtpSchema } from '../types/email-otp.js'
|
|
57
58
|
import { emailSchema } from '../types/email.js'
|
|
58
59
|
import { handleSchema } from '../types/handle.js'
|
|
59
|
-
import { newPasswordSchema } from '../types/password.js'
|
|
60
|
+
import { newPasswordSchema, oldPasswordSchema } from '../types/password.js'
|
|
60
61
|
import { validateCsrfToken } from './assets/csrf.js'
|
|
61
62
|
import {
|
|
62
63
|
ERROR_REDIRECT_KEYS,
|
|
@@ -69,8 +70,6 @@ import {
|
|
|
69
70
|
} from './assets/send-redirect.js'
|
|
70
71
|
import type { MiddlewareOptions } from './middleware-options.js'
|
|
71
72
|
|
|
72
|
-
const verifyHandleSchema = z.object({ handle: handleSchema }).strict()
|
|
73
|
-
|
|
74
73
|
export function createApiMiddleware<
|
|
75
74
|
Ctx extends object | void = void,
|
|
76
75
|
Req extends IncomingMessage = IncomingMessage,
|
|
@@ -87,7 +86,7 @@ export function createApiMiddleware<
|
|
|
87
86
|
apiRoute({
|
|
88
87
|
method: 'POST',
|
|
89
88
|
endpoint: '/verify-handle-availability',
|
|
90
|
-
schema:
|
|
89
|
+
schema: z.object({ handle: handleSchema }).strict(),
|
|
91
90
|
async handler() {
|
|
92
91
|
await server.accountManager.verifyHandleAvailability(this.input.handle)
|
|
93
92
|
return { json: { available: true } }
|
|
@@ -116,13 +115,13 @@ export function createApiMiddleware<
|
|
|
116
115
|
// Only "remember" the newly created account if it was not created during an
|
|
117
116
|
// OAuth flow.
|
|
118
117
|
if (remember) {
|
|
119
|
-
await server.accountManager.upsertDeviceAccount(deviceId, account.
|
|
118
|
+
await server.accountManager.upsertDeviceAccount(deviceId, account.did)
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
const ephemeralToken = remember
|
|
123
122
|
? undefined
|
|
124
123
|
: await server.signer.createEphemeralToken({
|
|
125
|
-
sub: account.
|
|
124
|
+
sub: account.did,
|
|
126
125
|
deviceId,
|
|
127
126
|
requestUri: this.requestUri,
|
|
128
127
|
})
|
|
@@ -159,46 +158,21 @@ export function createApiMiddleware<
|
|
|
159
158
|
)
|
|
160
159
|
|
|
161
160
|
if (remember) {
|
|
162
|
-
await server.accountManager.upsertDeviceAccount(deviceId, account.
|
|
161
|
+
await server.accountManager.upsertDeviceAccount(deviceId, account.did)
|
|
163
162
|
} else {
|
|
164
163
|
// In case the user was already signed in, and signed in again, this
|
|
165
164
|
// time without "remember me", let's sign them off of the device.
|
|
166
|
-
await server.accountManager.removeDeviceAccount(deviceId, account.
|
|
165
|
+
await server.accountManager.removeDeviceAccount(deviceId, account.did)
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
const ephemeralToken = remember
|
|
170
169
|
? undefined
|
|
171
170
|
: await server.signer.createEphemeralToken({
|
|
172
|
-
sub: account.
|
|
171
|
+
sub: account.did,
|
|
173
172
|
deviceId,
|
|
174
173
|
requestUri,
|
|
175
174
|
})
|
|
176
175
|
|
|
177
|
-
if (requestUri) {
|
|
178
|
-
// Check if a consent is required for the client, but only if this
|
|
179
|
-
// call is made within the context of an oauth request.
|
|
180
|
-
|
|
181
|
-
const { clientId, parameters } = await server.requestManager.get(
|
|
182
|
-
requestUri,
|
|
183
|
-
deviceId,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
const { authorizedClients } = await server.accountManager.getAccount(
|
|
187
|
-
account.sub,
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
const json = {
|
|
191
|
-
account,
|
|
192
|
-
ephemeralToken,
|
|
193
|
-
consentRequired: server.checkConsentRequired(
|
|
194
|
-
parameters,
|
|
195
|
-
authorizedClients.get(clientId),
|
|
196
|
-
),
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return { json }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
176
|
const json = { account, ephemeralToken }
|
|
203
177
|
return { json }
|
|
204
178
|
},
|
|
@@ -211,12 +185,12 @@ export function createApiMiddleware<
|
|
|
211
185
|
endpoint: '/sign-out',
|
|
212
186
|
schema: z
|
|
213
187
|
.object({
|
|
214
|
-
|
|
188
|
+
did: z.union([didSchema, z.array(didSchema)]),
|
|
215
189
|
})
|
|
216
190
|
.strict(),
|
|
217
191
|
rotateDeviceCookies: true,
|
|
218
192
|
async handler() {
|
|
219
|
-
const uniqueSubs = new Set(asArray(this.input.
|
|
193
|
+
const uniqueSubs = new Set(asArray(this.input.did))
|
|
220
194
|
|
|
221
195
|
for (const sub of uniqueSubs) {
|
|
222
196
|
await server.accountManager.removeDeviceAccount(this.deviceId, sub)
|
|
@@ -275,7 +249,7 @@ export function createApiMiddleware<
|
|
|
275
249
|
endpoint: '/update-email-request',
|
|
276
250
|
schema: z
|
|
277
251
|
.object({
|
|
278
|
-
|
|
252
|
+
did: didSchema,
|
|
279
253
|
locale: localeSchema.optional(),
|
|
280
254
|
})
|
|
281
255
|
.strict(),
|
|
@@ -301,23 +275,23 @@ export function createApiMiddleware<
|
|
|
301
275
|
endpoint: '/update-email-confirm',
|
|
302
276
|
schema: z
|
|
303
277
|
.object({
|
|
304
|
-
|
|
278
|
+
did: didSchema,
|
|
305
279
|
email: emailSchema,
|
|
306
280
|
token: emailOtpSchema.optional(),
|
|
307
281
|
locale: localeSchema.optional(),
|
|
308
282
|
})
|
|
309
283
|
.strict(),
|
|
310
284
|
async handler(req, res) {
|
|
311
|
-
|
|
285
|
+
let { account } = await authenticate.call(this, req, res)
|
|
312
286
|
|
|
313
|
-
await server.accountManager.updateEmailConfirm(
|
|
287
|
+
account = await server.accountManager.updateEmailConfirm(
|
|
314
288
|
this.deviceId,
|
|
315
289
|
this.deviceMetadata,
|
|
316
290
|
this.input,
|
|
317
291
|
account,
|
|
318
292
|
)
|
|
319
293
|
|
|
320
|
-
return { json: {
|
|
294
|
+
return { json: { account } }
|
|
321
295
|
},
|
|
322
296
|
}),
|
|
323
297
|
)
|
|
@@ -328,7 +302,7 @@ export function createApiMiddleware<
|
|
|
328
302
|
endpoint: '/verify-email-request',
|
|
329
303
|
schema: z
|
|
330
304
|
.object({
|
|
331
|
-
|
|
305
|
+
did: didSchema,
|
|
332
306
|
locale: localeSchema.optional(),
|
|
333
307
|
})
|
|
334
308
|
.strict(),
|
|
@@ -353,22 +327,22 @@ export function createApiMiddleware<
|
|
|
353
327
|
endpoint: '/verify-email-confirm',
|
|
354
328
|
schema: z
|
|
355
329
|
.object({
|
|
356
|
-
|
|
330
|
+
did: didSchema,
|
|
357
331
|
token: emailOtpSchema,
|
|
358
332
|
email: emailSchema,
|
|
359
333
|
})
|
|
360
334
|
.strict(),
|
|
361
335
|
async handler(req, res) {
|
|
362
|
-
|
|
336
|
+
let { account } = await authenticate.call(this, req, res)
|
|
363
337
|
|
|
364
|
-
await server.accountManager.verifyEmailConfirm(
|
|
338
|
+
account = await server.accountManager.verifyEmailConfirm(
|
|
365
339
|
this.deviceId,
|
|
366
340
|
this.deviceMetadata,
|
|
367
341
|
this.input,
|
|
368
342
|
account,
|
|
369
343
|
)
|
|
370
344
|
|
|
371
|
-
return { json: {
|
|
345
|
+
return { json: { account } }
|
|
372
346
|
},
|
|
373
347
|
}),
|
|
374
348
|
)
|
|
@@ -379,14 +353,104 @@ export function createApiMiddleware<
|
|
|
379
353
|
endpoint: '/update-handle',
|
|
380
354
|
schema: z
|
|
381
355
|
.object({
|
|
382
|
-
|
|
356
|
+
did: didSchema,
|
|
383
357
|
handle: handleSchema,
|
|
384
358
|
})
|
|
385
359
|
.strict(),
|
|
360
|
+
async handler(req, res) {
|
|
361
|
+
let { account } = await authenticate.call(this, req, res)
|
|
362
|
+
|
|
363
|
+
account = await server.accountManager.updateHandle(
|
|
364
|
+
this.deviceId,
|
|
365
|
+
this.deviceMetadata,
|
|
366
|
+
this.input,
|
|
367
|
+
account,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return { json: { account } }
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
router.use(
|
|
376
|
+
apiRoute({
|
|
377
|
+
method: 'POST',
|
|
378
|
+
endpoint: '/deactivate-account',
|
|
379
|
+
schema: z.object({ did: didSchema }).strict(),
|
|
380
|
+
async handler(req, res) {
|
|
381
|
+
let { account } = await authenticate.call(this, req, res)
|
|
382
|
+
|
|
383
|
+
if (!account.deactivated) {
|
|
384
|
+
account = await server.accountManager.deactivateAccount(
|
|
385
|
+
this.deviceId,
|
|
386
|
+
this.deviceMetadata,
|
|
387
|
+
account,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { json: { account } }
|
|
392
|
+
},
|
|
393
|
+
}),
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
router.use(
|
|
397
|
+
apiRoute({
|
|
398
|
+
method: 'POST',
|
|
399
|
+
endpoint: '/reactivate-account',
|
|
400
|
+
schema: z.object({ did: didSchema }).strict(),
|
|
401
|
+
async handler(req, res) {
|
|
402
|
+
let { account } = await authenticate.call(this, req, res)
|
|
403
|
+
|
|
404
|
+
if (account.deactivated) {
|
|
405
|
+
account = await server.accountManager.reactivateAccount(
|
|
406
|
+
this.deviceId,
|
|
407
|
+
this.deviceMetadata,
|
|
408
|
+
account,
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return { json: { account } }
|
|
413
|
+
},
|
|
414
|
+
}),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
router.use(
|
|
418
|
+
apiRoute({
|
|
419
|
+
method: 'POST',
|
|
420
|
+
endpoint: '/delete-account-request',
|
|
421
|
+
schema: z
|
|
422
|
+
.object({ did: didSchema, locale: localeSchema.optional() })
|
|
423
|
+
.strict(),
|
|
424
|
+
async handler(req, res) {
|
|
425
|
+
const { account } = await authenticate.call(this, req, res)
|
|
426
|
+
|
|
427
|
+
await server.accountManager.deleteAccountRequest(
|
|
428
|
+
this.deviceId,
|
|
429
|
+
this.deviceMetadata,
|
|
430
|
+
this.input,
|
|
431
|
+
account,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
return { json: { success: true } }
|
|
435
|
+
},
|
|
436
|
+
}),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
router.use(
|
|
440
|
+
apiRoute({
|
|
441
|
+
method: 'POST',
|
|
442
|
+
endpoint: '/delete-account-confirm',
|
|
443
|
+
schema: z
|
|
444
|
+
.object({
|
|
445
|
+
did: didSchema,
|
|
446
|
+
token: emailOtpSchema,
|
|
447
|
+
password: oldPasswordSchema,
|
|
448
|
+
})
|
|
449
|
+
.strict(),
|
|
386
450
|
async handler(req, res) {
|
|
387
451
|
const { account } = await authenticate.call(this, req, res)
|
|
388
452
|
|
|
389
|
-
await server.accountManager.
|
|
453
|
+
await server.accountManager.deleteAccountConfirm(
|
|
390
454
|
this.deviceId,
|
|
391
455
|
this.deviceMetadata,
|
|
392
456
|
this.input,
|
|
@@ -424,12 +488,12 @@ export function createApiMiddleware<
|
|
|
424
488
|
apiRoute({
|
|
425
489
|
method: 'GET',
|
|
426
490
|
endpoint: '/oauth-sessions',
|
|
427
|
-
schema: z.object({
|
|
491
|
+
schema: z.object({ did: didSchema }).strict(),
|
|
428
492
|
async handler(req, res) {
|
|
429
493
|
const { account } = await authenticate.call(this, req, res)
|
|
430
494
|
|
|
431
495
|
const tokenInfos = await server.tokenManager.listAccountTokens(
|
|
432
|
-
account.
|
|
496
|
+
account.did,
|
|
433
497
|
)
|
|
434
498
|
|
|
435
499
|
const clientIds = tokenInfos.map((tokenInfo) => tokenInfo.data.clientId)
|
|
@@ -468,12 +532,12 @@ export function createApiMiddleware<
|
|
|
468
532
|
apiRoute({
|
|
469
533
|
method: 'GET',
|
|
470
534
|
endpoint: '/account-sessions',
|
|
471
|
-
schema: z.object({
|
|
535
|
+
schema: z.object({ did: didSchema }).strict(),
|
|
472
536
|
async handler(req, res) {
|
|
473
537
|
const { account } = await authenticate.call(this, req, res)
|
|
474
538
|
|
|
475
539
|
const deviceAccounts = await server.accountManager.listAccountDevices(
|
|
476
|
-
account.
|
|
540
|
+
account.did,
|
|
477
541
|
)
|
|
478
542
|
|
|
479
543
|
const json = deviceAccounts.map(
|
|
@@ -499,7 +563,7 @@ export function createApiMiddleware<
|
|
|
499
563
|
apiRoute({
|
|
500
564
|
method: 'POST',
|
|
501
565
|
endpoint: '/revoke-account-session',
|
|
502
|
-
schema: z.object({
|
|
566
|
+
schema: z.object({ did: didSchema, deviceId: deviceIdSchema }).strict(),
|
|
503
567
|
async handler() {
|
|
504
568
|
// @NOTE This route is not authenticated. If a user is able to steal
|
|
505
569
|
// another user's session cookie, we allow them to revoke the device
|
|
@@ -507,7 +571,7 @@ export function createApiMiddleware<
|
|
|
507
571
|
|
|
508
572
|
await server.accountManager.removeDeviceAccount(
|
|
509
573
|
this.input.deviceId,
|
|
510
|
-
this.input.
|
|
574
|
+
this.input.did,
|
|
511
575
|
)
|
|
512
576
|
|
|
513
577
|
return { json: { success: true } }
|
|
@@ -519,7 +583,7 @@ export function createApiMiddleware<
|
|
|
519
583
|
apiRoute({
|
|
520
584
|
method: 'POST',
|
|
521
585
|
endpoint: '/revoke-oauth-session',
|
|
522
|
-
schema: z.object({
|
|
586
|
+
schema: z.object({ did: didSchema, tokenId: tokenIdSchema }).strict(),
|
|
523
587
|
async handler(req, res) {
|
|
524
588
|
const { account } = await authenticate.call(this, req, res)
|
|
525
589
|
|
|
@@ -527,7 +591,7 @@ export function createApiMiddleware<
|
|
|
527
591
|
this.input.tokenId,
|
|
528
592
|
)
|
|
529
593
|
|
|
530
|
-
if (!tokenInfo || tokenInfo.account.
|
|
594
|
+
if (!tokenInfo || tokenInfo.account.did !== account.did) {
|
|
531
595
|
// report this as though the token was not found
|
|
532
596
|
throw new InvalidRequestError(`Invalid token`)
|
|
533
597
|
}
|
|
@@ -544,10 +608,7 @@ export function createApiMiddleware<
|
|
|
544
608
|
method: 'POST',
|
|
545
609
|
endpoint: '/consent',
|
|
546
610
|
schema: z
|
|
547
|
-
.object({
|
|
548
|
-
sub: z.union([subSchema, signedJwtSchema]),
|
|
549
|
-
scope: z.string().optional(),
|
|
550
|
-
})
|
|
611
|
+
.object({ did: didSchema, scope: oauthScopeSchema.optional() })
|
|
551
612
|
.strict(),
|
|
552
613
|
async handler(req, res) {
|
|
553
614
|
if (!this.requestUri) {
|
|
@@ -710,7 +771,7 @@ export function createApiMiddleware<
|
|
|
710
771
|
return router.buildMiddleware()
|
|
711
772
|
|
|
712
773
|
async function authenticate(
|
|
713
|
-
this: ApiContext<void, {
|
|
774
|
+
this: ApiContext<void, { did: Did }>,
|
|
714
775
|
req: Req,
|
|
715
776
|
_res: Res,
|
|
716
777
|
) {
|
|
@@ -724,7 +785,7 @@ export function createApiMiddleware<
|
|
|
724
785
|
await server.signer.verifyEphemeralToken(ephemeralToken)
|
|
725
786
|
|
|
726
787
|
if (
|
|
727
|
-
payload.sub === this.input.
|
|
788
|
+
payload.sub === this.input.did &&
|
|
728
789
|
payload.deviceId === this.deviceId &&
|
|
729
790
|
payload.requestUri === this.requestUri
|
|
730
791
|
) {
|
|
@@ -744,7 +805,7 @@ export function createApiMiddleware<
|
|
|
744
805
|
// Ensures the "sub" has an active session on the device
|
|
745
806
|
const deviceAccount = await server.accountManager.getDeviceAccount(
|
|
746
807
|
this.deviceId,
|
|
747
|
-
this.input.
|
|
808
|
+
this.input.did,
|
|
748
809
|
)
|
|
749
810
|
|
|
750
811
|
// The session exists but was created too long ago
|
|
@@ -756,7 +817,7 @@ export function createApiMiddleware<
|
|
|
756
817
|
} catch (err) {
|
|
757
818
|
throw new WWWAuthenticateError(
|
|
758
819
|
'unauthorized',
|
|
759
|
-
`User ${this.input.
|
|
820
|
+
`User ${this.input.did} not authenticated on this device`,
|
|
760
821
|
{ Bearer: {} },
|
|
761
822
|
err,
|
|
762
823
|
)
|
|
@@ -803,9 +864,9 @@ export function createApiMiddleware<
|
|
|
803
864
|
}[keyof ApiEndpoints],
|
|
804
865
|
S extends // A schema that validates the POST input or GET params
|
|
805
866
|
ApiEndpoints[E] extends { method: 'POST'; input: infer I }
|
|
806
|
-
? z.ZodType<I>
|
|
867
|
+
? z.ZodType<I, z.ZodTypeDef, unknown>
|
|
807
868
|
: ApiEndpoints[E] extends { method: 'GET'; params: infer P }
|
|
808
|
-
? z.ZodType<P>
|
|
869
|
+
? z.ZodType<P, z.ZodTypeDef, unknown>
|
|
809
870
|
: void,
|
|
810
871
|
>(options: {
|
|
811
872
|
method: M
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import { didSchema } from '@atproto/did'
|
|
2
3
|
import { jwtPayloadSchema } from '@atproto/jwk'
|
|
3
4
|
import { clientIdSchema } from '../client/client-id.js'
|
|
4
|
-
import { subSchema } from '../oidc/sub.js'
|
|
5
5
|
import { tokenIdSchema } from '../token/token-id.js'
|
|
6
6
|
|
|
7
7
|
export const accessTokenPayloadSchema = jwtPayloadSchema
|
|
@@ -9,7 +9,7 @@ export const accessTokenPayloadSchema = jwtPayloadSchema
|
|
|
9
9
|
.extend({
|
|
10
10
|
// Following are required
|
|
11
11
|
jti: tokenIdSchema,
|
|
12
|
-
sub:
|
|
12
|
+
sub: didSchema,
|
|
13
13
|
exp: z.number().int(),
|
|
14
14
|
iat: z.number().int(),
|
|
15
15
|
iss: z.string().min(1),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import { didSchema } from '@atproto/did'
|
|
2
3
|
import { jwtPayloadSchema } from '@atproto/jwk'
|
|
3
4
|
import { deviceIdSchema } from '../oauth-store.js'
|
|
4
|
-
import { subSchema } from '../oidc/sub.js'
|
|
5
5
|
import { requestUriSchema } from '../request/request-uri.js'
|
|
6
6
|
|
|
7
7
|
export const apiTokenPayloadSchema = jwtPayloadSchema
|
|
8
8
|
.extend({
|
|
9
|
-
sub:
|
|
9
|
+
sub: didSchema,
|
|
10
10
|
|
|
11
11
|
deviceId: deviceIdSchema,
|
|
12
12
|
// If the token is bound to a particular authorization request, it can only
|
package/src/signer/signer.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
JwtHeader,
|
|
2
3
|
JwtPayload,
|
|
3
4
|
JwtPayloadGetter,
|
|
4
5
|
JwtSignHeader,
|
|
6
|
+
Key,
|
|
5
7
|
Keyset,
|
|
6
8
|
SignedJwt,
|
|
7
9
|
VerifyOptions,
|
|
10
|
+
VerifyResult,
|
|
8
11
|
} from '@atproto/jwk'
|
|
9
12
|
import { EPHEMERAL_SESSION_MAX_AGE } from '../constants.js'
|
|
10
13
|
import { dateToEpoch } from '../lib/util/date.js'
|
|
@@ -29,7 +32,7 @@ export class Signer {
|
|
|
29
32
|
async verify<C extends string = never>(
|
|
30
33
|
token: SignedJwt,
|
|
31
34
|
options?: Omit<VerifyOptions<C>, 'issuer'>,
|
|
32
|
-
) {
|
|
35
|
+
): Promise<VerifyResult<C> & { key: Key }> {
|
|
33
36
|
return this.keyset.verifyJwt<C>(token, {
|
|
34
37
|
...options,
|
|
35
38
|
issuer: [this.issuer],
|
|
@@ -64,7 +67,10 @@ export class Signer {
|
|
|
64
67
|
async verifyAccessToken<C extends string = never>(
|
|
65
68
|
token: SignedJwt,
|
|
66
69
|
options?: Omit<VerifyOptions<C>, 'issuer' | 'typ'>,
|
|
67
|
-
) {
|
|
70
|
+
): Promise<{
|
|
71
|
+
protectedHeader: JwtHeader
|
|
72
|
+
payload: RequiredKey<AccessTokenPayload, C>
|
|
73
|
+
}> {
|
|
68
74
|
const result = await this.verify<C>(token, { ...options, typ: 'at+jwt' })
|
|
69
75
|
return {
|
|
70
76
|
protectedHeader: result.protectedHeader,
|
|
@@ -77,7 +83,7 @@ export class Signer {
|
|
|
77
83
|
|
|
78
84
|
async createEphemeralToken(
|
|
79
85
|
payload: OmitKey<ApiTokenPayload, 'iss' | 'aud' | 'iat'>,
|
|
80
|
-
) {
|
|
86
|
+
): Promise<SignedJwt> {
|
|
81
87
|
return this.sign(
|
|
82
88
|
{
|
|
83
89
|
alg: undefined,
|
|
@@ -94,7 +100,10 @@ export class Signer {
|
|
|
94
100
|
async verifyEphemeralToken<C extends string = never>(
|
|
95
101
|
token: SignedJwt,
|
|
96
102
|
options?: Omit<VerifyOptions<C>, 'issuer' | 'audience' | 'typ'>,
|
|
97
|
-
) {
|
|
103
|
+
): Promise<{
|
|
104
|
+
protectedHeader: JwtHeader
|
|
105
|
+
payload: RequiredKey<ApiTokenPayload, C>
|
|
106
|
+
}> {
|
|
98
107
|
const result = await this.verify<C>(token, {
|
|
99
108
|
...options,
|
|
100
109
|
maxTokenAge: options?.maxTokenAge ?? EPHEMERAL_SESSION_MAX_AGE / 1e3,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
1
2
|
import { OAuthScope } from '@atproto/oauth-types'
|
|
2
3
|
import { ClientId } from '../client/client-id.js'
|
|
3
4
|
import { TokenId } from './token-id.js'
|
|
@@ -11,7 +12,7 @@ import { TokenId } from './token-id.js'
|
|
|
11
12
|
*/
|
|
12
13
|
export type TokenClaims = {
|
|
13
14
|
jti: TokenId
|
|
14
|
-
sub:
|
|
15
|
+
sub: Did
|
|
15
16
|
iat: number
|
|
16
17
|
exp: number
|
|
17
18
|
aud: string | [string, ...string[]]
|
package/src/token/token-data.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
1
2
|
import {
|
|
2
3
|
OAuthAuthorizationDetails,
|
|
3
4
|
OAuthAuthorizationRequestParameters,
|
|
@@ -5,7 +6,6 @@ import {
|
|
|
5
6
|
import { ClientAuth, ClientAuthLegacy } from '../client/client-auth.js'
|
|
6
7
|
import { ClientId } from '../client/client-id.js'
|
|
7
8
|
import { DeviceId } from '../device/device-id.js'
|
|
8
|
-
import { Sub } from '../oidc/sub.js'
|
|
9
9
|
import { Code } from '../request/code.js'
|
|
10
10
|
|
|
11
11
|
export type {
|
|
@@ -13,9 +13,9 @@ export type {
|
|
|
13
13
|
ClientId,
|
|
14
14
|
Code,
|
|
15
15
|
DeviceId,
|
|
16
|
+
Did,
|
|
16
17
|
OAuthAuthorizationDetails,
|
|
17
18
|
OAuthAuthorizationRequestParameters,
|
|
18
|
-
Sub,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export type TokenData = {
|
|
@@ -25,7 +25,7 @@ export type TokenData = {
|
|
|
25
25
|
clientId: ClientId
|
|
26
26
|
clientAuth: ClientAuth | ClientAuthLegacy
|
|
27
27
|
deviceId: DeviceId | null
|
|
28
|
-
|
|
28
|
+
did: Did
|
|
29
29
|
parameters: OAuthAuthorizationRequestParameters
|
|
30
30
|
details?: null // Legacy field, not used
|
|
31
31
|
code: Code | null
|