@atproto/oauth-provider 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +36 -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/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
|
}
|