@atproto/oauth-provider 0.2.16 → 0.3.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 +34 -0
- package/dist/account/account-store.d.ts +1 -1
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +6 -9
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/account.d.ts +1 -1
- package/dist/account/account.d.ts.map +1 -1
- package/dist/assets/app/bundle-manifest.json +2 -2
- package/dist/assets/app/main.js +1 -1
- package/dist/assets/app/main.js.map +1 -1
- package/dist/assets/assets-middleware.d.ts.map +1 -1
- package/dist/assets/assets-middleware.js.map +1 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +7 -6
- package/dist/assets/index.js.map +1 -1
- package/dist/client/client-auth.d.ts +1 -1
- package/dist/client/client-auth.d.ts.map +1 -1
- package/dist/client/client-auth.js +1 -1
- package/dist/client/client-auth.js.map +1 -1
- package/dist/client/client-manager.d.ts +2 -2
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +8 -10
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client-store.d.ts.map +1 -1
- package/dist/client/client-store.js.map +1 -1
- package/dist/client/client-utils.d.ts.map +1 -1
- package/dist/client/client-utils.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 +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/device/device-data.d.ts +7 -8
- package/dist/device/device-data.d.ts.map +1 -1
- package/dist/device/device-data.js +3 -2
- package/dist/device/device-data.js.map +1 -1
- package/dist/device/device-id.d.ts.map +1 -1
- package/dist/device/device-id.js.map +1 -1
- package/dist/device/device-manager.d.ts +104 -19
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js +44 -31
- package/dist/device/device-manager.js.map +1 -1
- package/dist/device/session-id.d.ts.map +1 -1
- package/dist/device/session-id.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.map +1 -1
- package/dist/errors/invalid-client-metadata-error.js +1 -1
- package/dist/errors/invalid-client-metadata-error.js.map +1 -1
- package/dist/errors/invalid-token-error.d.ts.map +1 -1
- package/dist/errors/invalid-token-error.js +1 -1
- package/dist/errors/invalid-token-error.js.map +1 -1
- package/dist/errors/www-authenticate-error.d.ts.map +1 -1
- package/dist/errors/www-authenticate-error.js.map +1 -1
- package/dist/lib/http/accept.d.ts +2 -1
- package/dist/lib/http/accept.d.ts.map +1 -1
- package/dist/lib/http/accept.js.map +1 -1
- package/dist/lib/http/method.d.ts +1 -1
- package/dist/lib/http/method.d.ts.map +1 -1
- package/dist/lib/http/middleware.d.ts +1 -1
- package/dist/lib/http/middleware.d.ts.map +1 -1
- package/dist/lib/http/request.d.ts +10 -1
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +40 -2
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/response.d.ts +3 -2
- package/dist/lib/http/response.d.ts.map +1 -1
- package/dist/lib/http/response.js.map +1 -1
- package/dist/lib/http/route.d.ts +2 -1
- package/dist/lib/http/route.d.ts.map +1 -1
- package/dist/lib/http/route.js.map +1 -1
- package/dist/lib/http/router.d.ts +2 -1
- package/dist/lib/http/router.d.ts.map +1 -1
- package/dist/lib/http/router.js.map +1 -1
- package/dist/lib/http/stream.d.ts +1 -1
- package/dist/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/http/stream.js +1 -1
- package/dist/lib/http/stream.js.map +1 -1
- package/dist/lib/http/types.d.ts +0 -1
- package/dist/lib/http/types.d.ts.map +1 -1
- package/dist/lib/util/authorization-header.d.ts.map +1 -1
- package/dist/lib/util/authorization-header.js +1 -1
- package/dist/lib/util/authorization-header.js.map +1 -1
- package/dist/lib/util/function.d.ts +8 -0
- package/dist/lib/util/function.d.ts.map +1 -1
- package/dist/lib/util/function.js +1 -1
- package/dist/lib/util/function.js.map +1 -1
- package/dist/lib/util/hostname.d.ts.map +1 -1
- package/dist/lib/util/time.d.ts +7 -1
- package/dist/lib/util/time.d.ts.map +1 -1
- package/dist/lib/util/time.js +23 -12
- package/dist/lib/util/time.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-hooks.d.ts +56 -4
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js +8 -0
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-provider.d.ts +13 -10
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +36 -58
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +1 -1
- package/dist/oauth-verifier.d.ts.map +1 -1
- package/dist/oauth-verifier.js.map +1 -1
- package/dist/output/build-authorize-data.d.ts.map +1 -1
- package/dist/output/build-authorize-data.js.map +1 -1
- package/dist/output/build-error-payload.d.ts.map +1 -1
- package/dist/output/build-error-payload.js +1 -1
- package/dist/output/build-error-payload.js.map +1 -1
- package/dist/output/output-manager.d.ts +1 -1
- package/dist/output/output-manager.d.ts.map +1 -1
- package/dist/output/output-manager.js.map +1 -1
- package/dist/output/send-authorize-redirect.d.ts +1 -1
- package/dist/output/send-authorize-redirect.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.js.map +1 -1
- package/dist/output/send-web-page.d.ts +1 -1
- package/dist/output/send-web-page.d.ts.map +1 -1
- package/dist/output/send-web-page.js.map +1 -1
- package/dist/replay/replay-store-redis.d.ts.map +1 -1
- package/dist/replay/replay-store-redis.js.map +1 -1
- package/dist/request/code.d.ts.map +1 -1
- package/dist/request/code.js.map +1 -1
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-data.js.map +1 -1
- package/dist/request/request-id.d.ts.map +1 -1
- package/dist/request/request-id.js.map +1 -1
- package/dist/request/request-info.d.ts +1 -1
- package/dist/request/request-info.d.ts.map +1 -1
- package/dist/request/request-manager.d.ts +2 -1
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +10 -2
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store-memory.d.ts +1 -1
- package/dist/request/request-store-memory.d.ts.map +1 -1
- package/dist/request/request-store-redis.d.ts +1 -1
- package/dist/request/request-store-redis.d.ts.map +1 -1
- package/dist/request/request-store-redis.js.map +1 -1
- package/dist/request/request-uri.d.ts.map +1 -1
- package/dist/request/request-uri.js.map +1 -1
- package/dist/signer/signed-token-payload.d.ts +1 -1
- package/dist/signer/signed-token-payload.d.ts.map +1 -1
- package/dist/signer/signed-token-payload.js +2 -5
- package/dist/signer/signed-token-payload.js.map +1 -1
- package/dist/signer/signer.d.ts +1 -1
- 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/refresh-token.js.map +1 -1
- package/dist/token/token-claims.d.ts +1 -1
- package/dist/token/token-claims.d.ts.map +1 -1
- package/dist/token/token-claims.js +2 -5
- package/dist/token/token-claims.js.map +1 -1
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-id.d.ts.map +1 -1
- package/dist/token/token-id.js.map +1 -1
- package/dist/token/token-manager.d.ts +3 -2
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +40 -25
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/verify-token-claims.d.ts.map +1 -1
- package/dist/token/verify-token-claims.js.map +1 -1
- package/package.json +13 -12
- package/rollup.config.js +4 -5
- package/src/account/account-store.ts +1 -2
- package/src/account/account.ts +1 -1
- package/src/assets/app/hooks/use-api.ts +1 -2
- package/src/assets/app/lib/api.ts +0 -1
- package/src/assets/assets-middleware.ts +0 -1
- package/src/assets/index.ts +2 -3
- package/src/client/client-auth.ts +1 -2
- package/src/client/client-manager.ts +22 -25
- package/src/client/client-store.ts +0 -1
- package/src/client/client-utils.ts +0 -1
- package/src/client/client.ts +13 -14
- package/src/device/device-data.ts +3 -3
- package/src/device/device-id.ts +0 -1
- package/src/device/device-manager.ts +94 -79
- package/src/device/session-id.ts +0 -1
- package/src/dpop/dpop-manager.ts +0 -2
- package/src/dpop/dpop-nonce.ts +0 -1
- package/src/errors/invalid-client-metadata-error.ts +1 -1
- package/src/errors/invalid-token-error.ts +1 -2
- package/src/errors/www-authenticate-error.ts +0 -1
- package/src/lib/http/accept.ts +2 -7
- package/src/lib/http/method.ts +1 -1
- package/src/lib/http/middleware.ts +1 -1
- package/src/lib/http/request.ts +66 -4
- package/src/lib/http/response.ts +3 -3
- package/src/lib/http/route.ts +2 -1
- package/src/lib/http/router.ts +2 -1
- package/src/lib/http/stream.ts +4 -5
- package/src/lib/http/types.ts +0 -1
- package/src/lib/util/authorization-header.ts +1 -2
- package/src/lib/util/function.ts +19 -2
- package/src/lib/util/hostname.ts +1 -1
- package/src/lib/util/time.ts +35 -18
- package/src/metadata/build-metadata.ts +0 -1
- package/src/oauth-hooks.ts +73 -14
- package/src/oauth-provider.ts +77 -37
- package/src/oauth-verifier.ts +1 -2
- package/src/output/build-authorize-data.ts +0 -1
- package/src/output/build-error-payload.ts +1 -2
- package/src/output/output-manager.ts +3 -4
- package/src/output/send-authorize-redirect.ts +1 -2
- package/src/output/send-web-page.ts +3 -4
- package/src/replay/replay-manager.ts +1 -1
- package/src/replay/replay-store-redis.ts +0 -1
- package/src/request/code.ts +0 -1
- package/src/request/request-data.ts +0 -1
- package/src/request/request-id.ts +0 -1
- package/src/request/request-info.ts +1 -1
- package/src/request/request-manager.ts +16 -6
- package/src/request/request-store-memory.ts +1 -1
- package/src/request/request-store-redis.ts +1 -2
- package/src/request/request-uri.ts +0 -1
- package/src/signer/signed-token-payload.ts +1 -2
- package/src/signer/signer.ts +1 -2
- package/src/token/refresh-token.ts +0 -1
- package/src/token/token-claims.ts +1 -2
- package/src/token/token-data.ts +0 -1
- package/src/token/token-id.ts +0 -1
- package/src/token/token-manager.ts +53 -25
- package/src/token/verify-token-claims.ts +0 -1
- package/tsconfig.backend.tsbuildinfo +1 -1
- package/dist/device/device-details.d.ts +0 -16
- package/dist/device/device-details.d.ts.map +0 -1
- package/dist/device/device-details.js +0 -34
- package/dist/device/device-details.js.map +0 -1
- package/src/device/device-details.ts +0 -43
package/src/lib/util/function.ts
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function serves two purposes:
|
|
3
|
+
* - It ensures that the return value is a Promise, even if the function returns
|
|
4
|
+
* a "thenable" (i.e. a Promise-like object).
|
|
5
|
+
* - It allows to avoid assigning a `this` context to the function, which is
|
|
6
|
+
* particularly useful when the function is a member of a "private" object.
|
|
7
|
+
*/
|
|
1
8
|
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
2
9
|
this: ThisParameterType<F>,
|
|
3
10
|
fn: F,
|
|
4
11
|
...args: Parameters<F>
|
|
5
|
-
): Promise<Awaited<ReturnType<F>>>
|
|
6
|
-
|
|
12
|
+
): Promise<Awaited<ReturnType<F>>>
|
|
13
|
+
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
14
|
+
this: ThisParameterType<F>,
|
|
15
|
+
fn?: F,
|
|
16
|
+
...args: Parameters<F>
|
|
17
|
+
): Promise<Awaited<ReturnType<F>> | undefined>
|
|
18
|
+
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
19
|
+
this: ThisParameterType<F>,
|
|
20
|
+
fn?: F,
|
|
21
|
+
...args: Parameters<F>
|
|
22
|
+
): Promise<Awaited<ReturnType<F>> | undefined> {
|
|
23
|
+
return (await fn?.(...args)) as Awaited<ReturnType<F>> | undefined
|
|
7
24
|
}
|
package/src/lib/util/hostname.ts
CHANGED
package/src/lib/util/time.ts
CHANGED
|
@@ -1,33 +1,50 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
1
2
|
import { Awaitable } from './type.js'
|
|
2
3
|
|
|
4
|
+
export function onOvertimeDefault(options: {
|
|
5
|
+
start: number
|
|
6
|
+
end: number
|
|
7
|
+
elapsed: number
|
|
8
|
+
time: number
|
|
9
|
+
}): void {
|
|
10
|
+
console.warn(
|
|
11
|
+
`constantTime: execution time was ${options.elapsed}ms (which is greater than ${options.time}ms). You should increase the "time" to properly defend against timing attacks.`,
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
/**
|
|
4
16
|
* Utility function to protect against timing attacks.
|
|
5
17
|
*/
|
|
6
|
-
export async function constantTime<T>(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
export async function constantTime<R, T = unknown>(
|
|
19
|
+
this: T,
|
|
20
|
+
time: number,
|
|
21
|
+
fn: (this: T) => Awaitable<R>,
|
|
22
|
+
onOvertime = onOvertimeDefault,
|
|
23
|
+
): Promise<R> {
|
|
24
|
+
if (!Number.isFinite(time) || time <= 0) {
|
|
25
|
+
throw new TypeError(`"time" must be a positive number`)
|
|
12
26
|
}
|
|
13
27
|
|
|
14
28
|
const start = Date.now()
|
|
15
29
|
try {
|
|
16
|
-
return await fn()
|
|
30
|
+
return await fn.call(this)
|
|
17
31
|
} finally {
|
|
18
|
-
const
|
|
32
|
+
const end = Date.now()
|
|
33
|
+
const elapsed = end - start
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
const remaining = time - elapsed
|
|
36
|
+
if (remaining >= 0) {
|
|
37
|
+
// Happy path, execution time was smaller than "time"
|
|
38
|
+
await sleep(remaining)
|
|
39
|
+
} else {
|
|
40
|
+
// The function execution took longer than "time"
|
|
41
|
+
onOvertime({ start, end, elapsed, time })
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.warn(
|
|
27
|
-
`constantTime: execution time was ${delta}ms, waiting for the next multiple of ${delay}ms. You should increase the delay to properly defend against timing attacks.`,
|
|
28
|
-
)
|
|
29
|
-
}
|
|
43
|
+
// Sleep until the next multiple of "time" to mitigate any attack
|
|
44
|
+
const multiplier = Math.ceil(elapsed / time)
|
|
45
|
+
const remaining = multiplier * time - elapsed
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
await sleep(remaining)
|
|
48
|
+
}
|
|
32
49
|
}
|
|
33
50
|
}
|
package/src/oauth-hooks.ts
CHANGED
|
@@ -5,28 +5,35 @@ import {
|
|
|
5
5
|
OAuthClientMetadata,
|
|
6
6
|
OAuthTokenResponse,
|
|
7
7
|
} from '@atproto/oauth-types'
|
|
8
|
-
|
|
9
8
|
import { Account } from './account/account.js'
|
|
10
9
|
import { ClientAuth } from './client/client-auth.js'
|
|
11
10
|
import { ClientId } from './client/client-id.js'
|
|
12
11
|
import { ClientInfo } from './client/client-info.js'
|
|
13
12
|
import { Client } from './client/client.js'
|
|
14
13
|
import { InvalidAuthorizationDetailsError } from './errors/invalid-authorization-details-error.js'
|
|
14
|
+
import { RequestMetadata } from './lib/http/request.js'
|
|
15
15
|
import { Awaitable } from './lib/util/type.js'
|
|
16
|
+
import { AccessDeniedError, OAuthError } from './oauth-errors.js'
|
|
17
|
+
import { DeviceId } from './oauth-store.js'
|
|
16
18
|
|
|
17
19
|
// Make sure all types needed to implement the OAuthHooks are exported
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
+
export {
|
|
21
|
+
AccessDeniedError,
|
|
22
|
+
type Account,
|
|
23
|
+
type Awaitable,
|
|
20
24
|
Client,
|
|
21
|
-
ClientAuth,
|
|
22
|
-
ClientId,
|
|
23
|
-
ClientInfo,
|
|
25
|
+
type ClientAuth,
|
|
26
|
+
type ClientId,
|
|
27
|
+
type ClientInfo,
|
|
28
|
+
type DeviceId,
|
|
24
29
|
InvalidAuthorizationDetailsError,
|
|
25
|
-
Jwks,
|
|
26
|
-
OAuthAuthorizationDetails,
|
|
27
|
-
OAuthAuthorizationRequestParameters,
|
|
28
|
-
OAuthClientMetadata,
|
|
29
|
-
|
|
30
|
+
type Jwks,
|
|
31
|
+
type OAuthAuthorizationDetails,
|
|
32
|
+
type OAuthAuthorizationRequestParameters,
|
|
33
|
+
type OAuthClientMetadata,
|
|
34
|
+
OAuthError,
|
|
35
|
+
type OAuthTokenResponse,
|
|
36
|
+
type RequestMetadata,
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export type OAuthHooks = {
|
|
@@ -37,10 +44,10 @@ export type OAuthHooks = {
|
|
|
37
44
|
* @throws {InvalidClientMetadataError} if the metadata is invalid
|
|
38
45
|
* @see {@link InvalidClientMetadataError}
|
|
39
46
|
*/
|
|
40
|
-
|
|
47
|
+
getClientInfo?: (
|
|
41
48
|
clientId: ClientId,
|
|
42
49
|
data: { metadata: OAuthClientMetadata; jwks?: Jwks },
|
|
43
|
-
) => Awaitable<
|
|
50
|
+
) => Awaitable<undefined | Partial<ClientInfo>>
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
53
|
* Allows enriching the authorization details with additional information
|
|
@@ -48,9 +55,61 @@ export type OAuthHooks = {
|
|
|
48
55
|
*
|
|
49
56
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc9396 | RFC 9396}
|
|
50
57
|
*/
|
|
51
|
-
|
|
58
|
+
getAuthorizationDetails?: (data: {
|
|
52
59
|
client: Client
|
|
60
|
+
clientAuth: ClientAuth
|
|
61
|
+
clientMetadata: RequestMetadata
|
|
53
62
|
parameters: OAuthAuthorizationRequestParameters
|
|
54
63
|
account: Account
|
|
55
64
|
}) => Awaitable<undefined | OAuthAuthorizationDetails>
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* This hook is called when a client is authorized.
|
|
68
|
+
*
|
|
69
|
+
* @throws {AccessDeniedError} to deny the authorization request and redirect
|
|
70
|
+
* the user to the client with an OAuth error (other errors will result in an
|
|
71
|
+
* internal server error being displayed to the user)
|
|
72
|
+
*
|
|
73
|
+
* @note We use `deviceMetadata` instead of `clientMetadata` to make it clear
|
|
74
|
+
* that this metadata is from the user device, which might be different from
|
|
75
|
+
* the client metadata (because the OAuth client could live in a backend).
|
|
76
|
+
*/
|
|
77
|
+
onAuthorized?: (data: {
|
|
78
|
+
client: Client
|
|
79
|
+
account: Account
|
|
80
|
+
parameters: OAuthAuthorizationRequestParameters
|
|
81
|
+
deviceId: DeviceId
|
|
82
|
+
deviceMetadata: RequestMetadata
|
|
83
|
+
}) => Awaitable<void>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* This hook is called when an authorized client exchanges an authorization
|
|
87
|
+
* code for an access token.
|
|
88
|
+
*
|
|
89
|
+
* @throws {OAuthError} to cancel the token creation and revoke the session
|
|
90
|
+
*/
|
|
91
|
+
onTokenCreated?: (data: {
|
|
92
|
+
client: Client
|
|
93
|
+
clientAuth: ClientAuth
|
|
94
|
+
clientMetadata: RequestMetadata
|
|
95
|
+
account: Account
|
|
96
|
+
parameters: OAuthAuthorizationRequestParameters
|
|
97
|
+
/** null when "password grant" used (in which case {@link onAuthorized} won't have been called) */
|
|
98
|
+
deviceId: null | DeviceId
|
|
99
|
+
}) => Awaitable<void>
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* This hook is called when an authorized client refreshes an access token.
|
|
103
|
+
*
|
|
104
|
+
* @throws {OAuthError} to cancel the token refresh and revoke the session
|
|
105
|
+
*/
|
|
106
|
+
onTokenRefreshed?: (data: {
|
|
107
|
+
client: Client
|
|
108
|
+
clientAuth: ClientAuth
|
|
109
|
+
clientMetadata: RequestMetadata
|
|
110
|
+
account: Account
|
|
111
|
+
parameters: OAuthAuthorizationRequestParameters
|
|
112
|
+
/** null when "password grant" used (in which case {@link onAuthorized} won't have been called) */
|
|
113
|
+
deviceId: null | DeviceId
|
|
114
|
+
}) => Awaitable<void>
|
|
56
115
|
}
|
package/src/oauth-provider.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
+
import { mediaType } from '@hapi/accept'
|
|
3
|
+
import createHttpError from 'http-errors'
|
|
4
|
+
import type { Redis, RedisOptions } from 'ioredis'
|
|
5
|
+
import { ZodError, z } from 'zod'
|
|
4
6
|
import { Jwks, Keyset } from '@atproto/jwk'
|
|
5
7
|
import {
|
|
6
8
|
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
|
@@ -29,11 +31,9 @@ import {
|
|
|
29
31
|
oauthTokenIdentificationSchema,
|
|
30
32
|
oauthTokenRequestSchema,
|
|
31
33
|
} from '@atproto/oauth-types'
|
|
32
|
-
import {
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
import z, { ZodError } from 'zod'
|
|
36
|
-
|
|
34
|
+
import { safeFetchWrap } from '@atproto-labs/fetch-node'
|
|
35
|
+
import { SimpleStore } from '@atproto-labs/simple-store'
|
|
36
|
+
import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory'
|
|
37
37
|
import { AccessTokenType } from './access-token/access-token-type.js'
|
|
38
38
|
import { AccountManager } from './account/account-manager.js'
|
|
39
39
|
import {
|
|
@@ -55,7 +55,7 @@ import { ClientStore, ifClientStore } from './client/client-store.js'
|
|
|
55
55
|
import { Client } from './client/client.js'
|
|
56
56
|
import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
|
|
57
57
|
import { DeviceId } from './device/device-id.js'
|
|
58
|
-
import { DeviceManager } from './device/device-manager.js'
|
|
58
|
+
import { DeviceManager, DeviceManagerOptions } from './device/device-manager.js'
|
|
59
59
|
import { DeviceStore, asDeviceStore } from './device/device-store.js'
|
|
60
60
|
import { AccessDeniedError } from './errors/access-denied-error.js'
|
|
61
61
|
import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
|
|
@@ -70,10 +70,8 @@ import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
|
|
|
70
70
|
import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
|
|
71
71
|
import {
|
|
72
72
|
Handler,
|
|
73
|
-
IncomingMessage,
|
|
74
73
|
Middleware,
|
|
75
74
|
Router,
|
|
76
|
-
ServerResponse,
|
|
77
75
|
combineMiddlewares,
|
|
78
76
|
parseHttpRequest,
|
|
79
77
|
setupCsrfToken,
|
|
@@ -86,6 +84,7 @@ import {
|
|
|
86
84
|
validateSameOrigin,
|
|
87
85
|
writeJson,
|
|
88
86
|
} from './lib/http/index.js'
|
|
87
|
+
import { RequestMetadata } from './lib/http/request.js'
|
|
89
88
|
import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
|
|
90
89
|
import { Override } from './lib/util/type.js'
|
|
91
90
|
import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
|
|
@@ -125,17 +124,17 @@ export type OAuthProviderStore = Partial<
|
|
|
125
124
|
>
|
|
126
125
|
|
|
127
126
|
export {
|
|
128
|
-
Keyset,
|
|
129
127
|
type CustomMetadata,
|
|
130
128
|
type Customization,
|
|
131
129
|
type Handler,
|
|
130
|
+
Keyset,
|
|
132
131
|
type OAuthAuthorizationServerMetadata,
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
export type RouterOptions<
|
|
136
135
|
Req extends IncomingMessage = IncomingMessage,
|
|
137
136
|
Res extends ServerResponse = ServerResponse,
|
|
138
|
-
> = {
|
|
137
|
+
> = DeviceManagerOptions & {
|
|
139
138
|
onError?: (req: Req, res: Res, err: unknown, message: string) => void
|
|
140
139
|
}
|
|
141
140
|
|
|
@@ -530,9 +529,10 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
530
529
|
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.1}
|
|
531
530
|
*/
|
|
532
531
|
protected async authorize(
|
|
533
|
-
|
|
534
|
-
credentials: OAuthClientCredentialsNone,
|
|
532
|
+
clientCredentials: OAuthClientCredentialsNone,
|
|
535
533
|
query: OAuthAuthorizationRequestQuery,
|
|
534
|
+
deviceId: DeviceId,
|
|
535
|
+
deviceMetadata: RequestMetadata,
|
|
536
536
|
): Promise<AuthorizationResultRedirect | AuthorizationResultAuthorize> {
|
|
537
537
|
const { issuer } = this
|
|
538
538
|
|
|
@@ -547,7 +547,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
547
547
|
: null
|
|
548
548
|
|
|
549
549
|
const client = await this.clientManager
|
|
550
|
-
.getClient(
|
|
550
|
+
.getClient(clientCredentials.client_id)
|
|
551
551
|
.catch(accessDeniedCatcher)
|
|
552
552
|
|
|
553
553
|
const { clientAuth, parameters, uri } =
|
|
@@ -581,10 +581,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
581
581
|
}
|
|
582
582
|
|
|
583
583
|
const code = await this.requestManager.setAuthorized(
|
|
584
|
-
client,
|
|
585
584
|
uri,
|
|
586
|
-
|
|
585
|
+
client,
|
|
587
586
|
ssoSession.account,
|
|
587
|
+
deviceId,
|
|
588
|
+
deviceMetadata,
|
|
588
589
|
)
|
|
589
590
|
|
|
590
591
|
return { issuer, client, parameters, redirect: { code } }
|
|
@@ -597,10 +598,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
597
598
|
const ssoSession = ssoSessions[0]!
|
|
598
599
|
if (!ssoSession.loginRequired && !ssoSession.consentRequired) {
|
|
599
600
|
const code = await this.requestManager.setAuthorized(
|
|
600
|
-
client,
|
|
601
601
|
uri,
|
|
602
|
-
|
|
602
|
+
client,
|
|
603
603
|
ssoSession.account,
|
|
604
|
+
deviceId,
|
|
605
|
+
deviceMetadata,
|
|
604
606
|
)
|
|
605
607
|
|
|
606
608
|
return { issuer, client, parameters, redirect: { code } }
|
|
@@ -721,10 +723,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
721
723
|
}
|
|
722
724
|
|
|
723
725
|
protected async acceptRequest(
|
|
724
|
-
deviceId: DeviceId,
|
|
725
726
|
uri: RequestUri,
|
|
726
727
|
clientId: ClientId,
|
|
727
728
|
sub: string,
|
|
729
|
+
deviceId: DeviceId,
|
|
730
|
+
deviceMetadata: RequestMetadata,
|
|
728
731
|
): Promise<AuthorizationResultRedirect> {
|
|
729
732
|
const { issuer } = this
|
|
730
733
|
const client = await this.clientManager.getClient(clientId)
|
|
@@ -747,10 +750,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
747
750
|
}
|
|
748
751
|
|
|
749
752
|
const code = await this.requestManager.setAuthorized(
|
|
750
|
-
client,
|
|
751
753
|
uri,
|
|
752
|
-
|
|
754
|
+
client,
|
|
753
755
|
account,
|
|
756
|
+
deviceId,
|
|
757
|
+
deviceMetadata,
|
|
754
758
|
)
|
|
755
759
|
|
|
756
760
|
await this.accountManager.addAuthorizedClient(
|
|
@@ -792,11 +796,13 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
792
796
|
}
|
|
793
797
|
|
|
794
798
|
protected async token(
|
|
795
|
-
|
|
799
|
+
clientCredentials: OAuthClientCredentials,
|
|
800
|
+
clientMetadata: RequestMetadata,
|
|
796
801
|
request: OAuthTokenRequest,
|
|
797
802
|
dpopJkt: null | string,
|
|
798
803
|
): Promise<OAuthTokenResponse> {
|
|
799
|
-
const [client, clientAuth] =
|
|
804
|
+
const [client, clientAuth] =
|
|
805
|
+
await this.authenticateClient(clientCredentials)
|
|
800
806
|
|
|
801
807
|
if (!this.metadata.grant_types_supported?.includes(request.grant_type)) {
|
|
802
808
|
throw new InvalidGrantError(
|
|
@@ -811,11 +817,23 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
811
817
|
}
|
|
812
818
|
|
|
813
819
|
if (request.grant_type === 'authorization_code') {
|
|
814
|
-
return this.codeGrant(
|
|
820
|
+
return this.codeGrant(
|
|
821
|
+
client,
|
|
822
|
+
clientAuth,
|
|
823
|
+
clientMetadata,
|
|
824
|
+
request,
|
|
825
|
+
dpopJkt,
|
|
826
|
+
)
|
|
815
827
|
}
|
|
816
828
|
|
|
817
829
|
if (request.grant_type === 'refresh_token') {
|
|
818
|
-
return this.refreshTokenGrant(
|
|
830
|
+
return this.refreshTokenGrant(
|
|
831
|
+
client,
|
|
832
|
+
clientAuth,
|
|
833
|
+
clientMetadata,
|
|
834
|
+
request,
|
|
835
|
+
dpopJkt,
|
|
836
|
+
)
|
|
819
837
|
}
|
|
820
838
|
|
|
821
839
|
throw new InvalidGrantError(
|
|
@@ -826,6 +844,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
826
844
|
protected async codeGrant(
|
|
827
845
|
client: Client,
|
|
828
846
|
clientAuth: ClientAuth,
|
|
847
|
+
clientMetadata: RequestMetadata,
|
|
829
848
|
input: OAuthAuthorizationCodeGrantTokenRequest,
|
|
830
849
|
dpopJkt: null | string,
|
|
831
850
|
): Promise<OAuthTokenResponse> {
|
|
@@ -869,6 +888,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
869
888
|
return await this.tokenManager.create(
|
|
870
889
|
client,
|
|
871
890
|
clientAuth,
|
|
891
|
+
clientMetadata,
|
|
872
892
|
account,
|
|
873
893
|
{ id: deviceId, info },
|
|
874
894
|
parameters,
|
|
@@ -891,10 +911,17 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
891
911
|
async refreshTokenGrant(
|
|
892
912
|
client: Client,
|
|
893
913
|
clientAuth: ClientAuth,
|
|
914
|
+
clientMetadata: RequestMetadata,
|
|
894
915
|
input: OAuthRefreshTokenGrantTokenRequest,
|
|
895
916
|
dpopJkt: null | string,
|
|
896
917
|
): Promise<OAuthTokenResponse> {
|
|
897
|
-
return this.tokenManager.refresh(
|
|
918
|
+
return this.tokenManager.refresh(
|
|
919
|
+
client,
|
|
920
|
+
clientAuth,
|
|
921
|
+
clientMetadata,
|
|
922
|
+
input,
|
|
923
|
+
dpopJkt,
|
|
924
|
+
)
|
|
898
925
|
}
|
|
899
926
|
|
|
900
927
|
/**
|
|
@@ -999,7 +1026,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
999
1026
|
Req extends IncomingMessage = IncomingMessage,
|
|
1000
1027
|
Res extends ServerResponse = ServerResponse,
|
|
1001
1028
|
>(options?: RouterOptions<Req, Res>) {
|
|
1002
|
-
const deviceManager = new DeviceManager(this.deviceStore)
|
|
1029
|
+
const deviceManager = new DeviceManager(this.deviceStore, options)
|
|
1003
1030
|
const outputManager = new OutputManager(this.customization)
|
|
1004
1031
|
|
|
1005
1032
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
@@ -1225,7 +1252,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
1225
1252
|
jsonHandler(async function (req, _res) {
|
|
1226
1253
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
|
1227
1254
|
|
|
1228
|
-
const
|
|
1255
|
+
const clientMetadata = await deviceManager.getRequestMetadata(req)
|
|
1256
|
+
|
|
1257
|
+
const clientCredentials = await oauthClientCredentialsSchema
|
|
1229
1258
|
.parseAsync(payload, { path: ['body'] })
|
|
1230
1259
|
.catch(throwInvalidClient)
|
|
1231
1260
|
|
|
@@ -1239,7 +1268,12 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
1239
1268
|
this.url,
|
|
1240
1269
|
)
|
|
1241
1270
|
|
|
1242
|
-
return server.token(
|
|
1271
|
+
return server.token(
|
|
1272
|
+
clientCredentials,
|
|
1273
|
+
clientMetadata,
|
|
1274
|
+
tokenRequest,
|
|
1275
|
+
dpopJkt,
|
|
1276
|
+
)
|
|
1243
1277
|
}),
|
|
1244
1278
|
)
|
|
1245
1279
|
|
|
@@ -1314,11 +1348,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
1314
1348
|
|
|
1315
1349
|
const query = Object.fromEntries(this.url.searchParams)
|
|
1316
1350
|
|
|
1317
|
-
const
|
|
1351
|
+
const clientCredentials = await oauthClientCredentialsSchema
|
|
1318
1352
|
.parseAsync(query, { path: ['body'] })
|
|
1319
1353
|
.catch(throwInvalidRequest)
|
|
1320
1354
|
|
|
1321
|
-
if ('client_secret' in
|
|
1355
|
+
if ('client_secret' in clientCredentials) {
|
|
1322
1356
|
throw new InvalidRequestError('Client secret must not be provided')
|
|
1323
1357
|
}
|
|
1324
1358
|
|
|
@@ -1326,10 +1360,15 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
1326
1360
|
.parseAsync(query, { path: ['query'] })
|
|
1327
1361
|
.catch(throwInvalidRequest)
|
|
1328
1362
|
|
|
1329
|
-
const { deviceId } = await deviceManager.load(req, res)
|
|
1363
|
+
const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
|
|
1330
1364
|
|
|
1331
1365
|
const data = await server
|
|
1332
|
-
.authorize(
|
|
1366
|
+
.authorize(
|
|
1367
|
+
clientCredentials,
|
|
1368
|
+
authorizationRequest,
|
|
1369
|
+
deviceId,
|
|
1370
|
+
deviceMetadata,
|
|
1371
|
+
)
|
|
1333
1372
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
|
1334
1373
|
|
|
1335
1374
|
switch (true) {
|
|
@@ -1432,14 +1471,15 @@ export class OAuthProvider extends OAuthVerifier {
|
|
|
1432
1471
|
true,
|
|
1433
1472
|
)
|
|
1434
1473
|
|
|
1435
|
-
const { deviceId } = await deviceManager.load(req, res)
|
|
1474
|
+
const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
|
|
1436
1475
|
|
|
1437
1476
|
const data = await server
|
|
1438
1477
|
.acceptRequest(
|
|
1439
|
-
deviceId,
|
|
1440
1478
|
input.request_uri,
|
|
1441
1479
|
input.client_id,
|
|
1442
1480
|
input.account_sub,
|
|
1481
|
+
deviceId,
|
|
1482
|
+
deviceMetadata,
|
|
1443
1483
|
)
|
|
1444
1484
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
|
1445
1485
|
|
package/src/oauth-verifier.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Redis, RedisOptions } from 'ioredis'
|
|
1
2
|
import { Key, Keyset, isSignedJwt } from '@atproto/jwk'
|
|
2
3
|
import {
|
|
3
4
|
OAuthAccessToken,
|
|
@@ -5,8 +6,6 @@ import {
|
|
|
5
6
|
OAuthTokenType,
|
|
6
7
|
oauthIssuerIdentifierSchema,
|
|
7
8
|
} from '@atproto/oauth-types'
|
|
8
|
-
import type { Redis, RedisOptions } from 'ioredis'
|
|
9
|
-
|
|
10
9
|
import { AccessTokenType } from './access-token/access-token-type.js'
|
|
11
10
|
import { DpopManager, DpopManagerOptions } from './dpop/dpop-manager.js'
|
|
12
11
|
import { DpopNonce } from './dpop/dpop-nonce.js'
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { ServerResponse } from 'node:http'
|
|
2
|
-
|
|
1
|
+
import type { ServerResponse } from 'node:http'
|
|
3
2
|
import { Asset } from '../assets/asset.js'
|
|
4
3
|
import { getAsset } from '../assets/index.js'
|
|
5
|
-
import {
|
|
4
|
+
import { Html, cssCode, html } from '../lib/html/index.js'
|
|
6
5
|
import {
|
|
7
6
|
AuthorizationResultAuthorize,
|
|
8
7
|
buildAuthorizeData,
|
|
9
8
|
} from './build-authorize-data.js'
|
|
10
9
|
import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js'
|
|
11
10
|
import {
|
|
11
|
+
Customization,
|
|
12
12
|
buildCustomizationCss,
|
|
13
13
|
buildCustomizationData,
|
|
14
|
-
Customization,
|
|
15
14
|
} from './customization.js'
|
|
16
15
|
import { declareBackendData, sendWebPage } from './send-web-page.js'
|
|
17
16
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import type { ServerResponse } from 'node:http'
|
|
1
2
|
import {
|
|
2
3
|
OAuthAuthorizationRequestParameters,
|
|
3
4
|
OAuthTokenType,
|
|
4
5
|
} from '@atproto/oauth-types'
|
|
5
|
-
import { ServerResponse } from 'node:http'
|
|
6
|
-
|
|
7
6
|
import { InvalidRequestError } from '../errors/invalid-request-error.js'
|
|
8
7
|
import { html, js } from '../lib/html/index.js'
|
|
9
8
|
import { Code } from '../request/code.js'
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
|
-
import { ServerResponse } from 'node:http'
|
|
3
|
-
|
|
2
|
+
import type { ServerResponse } from 'node:http'
|
|
4
3
|
import {
|
|
5
4
|
AssetRef,
|
|
6
|
-
buildDocument,
|
|
7
5
|
BuildDocumentOptions,
|
|
8
6
|
Html,
|
|
7
|
+
buildDocument,
|
|
9
8
|
js,
|
|
10
9
|
} from '../lib/html/index.js'
|
|
11
|
-
import {
|
|
10
|
+
import { WriteResponseOptions, writeHtml } from '../lib/http/response.js'
|
|
12
11
|
|
|
13
12
|
export function declareBackendData(name: string, data: unknown) {
|
|
14
13
|
// The script tag is removed after the data is assigned to the global variable
|
package/src/request/code.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
|
2
|
-
import { ClientId } from '../client/client-id.js'
|
|
3
2
|
import { ClientAuth } from '../client/client-auth.js'
|
|
3
|
+
import { ClientId } from '../client/client-id.js'
|
|
4
4
|
import { RequestId } from './request-id.js'
|
|
5
5
|
import { RequestUri } from './request-uri.js'
|
|
6
6
|
|