@atproto/oauth-provider 0.2.0 → 0.2.2
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 +42 -0
- package/dist/account/account-store.d.ts +2 -2
- package/dist/assets/app/bundle-manifest.json +3 -3
- package/dist/assets/app/main.css +1 -1
- package/dist/assets/app/main.js +3 -3
- 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 +4 -2
- package/dist/assets/assets-middleware.js.map +1 -1
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +127 -118
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client-utils.d.ts +1 -2
- package/dist/client/client-utils.d.ts.map +1 -1
- package/dist/client/client-utils.js +3 -12
- package/dist/client/client-utils.js.map +1 -1
- package/dist/client/client.d.ts +8 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +70 -1
- package/dist/client/client.js.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -2
- package/dist/constants.js.map +1 -1
- package/dist/errors/access-denied-error.d.ts +4 -4
- package/dist/errors/access-denied-error.d.ts.map +1 -1
- package/dist/errors/access-denied-error.js +2 -2
- package/dist/errors/access-denied-error.js.map +1 -1
- package/dist/errors/account-selection-required-error.d.ts +2 -2
- package/dist/errors/account-selection-required-error.d.ts.map +1 -1
- package/dist/errors/account-selection-required-error.js.map +1 -1
- package/dist/errors/consent-required-error.d.ts +2 -2
- package/dist/errors/consent-required-error.d.ts.map +1 -1
- package/dist/errors/consent-required-error.js.map +1 -1
- package/dist/errors/invalid-authorization-details-error.d.ts +2 -2
- package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -1
- package/dist/errors/invalid-authorization-details-error.js.map +1 -1
- package/dist/errors/invalid-client-id-error.d.ts +1 -1
- package/dist/errors/invalid-client-id-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-id-error.js +12 -6
- package/dist/errors/invalid-client-id-error.js.map +1 -1
- package/dist/errors/invalid-client-metadata-error.d.ts +1 -1
- package/dist/errors/invalid-client-metadata-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-metadata-error.js +11 -3
- package/dist/errors/invalid-client-metadata-error.js.map +1 -1
- package/dist/errors/invalid-parameters-error.d.ts +2 -2
- package/dist/errors/invalid-parameters-error.d.ts.map +1 -1
- package/dist/errors/invalid-parameters-error.js.map +1 -1
- package/dist/errors/invalid-scope-error.d.ts +9 -0
- package/dist/errors/invalid-scope-error.d.ts.map +1 -0
- package/dist/errors/invalid-scope-error.js +14 -0
- package/dist/errors/invalid-scope-error.js.map +1 -0
- package/dist/errors/login-required-error.d.ts +2 -2
- package/dist/errors/login-required-error.d.ts.map +1 -1
- package/dist/errors/login-required-error.js.map +1 -1
- package/dist/lib/html/html.d.ts +1 -1
- package/dist/lib/html/html.d.ts.map +1 -1
- package/dist/lib/html/html.js +14 -11
- package/dist/lib/html/html.js.map +1 -1
- package/dist/lib/http/parser.d.ts +9 -2
- package/dist/lib/http/parser.d.ts.map +1 -1
- package/dist/lib/http/parser.js +15 -7
- package/dist/lib/http/parser.js.map +1 -1
- package/dist/lib/http/request.d.ts +0 -23
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +1 -11
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/stream.d.ts +28 -6
- package/dist/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/http/stream.js +21 -32
- package/dist/lib/http/stream.js.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/hostname.d.ts +3 -2
- package/dist/lib/util/hostname.d.ts.map +1 -1
- package/dist/lib/util/hostname.js +12 -8
- package/dist/lib/util/hostname.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js +2 -1
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-errors.d.ts +1 -0
- package/dist/oauth-errors.d.ts.map +1 -1
- package/dist/oauth-errors.js +3 -1
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-hooks.d.ts +3 -3
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-provider.d.ts +20 -22
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +234 -176
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +2 -2
- 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 +2 -2
- package/dist/output/build-authorize-data.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.d.ts +2 -4
- package/dist/output/send-authorize-redirect.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.js +5 -2
- package/dist/output/send-authorize-redirect.js.map +1 -1
- package/dist/request/request-data.d.ts +2 -2
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-info.d.ts +2 -2
- package/dist/request/request-info.d.ts.map +1 -1
- package/dist/request/request-manager.d.ts +4 -4
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +94 -60
- package/dist/request/request-manager.js.map +1 -1
- package/dist/signer/signed-token-payload.d.ts +122 -122
- package/dist/signer/signer.d.ts +41 -40
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js +13 -15
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/token-claims.d.ts +121 -121
- package/dist/token/token-data.d.ts +3 -3
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-manager.d.ts +4 -5
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +96 -72
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/verify-token-claims.d.ts +3 -3
- package/dist/token/verify-token-claims.d.ts.map +1 -1
- package/dist/token/verify-token-claims.js.map +1 -1
- package/package.json +7 -6
- package/src/assets/app/components/sign-in-form.tsx +31 -2
- package/src/assets/app/components/url-viewer.tsx +3 -3
- package/src/assets/assets-middleware.ts +4 -2
- package/src/client/client-manager.ts +163 -161
- package/src/client/client-utils.ts +7 -12
- package/src/client/client.ts +112 -3
- package/src/constants.ts +0 -2
- package/src/errors/access-denied-error.ts +10 -4
- package/src/errors/account-selection-required-error.ts +2 -2
- package/src/errors/consent-required-error.ts +2 -2
- package/src/errors/invalid-authorization-details-error.ts +2 -2
- package/src/errors/invalid-client-id-error.ts +15 -4
- package/src/errors/invalid-client-metadata-error.ts +15 -3
- package/src/errors/invalid-parameters-error.ts +2 -2
- package/src/errors/invalid-scope-error.ts +15 -0
- package/src/errors/login-required-error.ts +2 -2
- package/src/lib/html/html.ts +14 -12
- package/src/lib/http/parser.ts +21 -8
- package/src/lib/http/request.ts +1 -23
- package/src/lib/http/stream.ts +29 -60
- package/src/lib/util/authorization-header.ts +5 -2
- package/src/lib/util/hostname.ts +9 -5
- package/src/metadata/build-metadata.ts +3 -1
- package/src/oauth-errors.ts +1 -0
- package/src/oauth-hooks.ts +3 -3
- package/src/oauth-provider.ts +368 -269
- package/src/oauth-verifier.ts +2 -2
- package/src/output/build-authorize-data.ts +2 -2
- package/src/output/send-authorize-redirect.ts +7 -6
- package/src/request/request-data.ts +2 -2
- package/src/request/request-info.ts +2 -2
- package/src/request/request-manager.ts +129 -103
- package/src/signer/signer.ts +24 -25
- package/src/token/token-data.ts +3 -3
- package/src/token/token-manager.ts +141 -99
- package/src/token/verify-token-claims.ts +3 -3
- package/dist/request/types.d.ts +0 -328
- package/dist/request/types.d.ts.map +0 -1
- package/dist/request/types.js +0 -27
- package/dist/request/types.js.map +0 -1
- package/dist/token/types.d.ts +0 -250
- package/dist/token/types.d.ts.map +0 -1
- package/dist/token/types.js +0 -36
- package/dist/token/types.js.map +0 -1
- package/src/request/types.ts +0 -48
- package/src/token/types.ts +0 -86
package/src/oauth-verifier.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Key, Keyset, isSignedJwt } from '@atproto/jwk'
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
OAuthAccessToken,
|
|
4
4
|
OAuthTokenType,
|
|
5
5
|
oauthIssuerIdentifierSchema,
|
|
6
6
|
} from '@atproto/oauth-types'
|
|
@@ -154,7 +154,7 @@ export class OAuthVerifier {
|
|
|
154
154
|
|
|
155
155
|
protected async authenticateToken(
|
|
156
156
|
tokenType: OAuthTokenType,
|
|
157
|
-
token:
|
|
157
|
+
token: OAuthAccessToken,
|
|
158
158
|
dpopJkt: string | null,
|
|
159
159
|
verifyOptions?: VerifyTokenClaimsOptions,
|
|
160
160
|
): Promise<VerifyTokenClaimsResult> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
OAuthAuthorizationRequestParameters,
|
|
3
3
|
OAuthClientMetadata,
|
|
4
4
|
} from '@atproto/oauth-types'
|
|
5
5
|
|
|
@@ -16,7 +16,7 @@ export type ScopeDetail = {
|
|
|
16
16
|
export type AuthorizationResultAuthorize = {
|
|
17
17
|
issuer: string
|
|
18
18
|
client: Client
|
|
19
|
-
parameters:
|
|
19
|
+
parameters: OAuthAuthorizationRequestParameters
|
|
20
20
|
authorize: {
|
|
21
21
|
uri: RequestUri
|
|
22
22
|
scopeDetails?: ScopeDetail[]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
OAuthAuthorizationRequestParameters,
|
|
3
3
|
OAuthTokenType,
|
|
4
4
|
} from '@atproto/oauth-types'
|
|
5
5
|
import { ServerResponse } from 'node:http'
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { InvalidRequestError } from '../errors/invalid-request-error.js'
|
|
8
8
|
import { html, js } from '../lib/html/index.js'
|
|
9
9
|
import { Code } from '../request/code.js'
|
|
10
10
|
import { sendWebPage } from './send-web-page.js'
|
|
@@ -35,8 +35,7 @@ export type AuthorizationResponseParameters = {
|
|
|
35
35
|
|
|
36
36
|
export type AuthorizationResultRedirect = {
|
|
37
37
|
issuer: string
|
|
38
|
-
|
|
39
|
-
parameters: OAuthAuthenticationRequestParameters
|
|
38
|
+
parameters: OAuthAuthorizationRequestParameters
|
|
40
39
|
redirect: AuthorizationResponseParameters
|
|
41
40
|
}
|
|
42
41
|
|
|
@@ -44,9 +43,11 @@ export async function sendAuthorizeRedirect(
|
|
|
44
43
|
res: ServerResponse,
|
|
45
44
|
result: AuthorizationResultRedirect,
|
|
46
45
|
): Promise<void> {
|
|
47
|
-
const { issuer, parameters, redirect
|
|
46
|
+
const { issuer, parameters, redirect } = result
|
|
47
|
+
|
|
48
|
+
const uri = parameters.redirect_uri
|
|
49
|
+
if (!uri) throw new InvalidRequestError('No redirect_uri')
|
|
48
50
|
|
|
49
|
-
const uri = parameters.redirect_uri || client.metadata.redirect_uris[0]
|
|
50
51
|
const mode = parameters.response_mode || 'query' // @TODO: default should depend on response_type
|
|
51
52
|
|
|
52
53
|
const entries: [string, string][] = Object.entries({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
|
2
2
|
|
|
3
3
|
import { ClientAuth } from '../client/client-auth.js'
|
|
4
4
|
import { ClientId } from '../client/client-id.js'
|
|
@@ -9,7 +9,7 @@ import { Code } from './code.js'
|
|
|
9
9
|
export type RequestData = {
|
|
10
10
|
clientId: ClientId
|
|
11
11
|
clientAuth: ClientAuth
|
|
12
|
-
parameters: Readonly<
|
|
12
|
+
parameters: Readonly<OAuthAuthorizationRequestParameters>
|
|
13
13
|
expiresAt: Date
|
|
14
14
|
deviceId: DeviceId | null
|
|
15
15
|
sub: Sub | null
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
|
2
2
|
import { ClientId } from '../client/client-id.js'
|
|
3
3
|
import { ClientAuth } from '../client/client-auth.js'
|
|
4
4
|
import { RequestId } from './request-id.js'
|
|
@@ -7,7 +7,7 @@ import { RequestUri } from './request-uri.js'
|
|
|
7
7
|
export type RequestInfo = {
|
|
8
8
|
id: RequestId
|
|
9
9
|
uri: RequestUri
|
|
10
|
-
parameters: Readonly<
|
|
10
|
+
parameters: Readonly<OAuthAuthorizationRequestParameters>
|
|
11
11
|
expiresAt: Date
|
|
12
12
|
clientId: ClientId
|
|
13
13
|
clientAuth: ClientAuth
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
|
3
|
-
|
|
3
|
+
OAuthAuthorizationRequestParameters,
|
|
4
4
|
OAuthAuthorizationServerMetadata,
|
|
5
5
|
} from '@atproto/oauth-types'
|
|
6
6
|
|
|
@@ -20,7 +20,6 @@ import { InvalidAuthorizationDetailsError } from '../errors/invalid-authorizatio
|
|
|
20
20
|
import { InvalidGrantError } from '../errors/invalid-grant-error.js'
|
|
21
21
|
import { InvalidParametersError } from '../errors/invalid-parameters-error.js'
|
|
22
22
|
import { InvalidRequestError } from '../errors/invalid-request-error.js'
|
|
23
|
-
import { compareRedirectUri } from '../lib/util/redirect-uri.js'
|
|
24
23
|
import { OAuthHooks } from '../oauth-hooks.js'
|
|
25
24
|
import { Signer } from '../signer/signer.js'
|
|
26
25
|
import { Code, generateCode } from './code.js'
|
|
@@ -36,6 +35,7 @@ import {
|
|
|
36
35
|
encodeRequestUri,
|
|
37
36
|
RequestUri,
|
|
38
37
|
} from './request-uri.js'
|
|
38
|
+
import { InvalidScopeError } from '../errors/invalid-scope-error.js'
|
|
39
39
|
|
|
40
40
|
export class RequestManager {
|
|
41
41
|
constructor(
|
|
@@ -53,7 +53,7 @@ export class RequestManager {
|
|
|
53
53
|
async createAuthorizationRequest(
|
|
54
54
|
client: Client,
|
|
55
55
|
clientAuth: ClientAuth,
|
|
56
|
-
input: Readonly<
|
|
56
|
+
input: Readonly<OAuthAuthorizationRequestParameters>,
|
|
57
57
|
deviceId: null | DeviceId,
|
|
58
58
|
dpopJkt: null | string,
|
|
59
59
|
): Promise<RequestInfo> {
|
|
@@ -64,7 +64,7 @@ export class RequestManager {
|
|
|
64
64
|
protected async create(
|
|
65
65
|
client: Client,
|
|
66
66
|
clientAuth: ClientAuth,
|
|
67
|
-
parameters: Readonly<
|
|
67
|
+
parameters: Readonly<OAuthAuthorizationRequestParameters>,
|
|
68
68
|
deviceId: null | DeviceId = null,
|
|
69
69
|
): Promise<RequestInfo> {
|
|
70
70
|
const expiresAt = new Date(Date.now() + PAR_EXPIRES_IN)
|
|
@@ -84,19 +84,23 @@ export class RequestManager {
|
|
|
84
84
|
return { id, uri, expiresAt, parameters, clientId: client.id, clientAuth }
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
async validate(
|
|
87
|
+
protected async validate(
|
|
88
88
|
client: Client,
|
|
89
89
|
clientAuth: ClientAuth,
|
|
90
|
-
parameters: Readonly<
|
|
91
|
-
|
|
92
|
-
): Promise<Readonly<
|
|
90
|
+
parameters: Readonly<OAuthAuthorizationRequestParameters>,
|
|
91
|
+
dpop_jkt: null | string,
|
|
92
|
+
): Promise<Readonly<OAuthAuthorizationRequestParameters>> {
|
|
93
|
+
// -------------------------------
|
|
94
|
+
// Validate unsupported parameters
|
|
95
|
+
// -------------------------------
|
|
96
|
+
|
|
93
97
|
for (const k of [
|
|
94
98
|
// Known unsupported OIDC parameters
|
|
95
99
|
'claims',
|
|
96
100
|
'id_token_hint',
|
|
97
101
|
'nonce', // note that OIDC "nonce" is redundant with PKCE
|
|
98
102
|
] as const) {
|
|
99
|
-
if (parameters[k]) {
|
|
103
|
+
if (parameters[k] !== undefined) {
|
|
100
104
|
throw new InvalidParametersError(
|
|
101
105
|
parameters,
|
|
102
106
|
`Unsupported "${k}" parameter`,
|
|
@@ -104,73 +108,48 @@ export class RequestManager {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'Only "code" response type is allowed',
|
|
111
|
-
)
|
|
112
|
-
}
|
|
111
|
+
// -----------------------
|
|
112
|
+
// Validate against server
|
|
113
|
+
// -----------------------
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// > (Section 3.2.3) to inform the client of the actual scope granted.
|
|
121
|
-
|
|
122
|
-
const cScopes = client.metadata.scope?.split(' ').filter(Boolean)
|
|
123
|
-
const sScopes = this.metadata.scopes_supported
|
|
124
|
-
|
|
125
|
-
const scopes = new Set(
|
|
126
|
-
parameters.scope?.split(' ').filter(Boolean) || cScopes,
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
if (scopes.has('openid')) {
|
|
130
|
-
throw new InvalidParametersError(
|
|
115
|
+
if (
|
|
116
|
+
!this.metadata.response_types_supported?.includes(
|
|
117
|
+
parameters.response_type,
|
|
118
|
+
)
|
|
119
|
+
) {
|
|
120
|
+
throw new AccessDeniedError(
|
|
131
121
|
parameters,
|
|
132
|
-
|
|
122
|
+
`Unsupported response_type "${parameters.response_type}"`,
|
|
123
|
+
'unsupported_response_type',
|
|
133
124
|
)
|
|
134
125
|
}
|
|
135
126
|
|
|
136
|
-
if (
|
|
137
|
-
|
|
127
|
+
if (
|
|
128
|
+
parameters.response_type === 'code' &&
|
|
129
|
+
!this.metadata.grant_types_supported?.includes('authorization_code')
|
|
130
|
+
) {
|
|
131
|
+
throw new AccessDeniedError(
|
|
138
132
|
parameters,
|
|
139
|
-
|
|
133
|
+
`Unsupported grant_type "authorization_code"`,
|
|
134
|
+
'unsupported_grant_type',
|
|
140
135
|
)
|
|
141
136
|
}
|
|
142
137
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// for dynamic scopes.
|
|
155
|
-
if (!sScopes?.includes(scope)) {
|
|
156
|
-
throw new InvalidParametersError(
|
|
157
|
-
parameters,
|
|
158
|
-
`Scope "${scope}" is not supported by this server`,
|
|
159
|
-
)
|
|
138
|
+
if (parameters.scope) {
|
|
139
|
+
for (const scope of parameters.scope.split(' ')) {
|
|
140
|
+
// Currently, the implementation requires all the scopes to be statically
|
|
141
|
+
// defined in the server metadata. In the future, we might add support
|
|
142
|
+
// for dynamic scopes.
|
|
143
|
+
if (!this.metadata.scopes_supported?.includes(scope)) {
|
|
144
|
+
throw new InvalidParametersError(
|
|
145
|
+
parameters,
|
|
146
|
+
`Scope "${scope}" is not supported by this server`,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
160
149
|
}
|
|
161
150
|
}
|
|
162
151
|
|
|
163
|
-
parameters = { ...parameters, scope: [...scopes].join(' ') || undefined }
|
|
164
|
-
|
|
165
152
|
if (parameters.authorization_details) {
|
|
166
|
-
const clientAuthDetailsTypes = client.metadata.authorization_details_types
|
|
167
|
-
if (!clientAuthDetailsTypes) {
|
|
168
|
-
throw new InvalidAuthorizationDetailsError(
|
|
169
|
-
parameters,
|
|
170
|
-
'Client Metadata does not declare any "authorization_details"',
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
153
|
for (const detail of parameters.authorization_details) {
|
|
175
154
|
if (
|
|
176
155
|
!this.metadata.authorization_details_types_supported?.includes(
|
|
@@ -182,35 +161,45 @@ export class RequestManager {
|
|
|
182
161
|
`Unsupported "authorization_details" type "${detail.type}"`,
|
|
183
162
|
)
|
|
184
163
|
}
|
|
185
|
-
if (!clientAuthDetailsTypes?.includes(detail.type)) {
|
|
186
|
-
throw new InvalidAuthorizationDetailsError(
|
|
187
|
-
parameters,
|
|
188
|
-
`Client Metadata does not declare any "authorization_details" of type "${detail.type}"`,
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
164
|
}
|
|
192
165
|
}
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
167
|
+
// -----------------------
|
|
168
|
+
// Validate against client
|
|
169
|
+
// -----------------------
|
|
170
|
+
|
|
171
|
+
parameters = client.validateRequest(parameters)
|
|
172
|
+
|
|
173
|
+
// -------------------
|
|
174
|
+
// Validate parameters
|
|
175
|
+
// -------------------
|
|
176
|
+
|
|
177
|
+
if (!parameters.redirect_uri) {
|
|
178
|
+
// Should already be ensured by client.validateRequest(). Adding here for
|
|
179
|
+
// clarity & extra safety.
|
|
180
|
+
throw new InvalidParametersError(parameters, 'Missing "redirect_uri"')
|
|
205
181
|
}
|
|
206
182
|
|
|
183
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#section-1.4.1
|
|
184
|
+
// > The authorization server MAY fully or partially ignore the scope
|
|
185
|
+
// > requested by the client, based on the authorization server policy or
|
|
186
|
+
// > the resource owner's instructions. If the issued access token scope is
|
|
187
|
+
// > different from the one requested by the client, the authorization
|
|
188
|
+
// > server MUST include the scope response parameter in the token response
|
|
189
|
+
// > (Section 3.2.3) to inform the client of the actual scope granted.
|
|
190
|
+
|
|
191
|
+
// Let's make sure the scopes are unique (to reduce the token & storage size)
|
|
192
|
+
const scopes = new Set(parameters.scope?.split(' '))
|
|
193
|
+
|
|
194
|
+
parameters = { ...parameters, scope: [...scopes].join(' ') || undefined }
|
|
195
|
+
|
|
207
196
|
// https://datatracker.ietf.org/doc/html/rfc9449#section-10
|
|
208
197
|
if (!parameters.dpop_jkt) {
|
|
209
|
-
if (
|
|
210
|
-
} else if (parameters.dpop_jkt !==
|
|
198
|
+
if (dpop_jkt) parameters = { ...parameters, dpop_jkt }
|
|
199
|
+
} else if (parameters.dpop_jkt !== dpop_jkt) {
|
|
211
200
|
throw new InvalidParametersError(
|
|
212
201
|
parameters,
|
|
213
|
-
'
|
|
202
|
+
'"dpop_jkt" parameters does not match the DPoP proof',
|
|
214
203
|
)
|
|
215
204
|
}
|
|
216
205
|
|
|
@@ -223,40 +212,77 @@ export class RequestManager {
|
|
|
223
212
|
}
|
|
224
213
|
}
|
|
225
214
|
|
|
226
|
-
if (
|
|
215
|
+
if (parameters.code_challenge) {
|
|
216
|
+
switch (parameters.code_challenge_method) {
|
|
217
|
+
case undefined:
|
|
218
|
+
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.3
|
|
219
|
+
parameters = { ...parameters, code_challenge_method: 'plain' }
|
|
220
|
+
// falls through
|
|
221
|
+
case 'plain':
|
|
222
|
+
case 'S256':
|
|
223
|
+
break
|
|
224
|
+
default: {
|
|
225
|
+
throw new InvalidParametersError(
|
|
226
|
+
parameters,
|
|
227
|
+
`Unsupported code_challenge_method "${parameters.code_challenge_method}"`,
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
if (parameters.code_challenge_method) {
|
|
233
|
+
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1
|
|
234
|
+
throw new InvalidParametersError(
|
|
235
|
+
parameters,
|
|
236
|
+
'code_challenge is required when code_challenge_method is provided',
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1
|
|
241
|
+
//
|
|
242
|
+
// > An AS MUST reject requests without a code_challenge from public
|
|
243
|
+
// > clients, and MUST reject such requests from other clients unless
|
|
244
|
+
// > there is reasonable assurance that the client mitigates
|
|
245
|
+
// > authorization code injection in other ways. See Section 7.5.1 for
|
|
246
|
+
// > details.
|
|
247
|
+
//
|
|
248
|
+
// > [...] In the specific deployment and the specific request, there is
|
|
249
|
+
// > reasonable assurance by the authorization server that the client
|
|
250
|
+
// > implements the OpenID Connect nonce mechanism properly.
|
|
251
|
+
//
|
|
252
|
+
// atproto does not implement the OpenID Connect nonce mechanism, so we
|
|
253
|
+
// require the use of PKCE for all clients.
|
|
254
|
+
|
|
255
|
+
throw new InvalidParametersError(parameters, 'Use of PKCE is required')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// -----------------
|
|
259
|
+
// atproto extension
|
|
260
|
+
// -----------------
|
|
261
|
+
|
|
262
|
+
if (parameters.response_type !== 'code') {
|
|
227
263
|
throw new InvalidParametersError(
|
|
228
264
|
parameters,
|
|
229
|
-
|
|
230
|
-
'unsupported_response_type',
|
|
265
|
+
'atproto only supports the "code" response_type',
|
|
231
266
|
)
|
|
232
267
|
}
|
|
233
268
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
throw new
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
parameters.code_challenge &&
|
|
242
|
-
clientAuth.method === 'none' &&
|
|
243
|
-
(parameters.code_challenge_method ?? 'plain') === 'plain'
|
|
244
|
-
) {
|
|
245
|
-
throw new InvalidParametersError(
|
|
269
|
+
if (!scopes.has('atproto')) {
|
|
270
|
+
throw new InvalidScopeError(parameters, 'The "atproto" scope is required')
|
|
271
|
+
} else if (scopes.has('openid')) {
|
|
272
|
+
throw new InvalidScopeError(
|
|
246
273
|
parameters,
|
|
247
|
-
'
|
|
274
|
+
'OpenID Connect is not compatible with atproto',
|
|
248
275
|
)
|
|
249
276
|
}
|
|
250
277
|
|
|
251
|
-
|
|
252
|
-
if (parameters.code_challenge_method && !parameters.code_challenge) {
|
|
278
|
+
if (parameters.code_challenge_method !== 'S256') {
|
|
253
279
|
throw new InvalidParametersError(
|
|
254
280
|
parameters,
|
|
255
|
-
'
|
|
281
|
+
'atproto requires use of "S256" code_challenge_method',
|
|
256
282
|
)
|
|
257
283
|
}
|
|
258
284
|
|
|
259
|
-
//
|
|
285
|
+
// atproto extension: if the client is not trusted, and not authenticated,
|
|
260
286
|
// force users to consent to authorization requests. We do this to avoid
|
|
261
287
|
// unauthenticated clients from being able to silently re-authenticate
|
|
262
288
|
// users.
|
package/src/signer/signer.ts
CHANGED
|
@@ -7,11 +7,10 @@ import {
|
|
|
7
7
|
VerifyOptions,
|
|
8
8
|
} from '@atproto/jwk'
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
OAuthAuthorizationRequestParameters,
|
|
11
11
|
OAuthAuthorizationDetails,
|
|
12
12
|
} from '@atproto/oauth-types'
|
|
13
13
|
|
|
14
|
-
import { Account } from '../account/account.js'
|
|
15
14
|
import { Client } from '../client/client.js'
|
|
16
15
|
import { dateToEpoch } from '../lib/util/date.js'
|
|
17
16
|
import { TokenId } from '../token/token-id.js'
|
|
@@ -52,9 +51,10 @@ export class Signer {
|
|
|
52
51
|
|
|
53
52
|
async accessToken(
|
|
54
53
|
client: Client,
|
|
55
|
-
parameters:
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
parameters: OAuthAuthorizationRequestParameters,
|
|
55
|
+
options: {
|
|
56
|
+
aud: string | [string, ...string[]]
|
|
57
|
+
sub: string
|
|
58
58
|
jti: TokenId
|
|
59
59
|
exp: Date
|
|
60
60
|
iat?: Date
|
|
@@ -63,26 +63,25 @@ export class Signer {
|
|
|
63
63
|
authorization_details?: OAuthAuthorizationDetails
|
|
64
64
|
},
|
|
65
65
|
): Promise<SignedJwt> {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return this.sign(header, payload)
|
|
66
|
+
return this.sign(
|
|
67
|
+
{
|
|
68
|
+
// https://datatracker.ietf.org/doc/html/rfc9068#section-2.1
|
|
69
|
+
alg: options.alg,
|
|
70
|
+
typ: 'at+jwt',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
aud: options.aud,
|
|
74
|
+
iat: dateToEpoch(options?.iat),
|
|
75
|
+
exp: dateToEpoch(options.exp),
|
|
76
|
+
sub: options.sub,
|
|
77
|
+
jti: options.jti,
|
|
78
|
+
cnf: options.cnf,
|
|
79
|
+
// https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
|
|
80
|
+
client_id: client.id,
|
|
81
|
+
scope: parameters.scope,
|
|
82
|
+
authorization_details: options.authorization_details,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
async verifyAccessToken(token: SignedJwt) {
|
package/src/token/token-data.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
OAuthAuthenticationRequestParameters,
|
|
3
2
|
OAuthAuthorizationDetails,
|
|
3
|
+
OAuthAuthorizationRequestParameters,
|
|
4
4
|
} from '@atproto/oauth-types'
|
|
5
5
|
|
|
6
6
|
import { ClientAuth } from '../client/client-auth.js'
|
|
@@ -14,8 +14,8 @@ export type {
|
|
|
14
14
|
ClientId,
|
|
15
15
|
Code,
|
|
16
16
|
DeviceId,
|
|
17
|
-
OAuthAuthenticationRequestParameters,
|
|
18
17
|
OAuthAuthorizationDetails,
|
|
18
|
+
OAuthAuthorizationRequestParameters,
|
|
19
19
|
Sub,
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ export type TokenData = {
|
|
|
27
27
|
clientAuth: ClientAuth
|
|
28
28
|
deviceId: DeviceId | null
|
|
29
29
|
sub: Sub
|
|
30
|
-
parameters:
|
|
30
|
+
parameters: OAuthAuthorizationRequestParameters
|
|
31
31
|
details: OAuthAuthorizationDetails | null
|
|
32
32
|
code: Code | null
|
|
33
33
|
}
|