@atproto/oauth-types 0.4.0 → 0.4.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 +21 -0
- package/dist/atproto-loopback-client-id.d.ts +14 -0
- package/dist/atproto-loopback-client-id.d.ts.map +1 -0
- package/dist/atproto-loopback-client-id.js +43 -0
- package/dist/atproto-loopback-client-id.js.map +1 -0
- package/dist/atproto-loopback-client-metadata.d.ts +8 -1
- package/dist/atproto-loopback-client-metadata.d.ts.map +1 -1
- package/dist/atproto-loopback-client-metadata.js +13 -4
- package/dist/atproto-loopback-client-metadata.js.map +1 -1
- package/dist/atproto-loopback-client-redirect-uris.d.ts +2 -0
- package/dist/atproto-loopback-client-redirect-uris.d.ts.map +1 -0
- package/dist/atproto-loopback-client-redirect-uris.js +8 -0
- package/dist/atproto-loopback-client-redirect-uris.js.map +1 -0
- package/dist/atproto-oauth-scope.d.ts +12 -0
- package/dist/atproto-oauth-scope.d.ts.map +1 -0
- package/dist/atproto-oauth-scope.js +27 -0
- package/dist/atproto-oauth-scope.js.map +1 -0
- package/dist/atproto-oauth-token-response.d.ts +106 -0
- package/dist/atproto-oauth-token-response.d.ts.map +1 -0
- package/dist/atproto-oauth-token-response.js +15 -0
- package/dist/atproto-oauth-token-response.js.map +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth-access-token.js.map +1 -1
- package/dist/oauth-authorization-code-grant-token-request.js.map +1 -1
- package/dist/oauth-authorization-details.js.map +1 -1
- package/dist/oauth-authorization-request-jar.js.map +1 -1
- package/dist/oauth-authorization-request-par.d.ts +12 -12
- package/dist/oauth-authorization-request-par.js.map +1 -1
- package/dist/oauth-authorization-request-parameters.d.ts +12 -12
- package/dist/oauth-authorization-request-parameters.js.map +1 -1
- package/dist/oauth-authorization-request-query.d.ts +12 -12
- package/dist/oauth-authorization-request-query.js.map +1 -1
- package/dist/oauth-authorization-request-uri.js.map +1 -1
- package/dist/oauth-authorization-response-error.js.map +1 -1
- package/dist/oauth-authorization-server-metadata.js.map +1 -1
- package/dist/oauth-client-credentials-grant-token-request.js.map +1 -1
- package/dist/oauth-client-credentials.js.map +1 -1
- package/dist/oauth-client-id-discoverable.js.map +1 -1
- package/dist/oauth-client-id-loopback.d.ts +24 -8
- package/dist/oauth-client-id-loopback.d.ts.map +1 -1
- package/dist/oauth-client-id-loopback.js +97 -60
- package/dist/oauth-client-id-loopback.js.map +1 -1
- package/dist/oauth-client-id.js.map +1 -1
- package/dist/oauth-client-metadata.d.ts +160 -1288
- package/dist/oauth-client-metadata.d.ts.map +1 -1
- package/dist/oauth-client-metadata.js.map +1 -1
- package/dist/oauth-code-challenge-method.js.map +1 -1
- package/dist/oauth-endpoint-auth-method.js.map +1 -1
- package/dist/oauth-endpoint-name.js.map +1 -1
- package/dist/oauth-grant-type.js.map +1 -1
- package/dist/oauth-introspection-response.js.map +1 -1
- package/dist/oauth-issuer-identifier.js.map +1 -1
- package/dist/oauth-par-response.d.ts +2 -2
- package/dist/oauth-par-response.js.map +1 -1
- package/dist/oauth-password-grant-token-request.js.map +1 -1
- package/dist/oauth-protected-resource-metadata.js.map +1 -1
- package/dist/oauth-redirect-uri.js.map +1 -1
- package/dist/oauth-refresh-token-grant-token-request.js.map +1 -1
- package/dist/oauth-refresh-token.js.map +1 -1
- package/dist/oauth-request-uri.js.map +1 -1
- package/dist/oauth-response-mode.js.map +1 -1
- package/dist/oauth-response-type.js.map +1 -1
- package/dist/oauth-scope.d.ts +5 -3
- package/dist/oauth-scope.d.ts.map +1 -1
- package/dist/oauth-scope.js +11 -8
- package/dist/oauth-scope.js.map +1 -1
- package/dist/oauth-token-identification.js.map +1 -1
- package/dist/oauth-token-request.js.map +1 -1
- package/dist/oauth-token-response.js.map +1 -1
- package/dist/oauth-token-type.js.map +1 -1
- package/dist/oidc-authorization-error-response.js.map +1 -1
- package/dist/oidc-claims-parameter.js.map +1 -1
- package/dist/oidc-claims-properties.js.map +1 -1
- package/dist/oidc-entity-type.js.map +1 -1
- package/dist/oidc-userinfo.js.map +1 -1
- package/dist/uri.js +1 -1
- package/dist/uri.js.map +1 -1
- package/dist/util.d.ts +9 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +50 -1
- package/dist/util.js.map +1 -1
- package/package.json +3 -2
- package/src/atproto-loopback-client-id.ts +75 -0
- package/src/atproto-loopback-client-metadata.ts +33 -13
- package/src/atproto-loopback-client-redirect-uris.ts +4 -0
- package/src/atproto-oauth-scope.ts +34 -0
- package/src/atproto-oauth-token-response.ts +16 -0
- package/src/index.ts +5 -1
- package/src/oauth-client-id-loopback.ts +130 -72
- package/src/oauth-scope.ts +13 -7
- package/src/uri.ts +1 -1
- package/src/util.ts +60 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
AtprotoLoopbackClientIdParams,
|
|
3
|
+
OAuthLoopbackClientIdConfig,
|
|
4
|
+
buildAtprotoLoopbackClientId,
|
|
5
|
+
parseAtprotoLoopbackClientId,
|
|
6
|
+
} from './atproto-loopback-client-id.js'
|
|
7
|
+
import { AtprotoOAuthScope } from './atproto-oauth-scope.js'
|
|
8
|
+
import { OAuthClientIdLoopback } from './oauth-client-id-loopback.js'
|
|
5
9
|
import { OAuthClientMetadataInput } from './oauth-client-metadata.js'
|
|
10
|
+
import { OAuthLoopbackRedirectURI } from './oauth-redirect-uri.js'
|
|
11
|
+
|
|
12
|
+
export type AtprotoLoopbackClientMetadata = OAuthClientMetadataInput & {
|
|
13
|
+
client_id: OAuthClientIdLoopback
|
|
14
|
+
scope: AtprotoOAuthScope
|
|
15
|
+
redirect_uris: [OAuthLoopbackRedirectURI, ...OAuthLoopbackRedirectURI[]]
|
|
16
|
+
}
|
|
6
17
|
|
|
7
18
|
export function atprotoLoopbackClientMetadata(
|
|
8
19
|
clientId: string,
|
|
9
|
-
):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
): AtprotoLoopbackClientMetadata {
|
|
21
|
+
const params = parseAtprotoLoopbackClientId(clientId)
|
|
22
|
+
// Safe to cast because parseAtprotoLoopbackClientId ensures it's a loopback ID
|
|
23
|
+
return buildMetadataInternal(clientId as OAuthClientIdLoopback, params)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildAtprotoLoopbackClientMetadata(
|
|
27
|
+
config: OAuthLoopbackClientIdConfig,
|
|
28
|
+
): AtprotoLoopbackClientMetadata {
|
|
29
|
+
const clientId = buildAtprotoLoopbackClientId(config)
|
|
30
|
+
return buildMetadataInternal(clientId, parseAtprotoLoopbackClientId(clientId))
|
|
31
|
+
}
|
|
16
32
|
|
|
33
|
+
function buildMetadataInternal(
|
|
34
|
+
clientId: OAuthClientIdLoopback,
|
|
35
|
+
clientParams: AtprotoLoopbackClientIdParams,
|
|
36
|
+
): AtprotoLoopbackClientMetadata {
|
|
17
37
|
return {
|
|
18
|
-
client_id: clientId
|
|
19
|
-
scope,
|
|
20
|
-
redirect_uris,
|
|
38
|
+
client_id: clientId,
|
|
39
|
+
scope: clientParams.scope,
|
|
40
|
+
redirect_uris: clientParams.redirect_uris,
|
|
21
41
|
response_types: ['code'],
|
|
22
42
|
grant_types: ['authorization_code', 'refresh_token'],
|
|
23
43
|
token_endpoint_auth_method: 'none',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { OAuthScope, isOAuthScope } from './oauth-scope.js'
|
|
3
|
+
import { SpaceSeparatedValue, isSpaceSeparatedValue } from './util.js'
|
|
4
|
+
|
|
5
|
+
export const ATPROTO_SCOPE_VALUE = 'atproto'
|
|
6
|
+
export type AtprotoScopeValue = typeof ATPROTO_SCOPE_VALUE
|
|
7
|
+
|
|
8
|
+
export type AtprotoOAuthScope = OAuthScope &
|
|
9
|
+
SpaceSeparatedValue<AtprotoScopeValue>
|
|
10
|
+
|
|
11
|
+
export function isAtprotoOAuthScope(input: string): input is AtprotoOAuthScope {
|
|
12
|
+
return (
|
|
13
|
+
isOAuthScope(input) && isSpaceSeparatedValue(ATPROTO_SCOPE_VALUE, input)
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function asAtprotoOAuthScope<I extends string>(input: I) {
|
|
18
|
+
if (isAtprotoOAuthScope(input)) return input
|
|
19
|
+
throw new TypeError(`Value must contain "${ATPROTO_SCOPE_VALUE}" scope value`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function assertAtprotoOAuthScope(
|
|
23
|
+
input: string,
|
|
24
|
+
): asserts input is AtprotoOAuthScope {
|
|
25
|
+
void asAtprotoOAuthScope(input)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const atprotoOAuthScopeSchema = z.string().refine(isAtprotoOAuthScope, {
|
|
29
|
+
message: 'Invalid ATProto OAuth scope',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Default scope is for reading identity (did) only
|
|
33
|
+
export const DEFAULT_ATPROTO_OAUTH_SCOPE =
|
|
34
|
+
ATPROTO_SCOPE_VALUE satisfies AtprotoOAuthScope
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TypeOf, z } from 'zod'
|
|
2
|
+
import { atprotoDidSchema } from '@atproto/did'
|
|
3
|
+
import { atprotoOAuthScopeSchema } from './atproto-oauth-scope'
|
|
4
|
+
import { oauthTokenResponseSchema } from './oauth-token-response.js'
|
|
5
|
+
|
|
6
|
+
export const atprotoOAuthTokenResponseSchema = oauthTokenResponseSchema.extend({
|
|
7
|
+
token_type: z.literal('DPoP'),
|
|
8
|
+
sub: atprotoDidSchema,
|
|
9
|
+
scope: atprotoOAuthScopeSchema,
|
|
10
|
+
// OpenID is not compatible with atproto identities
|
|
11
|
+
id_token: z.never().optional(),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export type AtprotoOAuthTokenResponse = TypeOf<
|
|
15
|
+
typeof atprotoOAuthTokenResponseSchema
|
|
16
|
+
>
|
package/src/index.ts
CHANGED
|
@@ -2,9 +2,12 @@ export * from './constants.js'
|
|
|
2
2
|
export * from './uri.js'
|
|
3
3
|
export * from './util.js'
|
|
4
4
|
|
|
5
|
+
export * from './atproto-loopback-client-id.js'
|
|
5
6
|
export * from './atproto-loopback-client-metadata.js'
|
|
7
|
+
export * from './atproto-loopback-client-redirect-uris.js'
|
|
8
|
+
export * from './atproto-oauth-scope.js'
|
|
9
|
+
export * from './atproto-oauth-token-response.js'
|
|
6
10
|
export * from './oauth-access-token.js'
|
|
7
|
-
export * from './oauth-authorization-response-error.js'
|
|
8
11
|
export * from './oauth-authorization-code-grant-token-request.js'
|
|
9
12
|
export * from './oauth-authorization-details.js'
|
|
10
13
|
export * from './oauth-authorization-request-jar.js'
|
|
@@ -12,6 +15,7 @@ export * from './oauth-authorization-request-par.js'
|
|
|
12
15
|
export * from './oauth-authorization-request-parameters.js'
|
|
13
16
|
export * from './oauth-authorization-request-query.js'
|
|
14
17
|
export * from './oauth-authorization-request-uri.js'
|
|
18
|
+
export * from './oauth-authorization-response-error.js'
|
|
15
19
|
export * from './oauth-authorization-server-metadata.js'
|
|
16
20
|
export * from './oauth-client-credentials-grant-token-request.js'
|
|
17
21
|
export * from './oauth-client-credentials.js'
|
|
@@ -1,106 +1,164 @@
|
|
|
1
|
-
import { TypeOf, ZodIssueCode } from 'zod'
|
|
2
1
|
import { oauthClientIdSchema } from './oauth-client-id.js'
|
|
3
2
|
import {
|
|
4
3
|
OAuthLoopbackRedirectURI,
|
|
5
|
-
OAuthRedirectUri,
|
|
6
4
|
oauthLoopbackRedirectURISchema,
|
|
7
5
|
} from './oauth-redirect-uri.js'
|
|
8
6
|
import { OAuthScope, oauthScopeSchema } from './oauth-scope.js'
|
|
9
7
|
|
|
10
|
-
const
|
|
8
|
+
export const LOOPBACK_CLIENT_ID_ORIGIN = 'http://localhost'
|
|
9
|
+
|
|
10
|
+
// @NOTE This is not actually based on a standard, but rather a convention
|
|
11
|
+
// established by Bluesky in the Atproto specs and implementation. As such, and
|
|
12
|
+
// in order to respect the convention from this package, these should be
|
|
13
|
+
// prefixed with "Atproto" instead of "OAuth". For legacy reasons, we keep the
|
|
14
|
+
// current names, but we should rename them in a future major release, unless
|
|
15
|
+
// loopback client ids have since then been standardized.
|
|
16
|
+
|
|
17
|
+
export type OAuthClientIdLoopback =
|
|
18
|
+
`http://localhost${'' | `/`}${'' | `?${string}`}`
|
|
19
|
+
|
|
20
|
+
export type OAuthLoopbackClientIdParams = {
|
|
21
|
+
scope?: OAuthScope
|
|
22
|
+
redirect_uris?: [OAuthLoopbackRedirectURI, ...OAuthLoopbackRedirectURI[]]
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
export const oauthClientIdLoopbackSchema = oauthClientIdSchema.superRefine(
|
|
13
|
-
(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} catch (error) {
|
|
18
|
-
ctx.addIssue({
|
|
19
|
-
code: ZodIssueCode.custom,
|
|
20
|
-
message:
|
|
21
|
-
error instanceof TypeError
|
|
22
|
-
? error.message
|
|
23
|
-
: 'Invalid loopback client ID',
|
|
24
|
-
})
|
|
25
|
-
return false
|
|
26
|
+
(input, ctx): input is OAuthClientIdLoopback => {
|
|
27
|
+
const result = safeParseOAuthLoopbackClientId(input)
|
|
28
|
+
if (!result.success) {
|
|
29
|
+
ctx.addIssue({ code: 'custom', message: result.message })
|
|
26
30
|
}
|
|
31
|
+
return result.success
|
|
27
32
|
},
|
|
28
33
|
)
|
|
29
34
|
|
|
30
|
-
export
|
|
35
|
+
export function assertOAuthLoopbackClientId(
|
|
36
|
+
input: string,
|
|
37
|
+
): asserts input is OAuthClientIdLoopback {
|
|
38
|
+
void parseOAuthLoopbackClientId(input)
|
|
39
|
+
}
|
|
31
40
|
|
|
32
|
-
export function isOAuthClientIdLoopback(
|
|
33
|
-
|
|
34
|
-
):
|
|
35
|
-
|
|
36
|
-
parseOAuthLoopbackClientId(clientId)
|
|
37
|
-
return true
|
|
38
|
-
} catch {
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
+
export function isOAuthClientIdLoopback<T extends string>(
|
|
42
|
+
input: T,
|
|
43
|
+
): input is T & OAuthClientIdLoopback {
|
|
44
|
+
return safeParseOAuthLoopbackClientId(input).success
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
export function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
void parseOAuthLoopbackClientId(clientId)
|
|
47
|
+
export function asOAuthClientIdLoopback<T extends string>(input: T) {
|
|
48
|
+
assertOAuthLoopbackClientId(input)
|
|
49
|
+
return input
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
} {
|
|
55
|
-
if (!clientId.startsWith(PREFIX)) {
|
|
56
|
-
throw new TypeError(`Loopback ClientID must start with "${PREFIX}"`)
|
|
57
|
-
} else if (clientId.includes('#', PREFIX.length)) {
|
|
58
|
-
throw new TypeError('Loopback ClientID must not contain a hash component')
|
|
59
|
-
}
|
|
52
|
+
export function parseOAuthLoopbackClientId(
|
|
53
|
+
input: string,
|
|
54
|
+
): OAuthLoopbackClientIdParams {
|
|
55
|
+
const result = safeParseOAuthLoopbackClientId(input)
|
|
56
|
+
if (result.success) return result.value
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
? PREFIX.length + 1
|
|
64
|
-
: PREFIX.length
|
|
58
|
+
throw new TypeError(`Invalid loopback client ID: ${result.message}`)
|
|
59
|
+
}
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Similar to Zod's {@link SafeParseReturnType} but uses a simple "message"
|
|
63
|
+
* string instead of an "error" Error object.
|
|
64
|
+
*/
|
|
65
|
+
type LightParseReturnType<T> =
|
|
66
|
+
| { success: true; value: T }
|
|
67
|
+
| { success: false; message: string }
|
|
68
|
+
|
|
69
|
+
export function safeParseOAuthLoopbackClientId(
|
|
70
|
+
input: string,
|
|
71
|
+
): LightParseReturnType<OAuthLoopbackClientIdParams> {
|
|
72
|
+
// @NOTE Not using "new URL" to ensure input indeed matches the type
|
|
73
|
+
// OAuthClientIdLoopback
|
|
74
|
+
|
|
75
|
+
if (!input.startsWith(LOOPBACK_CLIENT_ID_ORIGIN)) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
message: `Value must start with "${LOOPBACK_CLIENT_ID_ORIGIN}"`,
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
if (
|
|
71
|
-
|
|
82
|
+
if (input.includes('#', LOOPBACK_CLIENT_ID_ORIGIN.length)) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
message: 'Value must not contain a hash component',
|
|
86
|
+
}
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
|
|
89
|
+
// Since we don't allow a path component (except for a single "/") the query
|
|
90
|
+
// string starts after the origin (+ 1 if there is a "/")
|
|
91
|
+
const queryStringIdx =
|
|
92
|
+
input.length > LOOPBACK_CLIENT_ID_ORIGIN.length &&
|
|
93
|
+
input.charCodeAt(LOOPBACK_CLIENT_ID_ORIGIN.length) === 0x2f /* '/' */
|
|
94
|
+
? LOOPBACK_CLIENT_ID_ORIGIN.length + 1
|
|
95
|
+
: LOOPBACK_CLIENT_ID_ORIGIN.length
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
// Since we determined the position of the query string based on the origin
|
|
98
|
+
// length (instead of looking for a "?"), we need to make sure the query
|
|
99
|
+
// string position (if any) indeed starts with a "?".
|
|
100
|
+
if (
|
|
101
|
+
input.length !== queryStringIdx &&
|
|
102
|
+
input.charCodeAt(queryStringIdx) !== 0x3f /* '?' */
|
|
103
|
+
) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
message: 'Value must not contain a path component',
|
|
79
107
|
}
|
|
80
108
|
}
|
|
81
109
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
const queryString = input.slice(queryStringIdx + 1)
|
|
111
|
+
return safeParseOAuthLoopbackClientIdQueryString(queryString)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function safeParseOAuthLoopbackClientIdQueryString(
|
|
115
|
+
input: string | Iterable<[key: string, value: string]>,
|
|
116
|
+
): LightParseReturnType<OAuthLoopbackClientIdParams> {
|
|
117
|
+
// Parse query params
|
|
118
|
+
const params: OAuthLoopbackClientIdParams = {}
|
|
119
|
+
|
|
120
|
+
const it = typeof input === 'string' ? new URLSearchParams(input) : input
|
|
121
|
+
for (const [key, value] of it) {
|
|
122
|
+
if (key === 'scope') {
|
|
123
|
+
if ('scope' in params) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
message: 'Duplicate "scope" query parameter',
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const res = oauthScopeSchema.safeParse(value)
|
|
131
|
+
if (!res.success) {
|
|
132
|
+
const reason = res.error.issues.map((i) => i.message).join(', ')
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
message: `Invalid "scope" query parameter: ${reason || 'Validation failed'}`,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
params.scope = res.data
|
|
140
|
+
} else if (key === 'redirect_uri') {
|
|
141
|
+
const res = oauthLoopbackRedirectURISchema.safeParse(value)
|
|
142
|
+
if (!res.success) {
|
|
143
|
+
const reason = res.error.issues.map((i) => i.message).join(', ')
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
message: `Invalid "redirect_uri" query parameter: ${reason || 'Validation failed'}`,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (params.redirect_uris == null) params.redirect_uris = [res.data]
|
|
151
|
+
else params.redirect_uris.push(res.data)
|
|
152
|
+
} else {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
message: `Unexpected query parameter "${key}"`,
|
|
156
|
+
}
|
|
90
157
|
}
|
|
91
158
|
}
|
|
92
159
|
|
|
93
|
-
const redirect_uris = searchParams.has('redirect_uri')
|
|
94
|
-
? (searchParams
|
|
95
|
-
.getAll('redirect_uri')
|
|
96
|
-
.map((value) => oauthLoopbackRedirectURISchema.parse(value)) as [
|
|
97
|
-
OAuthLoopbackRedirectURI,
|
|
98
|
-
...OAuthLoopbackRedirectURI[],
|
|
99
|
-
])
|
|
100
|
-
: undefined
|
|
101
|
-
|
|
102
160
|
return {
|
|
103
|
-
|
|
104
|
-
|
|
161
|
+
success: true,
|
|
162
|
+
value: params,
|
|
105
163
|
}
|
|
106
164
|
}
|
package/src/oauth-scope.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
|
|
3
|
+
// scope = scope-token *( SP scope-token )
|
|
4
|
+
// scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
|
|
5
|
+
export const OAUTH_SCOPE_REGEXP =
|
|
6
|
+
/^[\x21\x23-\x5B\x5D-\x7E]+(?: [\x21\x23-\x5B\x5D-\x7E]+)*$/
|
|
7
|
+
|
|
8
|
+
export const isOAuthScope = (input: string): boolean =>
|
|
9
|
+
OAUTH_SCOPE_REGEXP.test(input)
|
|
10
|
+
|
|
3
11
|
/**
|
|
4
|
-
* A space separated list of
|
|
5
|
-
* and double quote.
|
|
12
|
+
* A (single) space separated list of non empty printable ASCII char string
|
|
13
|
+
* (except backslash and double quote).
|
|
6
14
|
*
|
|
7
15
|
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-1.4.1}
|
|
8
16
|
*/
|
|
9
|
-
export const oauthScopeSchema = z
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
|
|
13
|
-
.regex(/^[\x21\x23-\x5B\x5D-\x7E]+(?: [\x21\x23-\x5B\x5D-\x7E]+)*$/)
|
|
17
|
+
export const oauthScopeSchema = z.string().refine(isOAuthScope, {
|
|
18
|
+
message: 'Invalid OAuth scope',
|
|
19
|
+
})
|
|
14
20
|
|
|
15
21
|
export type OAuthScope = z.infer<typeof oauthScopeSchema>
|
package/src/uri.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { isHostnameIP, isLoopbackHost } from './util.js'
|
|
|
3
3
|
|
|
4
4
|
const canParseUrl =
|
|
5
5
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
6
|
-
URL.canParse ??
|
|
6
|
+
URL.canParse?.bind(URL) ??
|
|
7
7
|
// URL.canParse is not available in Node.js < 18.7.0
|
|
8
8
|
((urlStr: string): boolean => {
|
|
9
9
|
try {
|
package/src/util.ts
CHANGED
|
@@ -86,3 +86,63 @@ export const numberPreprocess = (val: unknown): unknown => {
|
|
|
86
86
|
}
|
|
87
87
|
return val
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns true if the two arrays contain the same elements, regardless of order
|
|
92
|
+
* or duplicates.
|
|
93
|
+
*/
|
|
94
|
+
export function arrayEquivalent<T>(a: readonly T[], b: readonly T[]) {
|
|
95
|
+
if (a === b) return true
|
|
96
|
+
return a.every(includedIn, b) && b.every(includedIn, a)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function includedIn<T>(this: readonly T[], item: T) {
|
|
100
|
+
return this.includes(item)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function asArray<T>(
|
|
104
|
+
value: Iterable<T> | undefined,
|
|
105
|
+
): undefined | readonly T[] {
|
|
106
|
+
if (value == null) return undefined
|
|
107
|
+
if (Array.isArray(value)) return value // already a (possibly readonly) array
|
|
108
|
+
return Array.from(value)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type SpaceSeparatedValue<Value extends string> =
|
|
112
|
+
`${'' | `${string} `}${Value}${'' | ` ${string}`}`
|
|
113
|
+
|
|
114
|
+
export const isSpaceSeparatedValue = <Value extends string>(
|
|
115
|
+
value: Value,
|
|
116
|
+
input: string,
|
|
117
|
+
): input is SpaceSeparatedValue<Value> => {
|
|
118
|
+
if (value.length === 0) throw new TypeError('Value cannot be empty')
|
|
119
|
+
if (value.includes(' ')) throw new TypeError('Value cannot contain spaces')
|
|
120
|
+
|
|
121
|
+
// Optimized version of:
|
|
122
|
+
// return input.split(' ').includes(value)
|
|
123
|
+
|
|
124
|
+
const inputLength = input.length
|
|
125
|
+
const valueLength = value.length
|
|
126
|
+
|
|
127
|
+
if (inputLength < valueLength) return false
|
|
128
|
+
|
|
129
|
+
let idx = input.indexOf(value)
|
|
130
|
+
let idxEnd: number
|
|
131
|
+
|
|
132
|
+
while (idx !== -1) {
|
|
133
|
+
idxEnd = idx + valueLength
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
// at beginning or preceded by space
|
|
137
|
+
(idx === 0 || input.charCodeAt(idx - 1) === 32) &&
|
|
138
|
+
// at end or followed by space
|
|
139
|
+
(idxEnd === inputLength || input.charCodeAt(idxEnd) === 32)
|
|
140
|
+
) {
|
|
141
|
+
return true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
idx = input.indexOf(value, idxEnd + 1)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/atproto-loopback-client-metadata.ts","./src/constants.ts","./src/index.ts","./src/oauth-access-token.ts","./src/oauth-authorization-code-grant-token-request.ts","./src/oauth-authorization-details.ts","./src/oauth-authorization-request-jar.ts","./src/oauth-authorization-request-par.ts","./src/oauth-authorization-request-parameters.ts","./src/oauth-authorization-request-query.ts","./src/oauth-authorization-request-uri.ts","./src/oauth-authorization-response-error.ts","./src/oauth-authorization-server-metadata.ts","./src/oauth-client-credentials-grant-token-request.ts","./src/oauth-client-credentials.ts","./src/oauth-client-id-discoverable.ts","./src/oauth-client-id-loopback.ts","./src/oauth-client-id.ts","./src/oauth-client-metadata.ts","./src/oauth-code-challenge-method.ts","./src/oauth-endpoint-auth-method.ts","./src/oauth-endpoint-name.ts","./src/oauth-grant-type.ts","./src/oauth-introspection-response.ts","./src/oauth-issuer-identifier.ts","./src/oauth-par-response.ts","./src/oauth-password-grant-token-request.ts","./src/oauth-protected-resource-metadata.ts","./src/oauth-redirect-uri.ts","./src/oauth-refresh-token-grant-token-request.ts","./src/oauth-refresh-token.ts","./src/oauth-request-uri.ts","./src/oauth-response-mode.ts","./src/oauth-response-type.ts","./src/oauth-scope.ts","./src/oauth-token-identification.ts","./src/oauth-token-request.ts","./src/oauth-token-response.ts","./src/oauth-token-type.ts","./src/oidc-authorization-error-response.ts","./src/oidc-claims-parameter.ts","./src/oidc-claims-properties.ts","./src/oidc-entity-type.ts","./src/oidc-userinfo.ts","./src/uri.ts","./src/util.ts"],"version":"5.8.2"}
|
|
1
|
+
{"root":["./src/atproto-loopback-client-id.ts","./src/atproto-loopback-client-metadata.ts","./src/atproto-loopback-client-redirect-uris.ts","./src/atproto-oauth-scope.ts","./src/atproto-oauth-token-response.ts","./src/constants.ts","./src/index.ts","./src/oauth-access-token.ts","./src/oauth-authorization-code-grant-token-request.ts","./src/oauth-authorization-details.ts","./src/oauth-authorization-request-jar.ts","./src/oauth-authorization-request-par.ts","./src/oauth-authorization-request-parameters.ts","./src/oauth-authorization-request-query.ts","./src/oauth-authorization-request-uri.ts","./src/oauth-authorization-response-error.ts","./src/oauth-authorization-server-metadata.ts","./src/oauth-client-credentials-grant-token-request.ts","./src/oauth-client-credentials.ts","./src/oauth-client-id-discoverable.ts","./src/oauth-client-id-loopback.ts","./src/oauth-client-id.ts","./src/oauth-client-metadata.ts","./src/oauth-code-challenge-method.ts","./src/oauth-endpoint-auth-method.ts","./src/oauth-endpoint-name.ts","./src/oauth-grant-type.ts","./src/oauth-introspection-response.ts","./src/oauth-issuer-identifier.ts","./src/oauth-par-response.ts","./src/oauth-password-grant-token-request.ts","./src/oauth-protected-resource-metadata.ts","./src/oauth-redirect-uri.ts","./src/oauth-refresh-token-grant-token-request.ts","./src/oauth-refresh-token.ts","./src/oauth-request-uri.ts","./src/oauth-response-mode.ts","./src/oauth-response-type.ts","./src/oauth-scope.ts","./src/oauth-token-identification.ts","./src/oauth-token-request.ts","./src/oauth-token-response.ts","./src/oauth-token-type.ts","./src/oidc-authorization-error-response.ts","./src/oidc-claims-parameter.ts","./src/oidc-claims-properties.ts","./src/oidc-entity-type.ts","./src/oidc-userinfo.ts","./src/uri.ts","./src/util.ts"],"version":"5.8.2"}
|