@atproto/oauth-provider 0.2.16 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,6 +1,18 @@
|
|
1
|
+
import { Jwks, Keyset, jwksSchema } from '@atproto/jwk'
|
2
|
+
import {
|
3
|
+
OAuthAuthorizationServerMetadata,
|
4
|
+
OAuthClientIdDiscoverable,
|
5
|
+
OAuthClientIdLoopback,
|
6
|
+
OAuthClientMetadata,
|
7
|
+
OAuthClientMetadataInput,
|
8
|
+
isLoopbackHost,
|
9
|
+
isOAuthClientIdDiscoverable,
|
10
|
+
isOAuthClientIdLoopback,
|
11
|
+
oauthClientMetadataSchema,
|
12
|
+
} from '@atproto/oauth-types'
|
1
13
|
import {
|
2
|
-
bindFetch,
|
3
14
|
Fetch,
|
15
|
+
bindFetch,
|
4
16
|
fetchJsonProcessor,
|
5
17
|
fetchJsonZodProcessor,
|
6
18
|
fetchOkProcessor,
|
@@ -11,19 +23,6 @@ import {
|
|
11
23
|
GetCachedOptions,
|
12
24
|
SimpleStore,
|
13
25
|
} from '@atproto-labs/simple-store'
|
14
|
-
import { Jwks, jwksSchema, Keyset } from '@atproto/jwk'
|
15
|
-
import {
|
16
|
-
isLoopbackHost,
|
17
|
-
isOAuthClientIdDiscoverable,
|
18
|
-
isOAuthClientIdLoopback,
|
19
|
-
OAuthAuthorizationServerMetadata,
|
20
|
-
OAuthClientIdDiscoverable,
|
21
|
-
OAuthClientIdLoopback,
|
22
|
-
OAuthClientMetadata,
|
23
|
-
OAuthClientMetadataInput,
|
24
|
-
oauthClientMetadataSchema,
|
25
|
-
} from '@atproto/oauth-types'
|
26
|
-
|
27
26
|
import { InvalidClientMetadataError } from '../errors/invalid-client-metadata-error.js'
|
28
27
|
import { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'
|
29
28
|
import { callAsync } from '../lib/util/function.js'
|
@@ -111,17 +110,15 @@ export class ClientManager {
|
|
111
110
|
})
|
112
111
|
: undefined
|
113
112
|
|
114
|
-
const partialInfo = this.hooks.
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
})
|
124
|
-
: undefined
|
113
|
+
const partialInfo = await callAsync(this.hooks.getClientInfo, clientId, {
|
114
|
+
metadata,
|
115
|
+
jwks,
|
116
|
+
}).catch((err) => {
|
117
|
+
throw InvalidClientMetadataError.from(
|
118
|
+
err,
|
119
|
+
`Rejected client information for "${clientId}"`,
|
120
|
+
)
|
121
|
+
})
|
125
122
|
|
126
123
|
const isFirstParty = partialInfo?.isFirstParty ?? false
|
127
124
|
const isTrusted = partialInfo?.isTrusted ?? isFirstParty
|
@@ -2,7 +2,6 @@ import {
|
|
2
2
|
OAuthClientIdDiscoverable,
|
3
3
|
parseOAuthDiscoverableClientId,
|
4
4
|
} from '@atproto/oauth-types'
|
5
|
-
|
6
5
|
import { InvalidClientIdError } from '../errors/invalid-client-id-error.js'
|
7
6
|
import { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'
|
8
7
|
import { isInternetHost } from '../lib/util/hostname.js'
|
package/src/client/client.ts
CHANGED
@@ -1,26 +1,25 @@
|
|
1
|
-
import { Jwks } from '@atproto/jwk'
|
2
|
-
import {
|
3
|
-
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
4
|
-
OAuthAuthorizationRequestParameters,
|
5
|
-
OAuthClientCredentials,
|
6
|
-
OAuthClientMetadata,
|
7
|
-
OAuthRedirectUri,
|
8
|
-
} from '@atproto/oauth-types'
|
9
1
|
import {
|
10
|
-
UnsecuredJWT,
|
11
|
-
createLocalJWKSet,
|
12
|
-
createRemoteJWKSet,
|
13
|
-
errors,
|
14
|
-
jwtVerify,
|
15
2
|
type JWTPayload,
|
16
3
|
type JWTVerifyGetKey,
|
17
4
|
type JWTVerifyOptions,
|
18
5
|
type JWTVerifyResult,
|
19
6
|
type KeyLike,
|
20
7
|
type ResolvedKey,
|
8
|
+
UnsecuredJWT,
|
21
9
|
type UnsecuredResult,
|
10
|
+
createLocalJWKSet,
|
11
|
+
createRemoteJWKSet,
|
12
|
+
errors,
|
13
|
+
jwtVerify,
|
22
14
|
} from 'jose'
|
23
|
-
|
15
|
+
import { Jwks } from '@atproto/jwk'
|
16
|
+
import {
|
17
|
+
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
18
|
+
OAuthAuthorizationRequestParameters,
|
19
|
+
OAuthClientCredentials,
|
20
|
+
OAuthClientMetadata,
|
21
|
+
OAuthRedirectUri,
|
22
|
+
} from '@atproto/oauth-types'
|
24
23
|
import { CLIENT_ASSERTION_MAX_AGE, JAR_MAX_AGE } from '../constants.js'
|
25
24
|
import { InvalidAuthorizationDetailsError } from '../errors/invalid-authorization-details-error.js'
|
26
25
|
import { InvalidClientError } from '../errors/invalid-client-error.js'
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { z } from 'zod'
|
2
|
-
|
3
|
-
import { deviceDetailsSchema } from './device-details.js'
|
4
2
|
import { sessionIdSchema } from './session-id.js'
|
5
3
|
|
6
|
-
export const deviceDataSchema =
|
4
|
+
export const deviceDataSchema = z.object({
|
7
5
|
sessionId: sessionIdSchema,
|
8
6
|
lastSeenAt: z.date(),
|
7
|
+
userAgent: z.string().nullable(),
|
8
|
+
ipAddress: z.string(),
|
9
9
|
})
|
10
10
|
|
11
11
|
export type DeviceData = z.infer<typeof deviceDataSchema>
|
package/src/device/device-id.ts
CHANGED
@@ -1,87 +1,89 @@
|
|
1
|
-
import { IncomingMessage, ServerResponse } from 'node:http'
|
2
|
-
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
3
2
|
import { serialize as serializeCookie } from 'cookie'
|
4
|
-
import type Keygrip from 'keygrip'
|
5
3
|
import { z } from 'zod'
|
6
|
-
|
7
|
-
import { appendHeader, parseHttpCookies } from '../lib/http/index.js'
|
8
|
-
|
9
4
|
import { SESSION_FIXATION_MAX_AGE } from '../constants.js'
|
5
|
+
import { appendHeader, parseHttpCookies } from '../lib/http/index.js'
|
6
|
+
import { RequestMetadata, extractRequestMetadata } from '../lib/http/request.js'
|
10
7
|
import { DeviceData } from './device-data.js'
|
11
|
-
import { extractDeviceDetails } from './device-details.js'
|
12
8
|
import { DeviceId, deviceIdSchema, generateDeviceId } from './device-id.js'
|
13
9
|
import { DeviceStore } from './device-store.js'
|
14
10
|
import { generateSessionId, sessionIdSchema } from './session-id.js'
|
15
11
|
|
16
|
-
|
12
|
+
/**
|
13
|
+
* @see {@link https://www.npmjs.com/package/keygrip | Keygrip}
|
14
|
+
*/
|
15
|
+
export const keygripSchema = z.object({
|
16
|
+
sign: z.function().args(z.any()).returns(z.string()),
|
17
|
+
verify: z.function().args(z.any(), z.string()).returns(z.boolean()),
|
18
|
+
index: z.function().args(z.any(), z.string()).returns(z.number()),
|
19
|
+
})
|
20
|
+
|
21
|
+
export const deviceManagerOptionsSchema = z.object({
|
17
22
|
/**
|
18
23
|
* Controls whether the IP address is read from the `X-Forwarded-For` header
|
19
24
|
* (if `true`), or from the `req.socket.remoteAddress` property (if `false`).
|
20
25
|
*
|
21
26
|
* @default true // (nowadays, most requests are proxied)
|
22
27
|
*/
|
23
|
-
trustProxy: true,
|
24
|
-
|
28
|
+
trustProxy: z.boolean().default(true),
|
25
29
|
/**
|
26
30
|
* Amount of time (in ms) after which session IDs will be rotated
|
27
31
|
*
|
28
32
|
* @default 300e3 // (5 minutes)
|
29
33
|
*/
|
30
|
-
rotationRate:
|
31
|
-
|
34
|
+
rotationRate: z.number().default(300e3),
|
32
35
|
/**
|
33
36
|
* Cookie options
|
34
37
|
*/
|
35
|
-
cookie:
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
}
|
38
|
+
cookie: z
|
39
|
+
.object({
|
40
|
+
keys: keygripSchema.optional(),
|
41
|
+
/**
|
42
|
+
* Name of the cookie used to identify the device
|
43
|
+
*
|
44
|
+
* @default 'session-id'
|
45
|
+
*/
|
46
|
+
device: z.string().default('device-id'),
|
47
|
+
/**
|
48
|
+
* Name of the cookie used to identify the session
|
49
|
+
*
|
50
|
+
* @default 'session-id'
|
51
|
+
*/
|
52
|
+
session: z.string().default('session-id'),
|
53
|
+
/**
|
54
|
+
* Url path for the cookie
|
55
|
+
*
|
56
|
+
* @default '/oauth/authorize'
|
57
|
+
*/
|
58
|
+
path: z.string().default('/oauth/authorize'),
|
59
|
+
/**
|
60
|
+
* Amount of time (in ms) after which the session cookie will expire.
|
61
|
+
* If set to `null`, the cookie will be a session cookie (deleted when the
|
62
|
+
* browser is closed).
|
63
|
+
*
|
64
|
+
* @default 10 years
|
65
|
+
*/
|
66
|
+
age: z
|
67
|
+
.number()
|
68
|
+
.nullable()
|
69
|
+
.default(10 * 365.2 * 24 * 60 * 60e3),
|
70
|
+
/**
|
71
|
+
* Controls whether the cookie is only sent over HTTPS (if `true`), or also
|
72
|
+
* over HTTP (if `false`). This should **NOT** be set to `false` in
|
73
|
+
* production.
|
74
|
+
*/
|
75
|
+
secure: z.boolean().default(true),
|
76
|
+
/**
|
77
|
+
* Controls whether the cookie is sent along with cross-site requests.
|
78
|
+
*
|
79
|
+
* @default 'lax'
|
80
|
+
*/
|
81
|
+
sameSite: z.enum(['lax', 'strict']).default('lax'),
|
82
|
+
})
|
83
|
+
.default({}),
|
84
|
+
})
|
83
85
|
|
84
|
-
export type
|
86
|
+
export type DeviceManagerOptions = z.input<typeof deviceManagerOptionsSchema>
|
85
87
|
|
86
88
|
const cookieValueSchema = z.tuple([deviceIdSchema, sessionIdSchema])
|
87
89
|
type CookieValue = z.infer<typeof cookieValueSchema>
|
@@ -92,16 +94,23 @@ type CookieValue = z.infer<typeof cookieValueSchema>
|
|
92
94
|
* identify the session.
|
93
95
|
*/
|
94
96
|
export class DeviceManager {
|
97
|
+
private readonly options: z.infer<typeof deviceManagerOptionsSchema>
|
98
|
+
|
95
99
|
constructor(
|
96
100
|
private readonly store: DeviceStore,
|
97
|
-
|
98
|
-
) {
|
101
|
+
options: DeviceManagerOptions = {},
|
102
|
+
) {
|
103
|
+
this.options = deviceManagerOptionsSchema.parse(options)
|
104
|
+
}
|
99
105
|
|
100
106
|
public async load(
|
101
107
|
req: IncomingMessage,
|
102
108
|
res: ServerResponse,
|
103
109
|
forceRotate = false,
|
104
|
-
): Promise<{
|
110
|
+
): Promise<{
|
111
|
+
deviceId: DeviceId
|
112
|
+
deviceMetadata: RequestMetadata
|
113
|
+
}> {
|
105
114
|
const cookie = await this.getCookie(req)
|
106
115
|
if (cookie) {
|
107
116
|
return this.refresh(
|
@@ -118,8 +127,11 @@ export class DeviceManager {
|
|
118
127
|
private async create(
|
119
128
|
req: IncomingMessage,
|
120
129
|
res: ServerResponse,
|
121
|
-
): Promise<{
|
122
|
-
|
130
|
+
): Promise<{
|
131
|
+
deviceId: DeviceId
|
132
|
+
deviceMetadata: RequestMetadata
|
133
|
+
}> {
|
134
|
+
const deviceMetadata = this.getRequestMetadata(req)
|
123
135
|
|
124
136
|
const [deviceId, sessionId] = await Promise.all([
|
125
137
|
generateDeviceId(),
|
@@ -129,13 +141,13 @@ export class DeviceManager {
|
|
129
141
|
await this.store.createDevice(deviceId, {
|
130
142
|
sessionId,
|
131
143
|
lastSeenAt: new Date(),
|
132
|
-
userAgent,
|
133
|
-
ipAddress,
|
144
|
+
userAgent: deviceMetadata.userAgent,
|
145
|
+
ipAddress: deviceMetadata.ipAddress,
|
134
146
|
})
|
135
147
|
|
136
148
|
this.setCookie(res, [deviceId, sessionId])
|
137
149
|
|
138
|
-
return { deviceId }
|
150
|
+
return { deviceId, deviceMetadata }
|
139
151
|
}
|
140
152
|
|
141
153
|
private async refresh(
|
@@ -143,7 +155,10 @@ export class DeviceManager {
|
|
143
155
|
res: ServerResponse,
|
144
156
|
[deviceId, sessionId]: CookieValue,
|
145
157
|
forceRotate = false,
|
146
|
-
): Promise<{
|
158
|
+
): Promise<{
|
159
|
+
deviceId: DeviceId
|
160
|
+
deviceMetadata: RequestMetadata
|
161
|
+
}> {
|
147
162
|
const data = await this.store.readDevice(deviceId)
|
148
163
|
if (!data) return this.create(req, res)
|
149
164
|
|
@@ -162,24 +177,24 @@ export class DeviceManager {
|
|
162
177
|
}
|
163
178
|
}
|
164
179
|
|
165
|
-
const
|
180
|
+
const deviceMetadata = this.getRequestMetadata(req)
|
166
181
|
|
167
182
|
if (
|
168
183
|
forceRotate ||
|
169
|
-
|
170
|
-
|
184
|
+
deviceMetadata.ipAddress !== data.ipAddress ||
|
185
|
+
deviceMetadata.userAgent !== data.userAgent ||
|
171
186
|
age > this.options.rotationRate
|
172
187
|
) {
|
173
188
|
await this.rotate(req, res, deviceId, {
|
174
|
-
ipAddress:
|
175
|
-
userAgent:
|
189
|
+
ipAddress: deviceMetadata.ipAddress,
|
190
|
+
userAgent: deviceMetadata.userAgent || data.userAgent,
|
176
191
|
})
|
177
192
|
}
|
178
193
|
|
179
|
-
return { deviceId }
|
194
|
+
return { deviceId, deviceMetadata }
|
180
195
|
}
|
181
196
|
|
182
|
-
|
197
|
+
private async rotate(
|
183
198
|
req: IncomingMessage,
|
184
199
|
res: ServerResponse,
|
185
200
|
deviceId: DeviceId,
|
@@ -287,7 +302,7 @@ export class DeviceManager {
|
|
287
302
|
}
|
288
303
|
}
|
289
304
|
|
290
|
-
|
291
|
-
return
|
305
|
+
public getRequestMetadata(req: IncomingMessage) {
|
306
|
+
return extractRequestMetadata(req, this.options)
|
292
307
|
}
|
293
308
|
}
|
package/src/device/session-id.ts
CHANGED
package/src/dpop/dpop-manager.ts
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
import { createHash } from 'node:crypto'
|
2
|
-
|
3
2
|
import { EmbeddedJWK, calculateJwkThumbprint, errors, jwtVerify } from 'jose'
|
4
|
-
|
5
3
|
import { DPOP_NONCE_MAX_AGE } from '../constants.js'
|
6
4
|
import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js'
|
7
5
|
import { UseDpopNonceError } from '../errors/use-dpop-nonce-error.js'
|
package/src/dpop/dpop-nonce.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
import { JwtVerifyError } from '@atproto/jwk'
|
2
1
|
import { errors } from 'jose'
|
3
2
|
import { ZodError } from 'zod'
|
4
|
-
|
3
|
+
import { JwtVerifyError } from '@atproto/jwk'
|
5
4
|
import { OAuthError } from './oauth-error.js'
|
6
5
|
import { WWWAuthenticateError } from './www-authenticate-error.js'
|
7
6
|
|
package/src/lib/http/accept.ts
CHANGED
@@ -1,12 +1,7 @@
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
1
2
|
import { mediaType } from '@hapi/accept'
|
2
|
-
|
3
3
|
import { SubCtx, subCtx } from './context.js'
|
4
|
-
import {
|
5
|
-
IncomingMessage,
|
6
|
-
Middleware,
|
7
|
-
NextFunction,
|
8
|
-
ServerResponse,
|
9
|
-
} from './types.js'
|
4
|
+
import { Middleware, NextFunction } from './types.js'
|
10
5
|
|
11
6
|
type View<
|
12
7
|
T,
|
package/src/lib/http/method.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
2
2
|
import { writeJson } from './response.js'
|
3
|
-
import {
|
3
|
+
import { Handler, Middleware, NextFunction } from './types.js'
|
4
4
|
|
5
5
|
export function combineMiddlewares<M extends Middleware<any, any, any>>(
|
6
6
|
middlewares: Iterable<null | undefined | M>,
|
package/src/lib/http/request.ts
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
1
3
|
import { parse as parseCookie, serialize as serializeCookie } from 'cookie'
|
2
|
-
import { randomBytes } from 'crypto'
|
3
4
|
import createHttpError from 'http-errors'
|
4
|
-
|
5
5
|
import { appendHeader } from './response.js'
|
6
|
-
import {
|
7
|
-
import { urlMatch, UrlReference } from './url.js'
|
6
|
+
import { UrlReference, urlMatch } from './url.js'
|
8
7
|
|
9
8
|
export function validateHeaderValue(
|
10
9
|
req: IncomingMessage,
|
@@ -163,3 +162,66 @@ export function parseHttpCookies(
|
|
163
162
|
? ((req as any).cookies = parseCookie(req.headers['cookie']))
|
164
163
|
: null
|
165
164
|
}
|
165
|
+
|
166
|
+
export type ExtractRequestMetadataOptions = {
|
167
|
+
trustProxy?: boolean
|
168
|
+
}
|
169
|
+
|
170
|
+
export type RequestMetadata = {
|
171
|
+
userAgent: string | null
|
172
|
+
ipAddress: string
|
173
|
+
port: number
|
174
|
+
}
|
175
|
+
|
176
|
+
export function extractRequestMetadata(
|
177
|
+
req: IncomingMessage,
|
178
|
+
options?: ExtractRequestMetadataOptions,
|
179
|
+
): RequestMetadata {
|
180
|
+
const userAgent = req.headers['user-agent'] || null
|
181
|
+
const ipAddress = extractIpAddress(req, options) || null
|
182
|
+
const port = extractPort(req, options)
|
183
|
+
|
184
|
+
if (ipAddress == null || port == null) {
|
185
|
+
throw new Error('Could not determine IP address')
|
186
|
+
}
|
187
|
+
|
188
|
+
return { userAgent, ipAddress, port }
|
189
|
+
}
|
190
|
+
|
191
|
+
function extractIpAddress(
|
192
|
+
req: IncomingMessage,
|
193
|
+
options?: ExtractRequestMetadataOptions,
|
194
|
+
): string | undefined {
|
195
|
+
// Express app compatibility
|
196
|
+
if ('ip' in req && typeof req.ip === 'string') {
|
197
|
+
return req.ip
|
198
|
+
}
|
199
|
+
|
200
|
+
if (options?.trustProxy) {
|
201
|
+
const forwardedFor = req.headers['x-forwarded-for']
|
202
|
+
if (typeof forwardedFor === 'string') {
|
203
|
+
const firstForward = forwardedFor.split(',')[0]!.trim()
|
204
|
+
if (firstForward) return firstForward
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
return req.socket.remoteAddress
|
209
|
+
}
|
210
|
+
|
211
|
+
function extractPort(
|
212
|
+
req: IncomingMessage,
|
213
|
+
options?: ExtractRequestMetadataOptions,
|
214
|
+
): number | undefined {
|
215
|
+
if (options?.trustProxy) {
|
216
|
+
const forwardedPort = req.headers['x-forwarded-port']
|
217
|
+
if (typeof forwardedPort === 'string') {
|
218
|
+
const port = Number(forwardedPort.trim())
|
219
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
220
|
+
throw new Error('Invalid forwarded port')
|
221
|
+
}
|
222
|
+
return port
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
return req.socket.remotePort
|
227
|
+
}
|
package/src/lib/http/response.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
import { Handler
|
1
|
+
import type { ServerResponse } from 'node:http'
|
2
|
+
import { type Readable, pipeline } from 'node:stream'
|
3
|
+
import { Handler } from './types.js'
|
4
4
|
|
5
5
|
export function appendHeader(
|
6
6
|
res: ServerResponse,
|
package/src/lib/http/route.ts
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
1
2
|
import { SubCtx, subCtx } from './context.js'
|
2
3
|
import { MethodMatcherInput, createMethodMatcher } from './method.js'
|
3
4
|
import { combineMiddlewares } from './middleware.js'
|
4
5
|
import { Params, Path, createPathMatcher } from './path.js'
|
5
|
-
import {
|
6
|
+
import { Middleware } from './types.js'
|
6
7
|
|
7
8
|
export type RouteCtx<T, P extends Params> = SubCtx<T, { params: Readonly<P> }>
|
8
9
|
export type RouteMiddleware<
|
package/src/lib/http/router.ts
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
1
2
|
import { SubCtx, subCtx } from './context.js'
|
2
3
|
import { MethodMatcherInput } from './method.js'
|
3
4
|
import { asHandler, combineMiddlewares } from './middleware.js'
|
4
5
|
import { Params, Path } from './path.js'
|
5
6
|
import { RouteMiddleware, createRoute } from './route.js'
|
6
|
-
import {
|
7
|
+
import { Middleware } from './types.js'
|
7
8
|
|
8
9
|
export type RouterCtx<T> = SubCtx<T, { url: Readonly<URL> }>
|
9
10
|
export type RouterMiddleware<
|
package/src/lib/http/stream.ts
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
import {
|
2
|
-
import createHttpError from 'http-errors'
|
3
|
-
import { IncomingMessage } from 'node:http'
|
1
|
+
import type { IncomingMessage } from 'node:http'
|
4
2
|
import { Readable } from 'node:stream'
|
5
|
-
|
3
|
+
import createHttpError from 'http-errors'
|
4
|
+
import { decodeStream, streamToNodeBuffer } from '@atproto/common'
|
6
5
|
import {
|
7
6
|
KnownNames,
|
8
7
|
KnownParser,
|
9
|
-
parseContentType,
|
10
8
|
ParserResult,
|
9
|
+
parseContentType,
|
11
10
|
parsers,
|
12
11
|
} from './parser.js'
|
13
12
|
|
package/src/lib/http/types.ts
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
+
import { z } from 'zod'
|
1
2
|
import {
|
2
3
|
oauthAccessTokenSchema,
|
3
4
|
oauthTokenTypeSchema,
|
4
5
|
} from '@atproto/oauth-types'
|
5
|
-
import { z } from 'zod'
|
6
|
-
|
7
6
|
import { InvalidRequestError } from '../../errors/invalid-request-error.js'
|
8
7
|
import { WWWAuthenticateError } from '../../errors/www-authenticate-error.js'
|
9
8
|
|