@atproto/oauth-provider 0.18.3 → 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 +40 -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 +12 -9
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/sign-in-data.d.ts +2 -2
- package/dist/account/sign-up-input.d.ts +5 -5
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client.d.ts +1 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/customization/branding.d.ts +52 -52
- package/dist/customization/colors.d.ts +24 -24
- package/dist/customization/colors.d.ts.map +1 -1
- package/dist/customization/customization.d.ts +77 -77
- package/dist/customization/links.d.ts +4 -4
- package/dist/device/device-manager.d.ts +20 -20
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js.map +1 -1
- package/dist/device/device-store.js +1 -1
- package/dist/device/device-store.js.map +1 -1
- package/dist/dpop/dpop-manager.d.ts.map +1 -1
- package/dist/dpop/dpop-manager.js.map +1 -1
- package/dist/dpop/dpop-nonce.d.ts.map +1 -1
- package/dist/dpop/dpop-nonce.js +1 -1
- package/dist/dpop/dpop-nonce.js.map +1 -1
- package/dist/errors/access-denied-error.d.ts.map +1 -1
- package/dist/errors/account-selection-required-error.d.ts.map +1 -1
- package/dist/errors/authorization-error.d.ts.map +1 -1
- package/dist/errors/authorization-error.js.map +1 -1
- package/dist/errors/consent-required-error.d.ts.map +1 -1
- package/dist/errors/handle-unavailable-error.d.ts +1 -1
- package/dist/errors/handle-unavailable-error.d.ts.map +1 -1
- package/dist/errors/handle-unavailable-error.js.map +1 -1
- package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-id-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-metadata-error.d.ts.map +1 -1
- 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/errors/invalid-dpop-key-binding-error.d.ts.map +1 -1
- package/dist/errors/invalid-dpop-proof-error.d.ts.map +1 -1
- package/dist/errors/invalid-grant-error.d.ts.map +1 -1
- package/dist/errors/invalid-invite-code-error.d.ts.map +1 -1
- package/dist/errors/invalid-redirect-uri-error.d.ts.map +1 -1
- package/dist/errors/invalid-request-error.d.ts.map +1 -1
- package/dist/errors/invalid-scope-error.d.ts.map +1 -1
- package/dist/errors/invalid-token-error.d.ts.map +1 -1
- package/dist/errors/invalid-token-error.js.map +1 -1
- package/dist/errors/login-required-error.d.ts.map +1 -1
- package/dist/errors/oauth-error.d.ts.map +1 -1
- package/dist/errors/oauth-error.js.map +1 -1
- package/dist/errors/second-authentication-factor-required-error.d.ts +2 -2
- package/dist/errors/second-authentication-factor-required-error.d.ts.map +1 -1
- package/dist/errors/second-authentication-factor-required-error.js.map +1 -1
- package/dist/errors/unauthorized-client-error.d.ts.map +1 -1
- package/dist/errors/use-dpop-nonce-error.d.ts.map +1 -1
- package/dist/errors/www-authenticate-error.d.ts.map +1 -1
- package/dist/lexicon/lexicon-getter.d.ts.map +1 -1
- package/dist/lexicon/lexicon-manager.d.ts.map +1 -1
- package/dist/lexicon/lexicon-store.js +1 -1
- package/dist/lexicon/lexicon-store.js.map +1 -1
- package/dist/lib/csp/index.d.ts +3 -3
- package/dist/lib/csp/index.d.ts.map +1 -1
- package/dist/lib/hcaptcha.d.ts +3 -3
- package/dist/lib/hcaptcha.d.ts.map +1 -1
- package/dist/lib/hcaptcha.js.map +1 -1
- package/dist/lib/html/build-document.d.ts.map +1 -1
- package/dist/lib/html/html.d.ts.map +1 -1
- package/dist/lib/html/tags.d.ts.map +1 -1
- package/dist/lib/http/accept.js.map +1 -1
- package/dist/lib/http/context.js.map +1 -1
- package/dist/lib/http/middleware.js.map +1 -1
- package/dist/lib/http/parser.d.ts +5 -5
- package/dist/lib/http/parser.d.ts.map +1 -1
- package/dist/lib/http/response.js.map +1 -1
- package/dist/lib/http/router.d.ts.map +1 -1
- package/dist/lib/http/stream.d.ts +5 -5
- package/dist/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/util/authorization-header.d.ts +1 -1
- package/dist/lib/util/authorization-header.d.ts.map +1 -1
- package/dist/lib/util/color.js.map +1 -1
- package/dist/lib/util/crypto.d.ts +1 -1
- package/dist/lib/util/crypto.d.ts.map +1 -1
- package/dist/lib/util/date.js.map +1 -1
- package/dist/lib/util/function.js +1 -1
- package/dist/lib/util/function.js.map +1 -1
- package/dist/lib/util/type.d.ts.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-middleware.js.map +1 -1
- package/dist/oauth-provider.d.ts +31 -31
- 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/oauth-verifier.d.ts.map +1 -1
- package/dist/replay/replay-manager.d.ts.map +1 -1
- package/dist/replay/replay-manager.js.map +1 -1
- package/dist/replay/replay-store-memory.d.ts.map +1 -1
- package/dist/replay/replay-store-redis.d.ts.map +1 -1
- package/dist/request/code.d.ts.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 +32 -32
- 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 +2 -2
- 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/assets-manifest.js.map +1 -1
- package/dist/router/assets/assets.d.ts +1 -1
- package/dist/router/assets/assets.d.ts.map +1 -1
- package/dist/router/assets/assets.js.map +1 -1
- package/dist/router/assets/send-account-page.d.ts.map +1 -1
- package/dist/router/assets/send-authorization-page.d.ts.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/assets/send-cookie-error-page.d.ts.map +1 -1
- package/dist/router/assets/send-error-page.d.ts.map +1 -1
- package/dist/router/assets/send-redirect.d.ts +2 -2
- package/dist/router/assets/send-redirect.d.ts.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 +1762 -1762
- package/dist/signer/access-token-payload.d.ts.map +1 -1
- 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 +1738 -1738
- package/dist/signer/api-token-payload.d.ts.map +1 -1
- 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/refresh-token.d.ts.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-id.d.ts.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 +3 -3
- 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 +19 -19
- package/src/account/account-manager.ts +123 -20
- package/src/account/account-store.ts +67 -19
- package/src/device/device-store.ts +1 -1
- package/src/errors/invalid-credentials-error.ts +8 -8
- package/src/lexicon/lexicon-store.ts +1 -1
- 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 +3 -3
- 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 +6 -6
- 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,12 +1,17 @@
|
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
1
2
|
import type {
|
|
2
3
|
Account,
|
|
4
|
+
ConfirmAccountDeletionInput,
|
|
3
5
|
ConfirmEmailUpdateInput,
|
|
4
6
|
ConfirmEmailVerificationInput,
|
|
5
7
|
ConfirmResetPasswordInput,
|
|
8
|
+
DeactivateAccountInput,
|
|
9
|
+
InitiateAccountDeletionInput,
|
|
6
10
|
InitiateEmailUpdateInput,
|
|
7
11
|
InitiateEmailUpdateOutput,
|
|
8
12
|
InitiateEmailVerificationInput,
|
|
9
13
|
InitiatePasswordResetInput,
|
|
14
|
+
ReactivateAccountInput,
|
|
10
15
|
UpdateHandleInput,
|
|
11
16
|
} from '@atproto/oauth-provider-api'
|
|
12
17
|
import { OAuthScope } from '@atproto/oauth-types'
|
|
@@ -21,7 +26,6 @@ import {
|
|
|
21
26
|
InvalidRequestError,
|
|
22
27
|
SecondAuthenticationFactorRequiredError,
|
|
23
28
|
} from '../oauth-errors.js'
|
|
24
|
-
import { Sub } from '../oidc/sub.js'
|
|
25
29
|
import { InviteCode } from '../types/invite-code.js'
|
|
26
30
|
import { SignUpInput } from './sign-up-input.js'
|
|
27
31
|
|
|
@@ -30,11 +34,11 @@ import { SignUpInput } from './sign-up-input.js'
|
|
|
30
34
|
export * from '../client/client-id.js'
|
|
31
35
|
export * from '../device/device-data.js'
|
|
32
36
|
export * from '../device/device-id.js'
|
|
33
|
-
export * from '../oidc/sub.js'
|
|
34
37
|
export * from '../request/request-id.js'
|
|
35
38
|
|
|
36
39
|
export type {
|
|
37
40
|
Account,
|
|
41
|
+
Did,
|
|
38
42
|
HcaptchaVerifyResult,
|
|
39
43
|
InviteCode,
|
|
40
44
|
OAuthScope,
|
|
@@ -58,6 +62,11 @@ export type VerifyEmailRequestInput = InitiateEmailVerificationInput
|
|
|
58
62
|
export type VerifyEmailConfirmInput = ConfirmEmailVerificationInput
|
|
59
63
|
export type UpdateHandleData = UpdateHandleInput
|
|
60
64
|
|
|
65
|
+
export type DeactivateAccountData = DeactivateAccountInput
|
|
66
|
+
export type ReactivateAccountData = ReactivateAccountInput
|
|
67
|
+
export type DeleteAccountRequestInput = InitiateAccountDeletionInput
|
|
68
|
+
export type DeleteAccountConfirmInput = ConfirmAccountDeletionInput
|
|
69
|
+
|
|
61
70
|
export type CreateAccountData = {
|
|
62
71
|
locale: string
|
|
63
72
|
email: string
|
|
@@ -124,7 +133,7 @@ export interface AccountStore {
|
|
|
124
133
|
|
|
125
134
|
/**
|
|
126
135
|
* @throws {InvalidCredentialsError} - When the credentials are not valid.
|
|
127
|
-
* Populate {@link InvalidCredentialsError.
|
|
136
|
+
* Populate {@link InvalidCredentialsError.did} with the subject identifier
|
|
128
137
|
* when the identifier matched an existing account (e.g. wrong password for
|
|
129
138
|
* a known user); omit it when the identifier was not found. Throwing the
|
|
130
139
|
* generic {@link InvalidRequestError} is also accepted for backward
|
|
@@ -138,7 +147,7 @@ export interface AccountStore {
|
|
|
138
147
|
* Add a client & scopes to the list of authorized clients for the given account.
|
|
139
148
|
*/
|
|
140
149
|
setAuthorizedClient(
|
|
141
|
-
|
|
150
|
+
did: Did,
|
|
142
151
|
clientId: ClientId,
|
|
143
152
|
data: AuthorizedClientData,
|
|
144
153
|
): Awaitable<void>
|
|
@@ -146,7 +155,7 @@ export interface AccountStore {
|
|
|
146
155
|
/**
|
|
147
156
|
* @throws {InvalidRequestError} - When the credentials are not valid
|
|
148
157
|
*/
|
|
149
|
-
getAccount(
|
|
158
|
+
getAccount(did: Did): Awaitable<{
|
|
150
159
|
account: Account
|
|
151
160
|
authorizedClients: AuthorizedClients
|
|
152
161
|
}>
|
|
@@ -162,7 +171,7 @@ export interface AccountStore {
|
|
|
162
171
|
* {@link RequestStore.deleteRequest}), all accounts bound to that request
|
|
163
172
|
* should be deleted as well.
|
|
164
173
|
*/
|
|
165
|
-
upsertDeviceAccount(deviceId: DeviceId,
|
|
174
|
+
upsertDeviceAccount(deviceId: DeviceId, did: Did): Awaitable<void>
|
|
166
175
|
|
|
167
176
|
/**
|
|
168
177
|
* @param requestId - If provided, the result must either have the same
|
|
@@ -173,7 +182,7 @@ export interface AccountStore {
|
|
|
173
182
|
*/
|
|
174
183
|
getDeviceAccount(
|
|
175
184
|
deviceId: DeviceId,
|
|
176
|
-
|
|
185
|
+
did: Did,
|
|
177
186
|
): Awaitable<DeviceAccount | null>
|
|
178
187
|
|
|
179
188
|
/**
|
|
@@ -182,14 +191,14 @@ export interface AccountStore {
|
|
|
182
191
|
*
|
|
183
192
|
* @note Noop if the device-account is not found.
|
|
184
193
|
*/
|
|
185
|
-
removeDeviceAccount(deviceId: DeviceId,
|
|
194
|
+
removeDeviceAccount(deviceId: DeviceId, did: Did): Awaitable<void>
|
|
186
195
|
|
|
187
196
|
/**
|
|
188
197
|
* @returns **all** the device accounts that match the {@link requestId}
|
|
189
198
|
* criteria and given {@link filter}.
|
|
190
199
|
*/
|
|
191
200
|
listDeviceAccounts(
|
|
192
|
-
filter: {
|
|
201
|
+
filter: { did: Did } | { deviceId: DeviceId },
|
|
193
202
|
): Awaitable<DeviceAccount[]>
|
|
194
203
|
|
|
195
204
|
resetPasswordRequest(
|
|
@@ -206,8 +215,8 @@ export interface AccountStore {
|
|
|
206
215
|
/**
|
|
207
216
|
* Must trigger a verification email to be sent to the new email address, that
|
|
208
217
|
* will then be confirmed through {@link updateEmailConfirm}. The account's
|
|
209
|
-
*
|
|
210
|
-
* confirmed.
|
|
218
|
+
* {@link Account['emailVerified'] emailVerified} field is expected to become
|
|
219
|
+
* `false` until the new email is confirmed.
|
|
211
220
|
*/
|
|
212
221
|
updateEmailConfirm(data: UpdateEmailConfirmInput): Awaitable<Account | null>
|
|
213
222
|
|
|
@@ -225,25 +234,64 @@ export interface AccountStore {
|
|
|
225
234
|
* cannot be used
|
|
226
235
|
*/
|
|
227
236
|
updateHandle(data: UpdateHandleData): Awaitable<Account>
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Mark the account as deactivated. The account remains recoverable. Should
|
|
240
|
+
* be a no-op when the account is already deactivated.
|
|
241
|
+
*
|
|
242
|
+
* @throws {InvalidRequestError} - When the account cannot be deactivated
|
|
243
|
+
* (e.g. unknown account)
|
|
244
|
+
*/
|
|
245
|
+
deactivateAccount(data: DeactivateAccountData): Awaitable<Account>
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Reactivate a previously-deactivated account. Should be a no-op when the
|
|
249
|
+
* account is already active.
|
|
250
|
+
*
|
|
251
|
+
* @throws {InvalidRequestError} - When the account cannot be reactivated
|
|
252
|
+
* (e.g. unknown account)
|
|
253
|
+
*/
|
|
254
|
+
reactivateAccount(data: ReactivateAccountData): Awaitable<Account>
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Initiate account deletion: typically sends a confirmation token to the
|
|
258
|
+
* account's email address. The account is NOT deleted until
|
|
259
|
+
* {@link deleteAccountConfirm} is called with the matching token and the
|
|
260
|
+
* user's current password.
|
|
261
|
+
*/
|
|
262
|
+
deleteAccountRequest(data: DeleteAccountRequestInput): Awaitable<void>
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Finalize account deletion. Implementations MUST verify both the email
|
|
266
|
+
* confirmation `token` issued by {@link deleteAccountRequest} and the user's
|
|
267
|
+
* current `password` before deleting any data. Deletion is irreversible.
|
|
268
|
+
*
|
|
269
|
+
* @throws {InvalidRequestError} - When the token or password is invalid.
|
|
270
|
+
*/
|
|
271
|
+
deleteAccountConfirm(data: DeleteAccountConfirmInput): Awaitable<void>
|
|
228
272
|
}
|
|
229
273
|
|
|
230
274
|
export const isAccountStore = buildInterfaceChecker<AccountStore>([
|
|
231
|
-
'createAccount',
|
|
232
275
|
'authenticateAccount',
|
|
233
|
-
'
|
|
276
|
+
'createAccount',
|
|
277
|
+
'deactivateAccount',
|
|
278
|
+
'deleteAccountConfirm',
|
|
279
|
+
'deleteAccountRequest',
|
|
234
280
|
'getAccount',
|
|
235
|
-
'upsertDeviceAccount',
|
|
236
281
|
'getDeviceAccount',
|
|
237
|
-
'removeDeviceAccount',
|
|
238
282
|
'listDeviceAccounts',
|
|
239
|
-
'
|
|
283
|
+
'reactivateAccount',
|
|
284
|
+
'removeDeviceAccount',
|
|
240
285
|
'resetPasswordConfirm',
|
|
241
|
-
'
|
|
286
|
+
'resetPasswordRequest',
|
|
287
|
+
'setAuthorizedClient',
|
|
242
288
|
'updateEmailConfirm',
|
|
243
|
-
'
|
|
289
|
+
'updateEmailRequest',
|
|
290
|
+
'updateHandle',
|
|
291
|
+
'upsertDeviceAccount',
|
|
244
292
|
'verifyEmailConfirm',
|
|
293
|
+
'verifyEmailRequest',
|
|
245
294
|
'verifyHandleAvailability',
|
|
246
|
-
'updateHandle',
|
|
247
295
|
])
|
|
248
296
|
|
|
249
297
|
export function asAccountStore<V>(implementation: V): V & AccountStore {
|
|
@@ -18,9 +18,9 @@ export interface DeviceStore {
|
|
|
18
18
|
|
|
19
19
|
export const isDeviceStore = buildInterfaceChecker<DeviceStore>([
|
|
20
20
|
'createDevice',
|
|
21
|
+
'deleteDevice',
|
|
21
22
|
'readDevice',
|
|
22
23
|
'updateDevice',
|
|
23
|
-
'deleteDevice',
|
|
24
24
|
])
|
|
25
25
|
|
|
26
26
|
export function asDeviceStore<V>(implementation: V): V & DeviceStore {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
2
2
|
import { InvalidRequestError } from './invalid-request-error.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -6,22 +6,22 @@ import { InvalidRequestError } from './invalid-request-error.js'
|
|
|
6
6
|
* that a sign-in attempt was rejected because the provided credentials did not
|
|
7
7
|
* match a known account.
|
|
8
8
|
*
|
|
9
|
-
* Stores should populate {@link
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Stores should populate {@link did}. when the identifier resolved to an
|
|
10
|
+
* existing account but e.g. the password or OTP was incorrect. The
|
|
11
|
+
* identifier-unknown case should leave {@link did} unset. This information is
|
|
12
|
+
* surfaced to the `onSignInFailed` hook and never sent back to the client, so
|
|
13
|
+
* populating it does not affect the client-visible response.
|
|
14
14
|
*
|
|
15
15
|
* Only the subject identifier (DID) is carried — not a full `Account` — to
|
|
16
16
|
* avoid embedding PII (email, name, etc.) in an error that may be serialized
|
|
17
17
|
* by loggers or monitoring tools walking the `.cause` chain. Hook consumers
|
|
18
18
|
* that need richer profile info can resolve it from their own account store
|
|
19
|
-
* using
|
|
19
|
+
* using {@link did}.
|
|
20
20
|
*/
|
|
21
21
|
export class InvalidCredentialsError extends InvalidRequestError {
|
|
22
22
|
constructor(
|
|
23
23
|
message = 'Invalid identifier or password',
|
|
24
|
-
public readonly
|
|
24
|
+
public readonly did?: Did,
|
|
25
25
|
cause?: unknown,
|
|
26
26
|
) {
|
|
27
27
|
super(message, cause)
|
|
@@ -10,9 +10,9 @@ export interface LexiconStore {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const isLexiconStore = buildInterfaceChecker<LexiconStore>([
|
|
13
|
+
'deleteLexicon',
|
|
13
14
|
'findLexicon',
|
|
14
15
|
'storeLexicon',
|
|
15
|
-
'deleteLexicon',
|
|
16
16
|
])
|
|
17
17
|
|
|
18
18
|
export function ifLexiconStore<V extends Partial<LexiconStore>>(
|
package/src/lib/util/function.ts
CHANGED
|
@@ -16,8 +16,8 @@ export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
|
16
16
|
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
17
17
|
fn?: F,
|
|
18
18
|
...args: Parameters<F>
|
|
19
|
-
): Promise<
|
|
20
|
-
return
|
|
19
|
+
): Promise<unknown> {
|
|
20
|
+
return fn?.(...args)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function invokeOnce<T extends (this: any, ...a: any[]) => any>(
|
package/src/oauth-hooks.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
1
2
|
import { Jwks } from '@atproto/jwk'
|
|
2
3
|
import type { Account } from '@atproto/oauth-provider-api'
|
|
3
4
|
import {
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
OAuthTokenType,
|
|
10
11
|
} from '@atproto/oauth-types'
|
|
11
12
|
import {
|
|
13
|
+
DeleteAccountConfirmInput,
|
|
12
14
|
ResetPasswordConfirmInput,
|
|
13
15
|
ResetPasswordRequestInput,
|
|
14
16
|
SignUpData,
|
|
@@ -38,7 +40,6 @@ import {
|
|
|
38
40
|
} from './lib/hcaptcha.js'
|
|
39
41
|
import { RequestMetadata } from './lib/http/request.js'
|
|
40
42
|
import { Awaitable, OmitKey } from './lib/util/type.js'
|
|
41
|
-
import { Sub } from './oidc/sub.js'
|
|
42
43
|
import { RequestId } from './request/request-id.js'
|
|
43
44
|
import { AccessTokenPayload } from './signer/access-token-payload.js'
|
|
44
45
|
import { TokenClaims } from './token/token-claims.js'
|
|
@@ -55,6 +56,7 @@ export {
|
|
|
55
56
|
type ClientId,
|
|
56
57
|
type ClientInfo,
|
|
57
58
|
type DeviceId,
|
|
59
|
+
type Did,
|
|
58
60
|
type DpopProof,
|
|
59
61
|
type HcaptchaClientTokens,
|
|
60
62
|
type HcaptchaConfig,
|
|
@@ -75,7 +77,6 @@ export {
|
|
|
75
77
|
type SignInData,
|
|
76
78
|
type SignUpData,
|
|
77
79
|
type SignUpInput,
|
|
78
|
-
type Sub,
|
|
79
80
|
type TokenClaims,
|
|
80
81
|
type UpdateHandleData,
|
|
81
82
|
}
|
|
@@ -208,6 +209,87 @@ export type OAuthHooks = {
|
|
|
208
209
|
account: Account
|
|
209
210
|
}) => Awaitable<void>
|
|
210
211
|
|
|
212
|
+
/**
|
|
213
|
+
* This hook is called when a user requests account deactivation, before the
|
|
214
|
+
* account is deactivated on the account store.
|
|
215
|
+
*/
|
|
216
|
+
onDeactivateAccount?: (data: {
|
|
217
|
+
deviceId: DeviceId
|
|
218
|
+
deviceMetadata: RequestMetadata
|
|
219
|
+
account: Account
|
|
220
|
+
}) => Awaitable<void>
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* This hook is called after a user successfully deactivated their account on
|
|
224
|
+
* the account store.
|
|
225
|
+
*/
|
|
226
|
+
onDeactivatedAccount?: (data: {
|
|
227
|
+
deviceId: DeviceId
|
|
228
|
+
deviceMetadata: RequestMetadata
|
|
229
|
+
account: Account
|
|
230
|
+
}) => Awaitable<void>
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* This hook is called when a user requests account reactivation, before the
|
|
234
|
+
* account is reactivated on the account store.
|
|
235
|
+
*/
|
|
236
|
+
onReactivateAccount?: (data: {
|
|
237
|
+
deviceId: DeviceId
|
|
238
|
+
deviceMetadata: RequestMetadata
|
|
239
|
+
account: Account
|
|
240
|
+
}) => Awaitable<void>
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* This hook is called after a user successfully reactivated their account on
|
|
244
|
+
* the account store.
|
|
245
|
+
*/
|
|
246
|
+
onReactivatedAccount?: (data: {
|
|
247
|
+
deviceId: DeviceId
|
|
248
|
+
deviceMetadata: RequestMetadata
|
|
249
|
+
account: Account
|
|
250
|
+
}) => Awaitable<void>
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* This hook is called when a user requests account deletion, before the
|
|
254
|
+
* confirmation email is dispatched on the account store.
|
|
255
|
+
*/
|
|
256
|
+
onDeleteAccountRequest?: (data: {
|
|
257
|
+
deviceId: DeviceId
|
|
258
|
+
deviceMetadata: RequestMetadata
|
|
259
|
+
account: Account
|
|
260
|
+
}) => Awaitable<void>
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* This hook is called after a user has requested account deletion and the
|
|
264
|
+
* confirmation email has been dispatched.
|
|
265
|
+
*/
|
|
266
|
+
onDeleteAccountRequested?: (data: {
|
|
267
|
+
deviceId: DeviceId
|
|
268
|
+
deviceMetadata: RequestMetadata
|
|
269
|
+
account: Account
|
|
270
|
+
}) => Awaitable<void>
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* This hook is called when a user confirms account deletion, before the
|
|
274
|
+
* account is actually deleted on the account store.
|
|
275
|
+
*/
|
|
276
|
+
onDeleteAccountConfirm?: (data: {
|
|
277
|
+
deviceId: DeviceId
|
|
278
|
+
deviceMetadata: RequestMetadata
|
|
279
|
+
account: Account
|
|
280
|
+
}) => Awaitable<void>
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* This hook is called after a user has successfully deleted their account
|
|
284
|
+
* on the account store.
|
|
285
|
+
*/
|
|
286
|
+
onDeleteAccountConfirmed?: (data: {
|
|
287
|
+
deviceId: DeviceId
|
|
288
|
+
deviceMetadata: RequestMetadata
|
|
289
|
+
account: Account
|
|
290
|
+
input: DeleteAccountConfirmInput
|
|
291
|
+
}) => Awaitable<void>
|
|
292
|
+
|
|
211
293
|
/**
|
|
212
294
|
* This hook is called when a user attempts to sign up, after every validation
|
|
213
295
|
* has passed (including hcaptcha).
|
|
@@ -336,7 +418,7 @@ export type OAuthHooks = {
|
|
|
336
418
|
onSignInFailed?: (data: {
|
|
337
419
|
data: SignInData
|
|
338
420
|
error: InvalidRequestError
|
|
339
|
-
|
|
421
|
+
did: Did | null
|
|
340
422
|
deviceId: DeviceId
|
|
341
423
|
deviceMetadata: RequestMetadata
|
|
342
424
|
clientId?: ClientId
|
package/src/oauth-provider.ts
CHANGED
|
@@ -642,6 +642,10 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
642
642
|
throw new AccountSelectionRequiredError(parameters)
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
+
const ssoSessions = sessions
|
|
646
|
+
.filter(hasActiveAccount)
|
|
647
|
+
.filter(matchesHint, parameters)
|
|
648
|
+
|
|
645
649
|
// prompt=none
|
|
646
650
|
//
|
|
647
651
|
// > The Authorization Server MUST NOT display any authentication or
|
|
@@ -653,7 +657,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
653
657
|
// > Section 3.1.2.6. This can be used as a method to check for existing
|
|
654
658
|
// > authentication and/or consent.
|
|
655
659
|
if (parameters.prompt === 'none') {
|
|
656
|
-
const ssoSessions = sessions.filter(matchesHint, parameters)
|
|
657
660
|
if (ssoSessions.length > 1) {
|
|
658
661
|
throw new AccountSelectionRequiredError(parameters)
|
|
659
662
|
}
|
|
@@ -682,7 +685,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
682
685
|
|
|
683
686
|
// Automatic SSO when a hint was provided that matches a single session
|
|
684
687
|
if (parameters.prompt == null && parameters.login_hint != null) {
|
|
685
|
-
const ssoSessions = sessions.filter(matchesHint, parameters)
|
|
686
688
|
if (ssoSessions.length === 1) {
|
|
687
689
|
const ssoSession = ssoSessions[0]!
|
|
688
690
|
if (!ssoSession.loginRequired && !ssoSession.consentRequired) {
|
|
@@ -704,12 +706,14 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
704
706
|
client,
|
|
705
707
|
parameters,
|
|
706
708
|
requestUri,
|
|
707
|
-
sessions
|
|
708
|
-
|
|
709
|
+
sessions: sessions
|
|
710
|
+
// Strip un-necessary information (like consentRequired)
|
|
711
|
+
.map(({ account, loginRequired }) => ({ account, loginRequired })),
|
|
712
|
+
selectedDid:
|
|
709
713
|
parameters.prompt == null ||
|
|
710
714
|
parameters.prompt === 'login' ||
|
|
711
715
|
parameters.prompt === 'consent'
|
|
712
|
-
? sessions.find(matchesHint, parameters)?.account.
|
|
716
|
+
? sessions.find(matchesHint, parameters)?.account.did
|
|
713
717
|
: undefined,
|
|
714
718
|
permissionSets: await this.lexiconManager
|
|
715
719
|
.getPermissionSetsFromScope(parameters.scope)
|
|
@@ -884,9 +888,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
884
888
|
// As an additional security measure, we also sign the device out,
|
|
885
889
|
// so that the device cannot be used to access the account anymore
|
|
886
890
|
// without a new authentication.
|
|
887
|
-
const { deviceId,
|
|
891
|
+
const { deviceId, did } = tokenInfo.data
|
|
888
892
|
if (deviceId) {
|
|
889
|
-
await this.accountManager.removeDeviceAccount(deviceId,
|
|
893
|
+
await this.accountManager.removeDeviceAccount(deviceId, did)
|
|
890
894
|
}
|
|
891
895
|
}
|
|
892
896
|
}
|
|
@@ -912,7 +916,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
912
916
|
|
|
913
917
|
await this.validateCodeGrant(parameters, input)
|
|
914
918
|
|
|
915
|
-
const { account } = await this.accountManager.getAccount(data.
|
|
919
|
+
const { account } = await this.accountManager.getAccount(data.did)
|
|
916
920
|
|
|
917
921
|
return this.tokenManager.createToken(
|
|
918
922
|
client,
|
|
@@ -1098,5 +1102,9 @@ function matchesHint(
|
|
|
1098
1102
|
const hint = this.login_hint
|
|
1099
1103
|
if (!hint) return false
|
|
1100
1104
|
|
|
1101
|
-
return account.
|
|
1105
|
+
return account.did === hint || account.handle === hint
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function hasActiveAccount({ account }: { account: Account }): boolean {
|
|
1109
|
+
return !account.deactivated
|
|
1102
1110
|
}
|
|
@@ -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 = {
|
|
@@ -43,11 +43,11 @@ export interface RequestStore {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export const isRequestStore = buildInterfaceChecker<RequestStore>([
|
|
46
|
+
'consumeRequestCode',
|
|
46
47
|
'createRequest',
|
|
48
|
+
'deleteRequest',
|
|
47
49
|
'readRequest',
|
|
48
50
|
'updateRequest',
|
|
49
|
-
'deleteRequest',
|
|
50
|
-
'consumeRequestCode',
|
|
51
51
|
])
|
|
52
52
|
|
|
53
53
|
export function asRequestStore<V extends Partial<RequestStore>>(
|
|
@@ -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
|
},
|