@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
|
@@ -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
|
|