@atproto/oauth-types 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +10 -0
- package/dist/atproto-loopback-client-metadata.d.ts +4 -1
- package/dist/atproto-loopback-client-metadata.d.ts.map +1 -1
- package/dist/atproto-loopback-client-metadata.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/oauth-authorization-code-grant-token-request.d.ts +2 -2
- package/dist/oauth-authorization-code-grant-token-request.d.ts.map +1 -1
- package/dist/oauth-authorization-code-grant-token-request.js +2 -1
- package/dist/oauth-authorization-code-grant-token-request.js.map +1 -1
- package/dist/oauth-authorization-details.d.ts +42 -4
- package/dist/oauth-authorization-details.d.ts.map +1 -1
- package/dist/oauth-authorization-details.js +21 -1
- package/dist/oauth-authorization-details.js.map +1 -1
- package/dist/oauth-authorization-request-jar.d.ts +1 -1
- package/dist/oauth-authorization-request-par.d.ts +8 -8
- package/dist/oauth-authorization-request-parameters.d.ts +7 -7
- package/dist/oauth-authorization-request-parameters.d.ts.map +1 -1
- package/dist/oauth-authorization-request-parameters.js +2 -1
- package/dist/oauth-authorization-request-parameters.js.map +1 -1
- package/dist/oauth-authorization-request-query.d.ts +8 -8
- package/dist/oauth-authorization-server-metadata.d.ts +69 -66
- package/dist/oauth-authorization-server-metadata.d.ts.map +1 -1
- package/dist/oauth-authorization-server-metadata.js +14 -10
- package/dist/oauth-authorization-server-metadata.js.map +1 -1
- package/dist/oauth-client-id-discoverable.d.ts +3 -2
- package/dist/oauth-client-id-discoverable.d.ts.map +1 -1
- package/dist/oauth-client-id-discoverable.js +52 -28
- package/dist/oauth-client-id-discoverable.js.map +1 -1
- package/dist/oauth-client-id-loopback.d.ts +5 -5
- package/dist/oauth-client-id-loopback.d.ts.map +1 -1
- package/dist/oauth-client-id-loopback.js +29 -27
- package/dist/oauth-client-id-loopback.js.map +1 -1
- package/dist/oauth-client-metadata.d.ts +22 -12
- package/dist/oauth-client-metadata.d.ts.map +1 -1
- package/dist/oauth-client-metadata.js +18 -8
- package/dist/oauth-client-metadata.js.map +1 -1
- package/dist/oauth-issuer-identifier.d.ts +1 -1
- package/dist/oauth-issuer-identifier.d.ts.map +1 -1
- package/dist/oauth-issuer-identifier.js +3 -19
- package/dist/oauth-issuer-identifier.js.map +1 -1
- package/dist/oauth-protected-resource-metadata.d.ts +15 -12
- package/dist/oauth-protected-resource-metadata.d.ts.map +1 -1
- package/dist/oauth-protected-resource-metadata.js +15 -5
- package/dist/oauth-protected-resource-metadata.js.map +1 -1
- package/dist/oauth-redirect-uri.d.ts +10 -0
- package/dist/oauth-redirect-uri.d.ts.map +1 -0
- package/dist/oauth-redirect-uri.js +35 -0
- package/dist/oauth-redirect-uri.js.map +1 -0
- package/dist/oauth-token-request.d.ts +2 -2
- package/dist/oauth-token-response.d.ts +6 -6
- package/dist/uri.d.ts +20 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +127 -0
- package/dist/uri.js.map +1 -0
- package/package.json +1 -1
- package/src/atproto-loopback-client-metadata.ts +8 -3
- package/src/index.ts +2 -0
- package/src/oauth-authorization-code-grant-token-request.ts +2 -1
- package/src/oauth-authorization-details.ts +21 -1
- package/src/oauth-authorization-request-parameters.ts +2 -1
- package/src/oauth-authorization-server-metadata.ts +14 -10
- package/src/oauth-client-id-discoverable.ts +69 -51
- package/src/oauth-client-id-loopback.ts +40 -40
- package/src/oauth-client-metadata.ts +18 -8
- package/src/oauth-issuer-identifier.ts +6 -21
- package/src/oauth-protected-resource-metadata.ts +15 -5
- package/src/oauth-redirect-uri.ts +56 -0
- package/src/uri.ts +171 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/uri.d.ts
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
import { TypeOf, z } from 'zod';
|
2
|
+
/**
|
3
|
+
* Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
|
4
|
+
*
|
5
|
+
* Any value that matches this schema is safe to parse using `new URL()`.
|
6
|
+
*/
|
7
|
+
export declare const dangerousUriSchema: z.ZodEffects<z.ZodString, `${string}:${string}`, string>;
|
8
|
+
/**
|
9
|
+
* Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
|
10
|
+
*/
|
11
|
+
export type DangerousUrl = TypeOf<typeof dangerousUriSchema>;
|
12
|
+
export declare const loopbackUriSchema: z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `http://[::1]${string}` | "http://localhost" | `http://localhost#${string}` | `http://localhost?${string}` | `http://localhost/${string}` | `http://localhost:${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>;
|
13
|
+
export type LoopbackUri = TypeOf<typeof loopbackUriSchema>;
|
14
|
+
export declare const httpsUriSchema: z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `https://${string}`, string>;
|
15
|
+
export type HttpsUri = TypeOf<typeof httpsUriSchema>;
|
16
|
+
export declare const webUriSchema: z.ZodEffects<z.ZodString, `http://[::1]${string}` | "http://localhost" | `http://localhost#${string}` | `http://localhost?${string}` | `http://localhost/${string}` | `http://localhost:${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}` | `https://${string}`, string>;
|
17
|
+
export type WebUri = TypeOf<typeof webUriSchema>;
|
18
|
+
export declare const privateUseUriSchema: z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `${string}.${string}:/${string}`, string>;
|
19
|
+
export type PrivateUseUri = TypeOf<typeof privateUseUriSchema>;
|
20
|
+
//# sourceMappingURL=uri.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,CAAC,EAAgB,MAAM,KAAK,CAAA;AAG7C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,0DAQ5B,CAAA;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE5D,eAAO,MAAM,iBAAiB,2YA6B7B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc,qGA6C1B,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAA;AAEpD,eAAO,MAAM,YAAY,oXAqBrB,CAAA;AAEJ,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAA;AAEhD,eAAO,MAAM,mBAAmB,kHAsC/B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
|
package/dist/uri.js
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.privateUseUriSchema = exports.webUriSchema = exports.httpsUriSchema = exports.loopbackUriSchema = exports.dangerousUriSchema = void 0;
|
4
|
+
const zod_1 = require("zod");
|
5
|
+
const util_js_1 = require("./util.js");
|
6
|
+
/**
|
7
|
+
* Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
|
8
|
+
*
|
9
|
+
* Any value that matches this schema is safe to parse using `new URL()`.
|
10
|
+
*/
|
11
|
+
exports.dangerousUriSchema = zod_1.z
|
12
|
+
.string()
|
13
|
+
.refine((data) => data.includes(':') && URL.canParse(data), {
|
14
|
+
message: 'Invalid URL',
|
15
|
+
});
|
16
|
+
exports.loopbackUriSchema = exports.dangerousUriSchema.superRefine((value, ctx) => {
|
17
|
+
// Loopback url must use the "http:" protocol
|
18
|
+
if (!value.startsWith('http://')) {
|
19
|
+
ctx.addIssue({
|
20
|
+
code: zod_1.ZodIssueCode.custom,
|
21
|
+
message: 'URL must use the "http:" protocol',
|
22
|
+
});
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
const url = new URL(value);
|
26
|
+
if (!(0, util_js_1.isLoopbackHost)(url.hostname)) {
|
27
|
+
ctx.addIssue({
|
28
|
+
code: zod_1.ZodIssueCode.custom,
|
29
|
+
message: 'URL must use "localhost", "127.0.0.1" or "[::1]" as hostname',
|
30
|
+
});
|
31
|
+
return false;
|
32
|
+
}
|
33
|
+
return true;
|
34
|
+
});
|
35
|
+
exports.httpsUriSchema = exports.dangerousUriSchema.superRefine((value, ctx) => {
|
36
|
+
if (!value.startsWith('https://')) {
|
37
|
+
ctx.addIssue({
|
38
|
+
code: zod_1.ZodIssueCode.custom,
|
39
|
+
message: 'URL must use the "https:" protocol',
|
40
|
+
});
|
41
|
+
return false;
|
42
|
+
}
|
43
|
+
const url = new URL(value);
|
44
|
+
// Disallow loopback URLs with the `https:` protocol
|
45
|
+
if ((0, util_js_1.isLoopbackHost)(url.hostname)) {
|
46
|
+
ctx.addIssue({
|
47
|
+
code: zod_1.ZodIssueCode.custom,
|
48
|
+
message: 'https: URL must not use a loopback host',
|
49
|
+
});
|
50
|
+
return false;
|
51
|
+
}
|
52
|
+
if ((0, util_js_1.isHostnameIP)(url.hostname)) {
|
53
|
+
// Hostname is an IP address
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
// Hostname is a domain name
|
57
|
+
if (!url.hostname.includes('.')) {
|
58
|
+
// we don't depend on PSL here, so we only check for a dot
|
59
|
+
ctx.addIssue({
|
60
|
+
code: zod_1.ZodIssueCode.custom,
|
61
|
+
message: 'Domain name must contain at least two segments',
|
62
|
+
});
|
63
|
+
return false;
|
64
|
+
}
|
65
|
+
if (url.hostname.endsWith('.local')) {
|
66
|
+
ctx.addIssue({
|
67
|
+
code: zod_1.ZodIssueCode.custom,
|
68
|
+
message: 'Domain name must not end with ".local"',
|
69
|
+
});
|
70
|
+
return false;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
return true;
|
74
|
+
});
|
75
|
+
exports.webUriSchema = zod_1.z
|
76
|
+
.string()
|
77
|
+
.superRefine((value, ctx) => {
|
78
|
+
// discriminated union of `loopbackUriSchema` and `httpsUriSchema`
|
79
|
+
if (value.startsWith('http://')) {
|
80
|
+
const result = exports.loopbackUriSchema.safeParse(value);
|
81
|
+
if (!result.success)
|
82
|
+
result.error.issues.forEach(ctx.addIssue, ctx);
|
83
|
+
return result.success;
|
84
|
+
}
|
85
|
+
if (value.startsWith('https://')) {
|
86
|
+
const result = exports.httpsUriSchema.safeParse(value);
|
87
|
+
if (!result.success)
|
88
|
+
result.error.issues.forEach(ctx.addIssue, ctx);
|
89
|
+
return result.success;
|
90
|
+
}
|
91
|
+
ctx.addIssue({
|
92
|
+
code: zod_1.ZodIssueCode.custom,
|
93
|
+
message: 'URL must use the "http:" or "https:" protocol',
|
94
|
+
});
|
95
|
+
return false;
|
96
|
+
});
|
97
|
+
exports.privateUseUriSchema = exports.dangerousUriSchema.superRefine((value, ctx) => {
|
98
|
+
const dotIdx = value.indexOf('.');
|
99
|
+
const colonIdx = value.indexOf(':');
|
100
|
+
// Optimization: avoid parsing the URL if the protocol does not contain a "."
|
101
|
+
if (dotIdx === -1 || colonIdx === -1 || dotIdx > colonIdx) {
|
102
|
+
ctx.addIssue({
|
103
|
+
code: zod_1.ZodIssueCode.custom,
|
104
|
+
message: 'Private-use URI scheme requires a "." as part of the protocol',
|
105
|
+
});
|
106
|
+
return false;
|
107
|
+
}
|
108
|
+
const url = new URL(value);
|
109
|
+
// Should be covered by the check before, but let's be extra sure
|
110
|
+
if (!url.protocol.includes('.')) {
|
111
|
+
ctx.addIssue({
|
112
|
+
code: zod_1.ZodIssueCode.custom,
|
113
|
+
message: 'Invalid private-use URI scheme',
|
114
|
+
});
|
115
|
+
return false;
|
116
|
+
}
|
117
|
+
if (url.hostname) {
|
118
|
+
// https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
|
119
|
+
ctx.addIssue({
|
120
|
+
code: zod_1.ZodIssueCode.custom,
|
121
|
+
message: 'Private-use URI schemes must not include a hostname (only one "/" is allowed after the protocol, as per RFC 8252)',
|
122
|
+
});
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
return true;
|
126
|
+
});
|
127
|
+
//# sourceMappingURL=uri.js.map
|
package/dist/uri.js.map
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"uri.js","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
OAuthClientIdLoopback,
|
3
|
+
parseOAuthLoopbackClientId,
|
4
|
+
} from './oauth-client-id-loopback.js'
|
2
5
|
import { OAuthClientMetadataInput } from './oauth-client-metadata.js'
|
3
6
|
|
4
7
|
export function atprotoLoopbackClientMetadata(
|
5
8
|
clientId: string,
|
6
|
-
): OAuthClientMetadataInput {
|
9
|
+
): OAuthClientMetadataInput & {
|
10
|
+
client_id: OAuthClientIdLoopback
|
11
|
+
} {
|
7
12
|
const {
|
8
13
|
scope = 'atproto',
|
9
14
|
redirect_uris = [`http://127.0.0.1/`, `http://[::1]/`],
|
10
15
|
} = parseOAuthLoopbackClientId(clientId)
|
11
16
|
|
12
17
|
return {
|
13
|
-
client_id: clientId,
|
18
|
+
client_id: clientId as OAuthClientIdLoopback,
|
14
19
|
scope,
|
15
20
|
redirect_uris,
|
16
21
|
client_name: 'Loopback client',
|
package/src/index.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
export * from './constants.js'
|
2
|
+
export * from './uri.js'
|
2
3
|
export * from './util.js'
|
3
4
|
|
4
5
|
export * from './atproto-loopback-client-metadata.js'
|
@@ -25,6 +26,7 @@ export * from './oauth-issuer-identifier.js'
|
|
25
26
|
export * from './oauth-par-response.js'
|
26
27
|
export * from './oauth-password-grant-token-request.js'
|
27
28
|
export * from './oauth-protected-resource-metadata.js'
|
29
|
+
export * from './oauth-redirect-uri.js'
|
28
30
|
export * from './oauth-refresh-token-grant-token-request.js'
|
29
31
|
export * from './oauth-refresh-token.js'
|
30
32
|
export * from './oauth-request-uri.js'
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { z } from 'zod'
|
2
|
+
import { oauthRedirectUriSchema } from './oauth-redirect-uri.js'
|
2
3
|
|
3
4
|
export const oauthAuthorizationCodeGrantTokenRequestSchema = z.object({
|
4
5
|
grant_type: z.literal('authorization_code'),
|
5
6
|
code: z.string().min(1),
|
6
|
-
redirect_uri:
|
7
|
+
redirect_uri: oauthRedirectUriSchema,
|
7
8
|
/** @see {@link https://datatracker.ietf.org/doc/html/rfc7636#section-4.1} */
|
8
9
|
code_verifier: z
|
9
10
|
.string()
|
@@ -1,14 +1,34 @@
|
|
1
1
|
import { z } from 'zod'
|
2
|
+
import { dangerousUriSchema } from './uri.js'
|
2
3
|
|
3
4
|
/**
|
4
5
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc9396#section-2 | RFC 9396, Section 2}
|
5
6
|
*/
|
6
7
|
export const oauthAuthorizationDetailSchema = z.object({
|
7
8
|
type: z.string(),
|
8
|
-
|
9
|
+
/**
|
10
|
+
* An array of strings representing the location of the resource or RS. These
|
11
|
+
* strings are typically URIs identifying the location of the RS.
|
12
|
+
*/
|
13
|
+
locations: z.array(dangerousUriSchema).optional(),
|
14
|
+
/**
|
15
|
+
* An array of strings representing the kinds of actions to be taken at the
|
16
|
+
* resource.
|
17
|
+
*/
|
9
18
|
actions: z.array(z.string()).optional(),
|
19
|
+
/**
|
20
|
+
* An array of strings representing the kinds of data being requested from the
|
21
|
+
* resource.
|
22
|
+
*/
|
10
23
|
datatypes: z.array(z.string()).optional(),
|
24
|
+
/**
|
25
|
+
* A string identifier indicating a specific resource available at the API.
|
26
|
+
*/
|
11
27
|
identifier: z.string().optional(),
|
28
|
+
/**
|
29
|
+
* An array of strings representing the types or levels of privilege being
|
30
|
+
* requested at the resource.
|
31
|
+
*/
|
12
32
|
privileges: z.array(z.string()).optional(),
|
13
33
|
})
|
14
34
|
|
@@ -4,6 +4,7 @@ import { z } from 'zod'
|
|
4
4
|
import { oauthAuthorizationDetailsSchema } from './oauth-authorization-details.js'
|
5
5
|
import { oauthClientIdSchema } from './oauth-client-id.js'
|
6
6
|
import { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'
|
7
|
+
import { oauthRedirectUriSchema } from './oauth-redirect-uri.js'
|
7
8
|
import { oauthResponseTypeSchema } from './oauth-response-type.js'
|
8
9
|
import { oauthScopeSchema } from './oauth-scope.js'
|
9
10
|
import { oidcClaimsParameterSchema } from './oidc-claims-parameter.js'
|
@@ -16,7 +17,7 @@ import { oidcEntityTypeSchema } from './oidc-entity-type.js'
|
|
16
17
|
export const oauthAuthorizationRequestParametersSchema = z.object({
|
17
18
|
client_id: oauthClientIdSchema,
|
18
19
|
state: z.string().optional(),
|
19
|
-
redirect_uri:
|
20
|
+
redirect_uri: oauthRedirectUriSchema.optional(),
|
20
21
|
scope: oauthScopeSchema.optional(),
|
21
22
|
response_type: oauthResponseTypeSchema,
|
22
23
|
|
@@ -2,9 +2,13 @@ import { z } from 'zod'
|
|
2
2
|
|
3
3
|
import { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'
|
4
4
|
import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'
|
5
|
+
import { webUriSchema } from './uri.js'
|
5
6
|
|
6
7
|
/**
|
7
8
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc8414}
|
9
|
+
* @note we do not enforce https: scheme in URIs to support development
|
10
|
+
* environments. Make sure to validate the URIs before using it in a production
|
11
|
+
* environment.
|
8
12
|
*/
|
9
13
|
export const oauthAuthorizationServerMetadataSchema = z.object({
|
10
14
|
issuer: oauthIssuerIdentifierSchema,
|
@@ -37,31 +41,31 @@ export const oauthAuthorizationServerMetadataSchema = z.object({
|
|
37
41
|
.array(z.string())
|
38
42
|
.optional(),
|
39
43
|
|
40
|
-
jwks_uri:
|
44
|
+
jwks_uri: webUriSchema.optional(),
|
41
45
|
|
42
|
-
authorization_endpoint:
|
46
|
+
authorization_endpoint: webUriSchema, // .optional(),
|
43
47
|
|
44
|
-
token_endpoint:
|
48
|
+
token_endpoint: webUriSchema, // .optional(),
|
45
49
|
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
46
50
|
token_endpoint_auth_signing_alg_values_supported: z
|
47
51
|
.array(z.string())
|
48
52
|
.optional(),
|
49
53
|
|
50
|
-
revocation_endpoint:
|
51
|
-
introspection_endpoint:
|
52
|
-
pushed_authorization_request_endpoint:
|
54
|
+
revocation_endpoint: webUriSchema.optional(),
|
55
|
+
introspection_endpoint: webUriSchema.optional(),
|
56
|
+
pushed_authorization_request_endpoint: webUriSchema.optional(),
|
53
57
|
|
54
58
|
require_pushed_authorization_requests: z.boolean().optional(),
|
55
59
|
|
56
|
-
userinfo_endpoint:
|
57
|
-
end_session_endpoint:
|
58
|
-
registration_endpoint:
|
60
|
+
userinfo_endpoint: webUriSchema.optional(),
|
61
|
+
end_session_endpoint: webUriSchema.optional(),
|
62
|
+
registration_endpoint: webUriSchema.optional(),
|
59
63
|
|
60
64
|
// https://datatracker.ietf.org/doc/html/rfc9449#section-5.1
|
61
65
|
dpop_signing_alg_values_supported: z.array(z.string()).optional(),
|
62
66
|
|
63
67
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
|
64
|
-
protected_resources: z.array(
|
68
|
+
protected_resources: z.array(webUriSchema).optional(),
|
65
69
|
|
66
70
|
// https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
|
67
71
|
client_id_metadata_document_supported: z.boolean().optional(),
|
@@ -1,69 +1,87 @@
|
|
1
|
-
import {
|
1
|
+
import { TypeOf, z } from 'zod'
|
2
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
3
|
+
import { httpsUriSchema } from './uri.js'
|
2
4
|
import { extractUrlPath, isHostnameIP } from './util.js'
|
3
5
|
|
4
6
|
/**
|
5
7
|
* @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}
|
6
8
|
*/
|
7
|
-
export
|
9
|
+
export const oauthClientIdDiscoverableSchema = z
|
10
|
+
.intersection(oauthClientIdSchema, httpsUriSchema)
|
11
|
+
.superRefine((value, ctx): value is `https://${string}/${string}` => {
|
12
|
+
const url = new URL(value)
|
13
|
+
|
14
|
+
if (url.username || url.password) {
|
15
|
+
ctx.addIssue({
|
16
|
+
code: z.ZodIssueCode.custom,
|
17
|
+
message: 'ClientID must not contain credentials',
|
18
|
+
})
|
19
|
+
return false
|
20
|
+
}
|
21
|
+
|
22
|
+
if (url.hash) {
|
23
|
+
ctx.addIssue({
|
24
|
+
code: z.ZodIssueCode.custom,
|
25
|
+
message: 'ClientID must not contain a fragment',
|
26
|
+
})
|
27
|
+
return false
|
28
|
+
}
|
29
|
+
|
30
|
+
if (url.pathname === '/') {
|
31
|
+
ctx.addIssue({
|
32
|
+
code: z.ZodIssueCode.custom,
|
33
|
+
message:
|
34
|
+
'ClientID must contain a path component (e.g. "/client-metadata.json")',
|
35
|
+
})
|
36
|
+
return false
|
37
|
+
}
|
38
|
+
|
39
|
+
if (url.pathname.endsWith('/')) {
|
40
|
+
ctx.addIssue({
|
41
|
+
code: z.ZodIssueCode.custom,
|
42
|
+
message: 'ClientID path must not end with a trailing slash',
|
43
|
+
})
|
44
|
+
return false
|
45
|
+
}
|
46
|
+
|
47
|
+
if (isHostnameIP(url.hostname)) {
|
48
|
+
ctx.addIssue({
|
49
|
+
code: z.ZodIssueCode.custom,
|
50
|
+
message: 'ClientID hostname must not be an IP address',
|
51
|
+
})
|
52
|
+
return false
|
53
|
+
}
|
54
|
+
|
55
|
+
// URL constructor normalizes the URL, so we extract the path manually to
|
56
|
+
// avoid normalization, then compare it to the normalized path to ensure
|
57
|
+
// that the URL does not contain path traversal or other unexpected characters
|
58
|
+
if (extractUrlPath(value) !== url.pathname) {
|
59
|
+
ctx.addIssue({
|
60
|
+
code: z.ZodIssueCode.custom,
|
61
|
+
message: `ClientID must be in canonical form ("${url.href}", got "${value}")`,
|
62
|
+
})
|
63
|
+
return false
|
64
|
+
}
|
65
|
+
|
66
|
+
return true
|
67
|
+
})
|
68
|
+
|
69
|
+
export type OAuthClientIdDiscoverable = TypeOf<
|
70
|
+
typeof oauthClientIdDiscoverableSchema
|
71
|
+
>
|
8
72
|
|
9
73
|
export function isOAuthClientIdDiscoverable(
|
10
74
|
clientId: string,
|
11
75
|
): clientId is OAuthClientIdDiscoverable {
|
12
|
-
|
13
|
-
parseOAuthDiscoverableClientId(clientId)
|
14
|
-
return true
|
15
|
-
} catch {
|
16
|
-
return false
|
17
|
-
}
|
76
|
+
return oauthClientIdDiscoverableSchema.safeParse(clientId).success
|
18
77
|
}
|
19
78
|
|
20
79
|
export function assertOAuthDiscoverableClientId(
|
21
80
|
value: string,
|
22
81
|
): asserts value is OAuthClientIdDiscoverable {
|
23
|
-
void
|
82
|
+
void oauthClientIdDiscoverableSchema.parse(value)
|
24
83
|
}
|
25
84
|
|
26
85
|
export function parseOAuthDiscoverableClientId(clientId: string): URL {
|
27
|
-
|
28
|
-
|
29
|
-
if (url.protocol !== 'https:') {
|
30
|
-
throw new TypeError('ClientID must use the "https:" protocol')
|
31
|
-
}
|
32
|
-
|
33
|
-
if (url.username || url.password) {
|
34
|
-
throw new TypeError('ClientID must not contain credentials')
|
35
|
-
}
|
36
|
-
|
37
|
-
if (url.hash) {
|
38
|
-
throw new TypeError('ClientID must not contain a fragment')
|
39
|
-
}
|
40
|
-
|
41
|
-
if (url.hostname === 'localhost') {
|
42
|
-
throw new TypeError('ClientID hostname must not be "localhost"')
|
43
|
-
}
|
44
|
-
|
45
|
-
if (url.pathname === '/') {
|
46
|
-
throw new TypeError(
|
47
|
-
'ClientID must contain a path component (e.g. "/client-metadata.json")',
|
48
|
-
)
|
49
|
-
}
|
50
|
-
|
51
|
-
if (url.pathname.endsWith('/')) {
|
52
|
-
throw new TypeError('ClientID path must not end with a trailing slash')
|
53
|
-
}
|
54
|
-
|
55
|
-
if (isHostnameIP(url.hostname)) {
|
56
|
-
throw new TypeError('ClientID hostname must not be an IP address')
|
57
|
-
}
|
58
|
-
|
59
|
-
// URL constructor normalizes the URL, so we extract the path manually to
|
60
|
-
// avoid normalization, then compare it to the normalized path to ensure
|
61
|
-
// that the URL does not contain path traversal or other unexpected characters
|
62
|
-
if (extractUrlPath(clientId) !== url.pathname) {
|
63
|
-
throw new TypeError(
|
64
|
-
`ClientID must be in canonical form ("${url.href}", got "${clientId}")`,
|
65
|
-
)
|
66
|
-
}
|
67
|
-
|
68
|
-
return url
|
86
|
+
return new URL(oauthClientIdDiscoverableSchema.parse(clientId))
|
69
87
|
}
|
@@ -1,11 +1,33 @@
|
|
1
|
-
import {
|
1
|
+
import { TypeOf, ZodIssueCode } from 'zod'
|
2
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
3
|
+
import {
|
4
|
+
OAuthLoopbackRedirectURI,
|
5
|
+
oauthLoopbackRedirectURISchema,
|
6
|
+
OAuthRedirectUri,
|
7
|
+
} from './oauth-redirect-uri.js'
|
2
8
|
import { OAuthScope, oauthScopeSchema } from './oauth-scope.js'
|
3
|
-
import { isLoopbackHost, safeUrl } from './util.js'
|
4
9
|
|
5
|
-
const
|
10
|
+
const PREFIX = 'http://localhost'
|
6
11
|
|
7
|
-
export
|
8
|
-
`${typeof
|
12
|
+
export const oauthClientIdLoopbackSchema = oauthClientIdSchema.superRefine(
|
13
|
+
(value, ctx): value is `${typeof PREFIX}${'' | '/'}${'' | `?${string}`}` => {
|
14
|
+
try {
|
15
|
+
assertOAuthLoopbackClientId(value)
|
16
|
+
return true
|
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
|
+
}
|
27
|
+
},
|
28
|
+
)
|
29
|
+
|
30
|
+
export type OAuthClientIdLoopback = TypeOf<typeof oauthClientIdLoopbackSchema>
|
9
31
|
|
10
32
|
export function isOAuthClientIdLoopback(
|
11
33
|
clientId: string,
|
@@ -28,21 +50,18 @@ export function assertOAuthLoopbackClientId(
|
|
28
50
|
// validation functions)
|
29
51
|
export function parseOAuthLoopbackClientId(clientId: string): {
|
30
52
|
scope?: OAuthScope
|
31
|
-
redirect_uris?: [
|
53
|
+
redirect_uris?: [OAuthRedirectUri, ...OAuthRedirectUri[]]
|
32
54
|
} {
|
33
|
-
if (!clientId.startsWith(
|
34
|
-
throw new TypeError(
|
35
|
-
|
36
|
-
)
|
37
|
-
} else if (clientId.includes('#', OAUTH_CLIENT_ID_LOOPBACK_URL.length)) {
|
55
|
+
if (!clientId.startsWith(PREFIX)) {
|
56
|
+
throw new TypeError(`Loopback ClientID must start with "${PREFIX}"`)
|
57
|
+
} else if (clientId.includes('#', PREFIX.length)) {
|
38
58
|
throw new TypeError('Loopback ClientID must not contain a hash component')
|
39
59
|
}
|
40
60
|
|
41
61
|
const queryStringIdx =
|
42
|
-
clientId.length >
|
43
|
-
|
44
|
-
|
45
|
-
: OAUTH_CLIENT_ID_LOOPBACK_URL.length
|
62
|
+
clientId.length > PREFIX.length && clientId[PREFIX.length] === '/'
|
63
|
+
? PREFIX.length + 1
|
64
|
+
: PREFIX.length
|
46
65
|
|
47
66
|
if (clientId.length === queryStringIdx) {
|
48
67
|
return {} // no query string to parse
|
@@ -72,33 +91,14 @@ export function parseOAuthLoopbackClientId(clientId: string): {
|
|
72
91
|
}
|
73
92
|
|
74
93
|
const redirect_uris = searchParams.has('redirect_uri')
|
75
|
-
? (searchParams
|
94
|
+
? (searchParams
|
95
|
+
.getAll('redirect_uri')
|
96
|
+
.map((value) => oauthLoopbackRedirectURISchema.parse(value)) as [
|
97
|
+
OAuthLoopbackRedirectURI,
|
98
|
+
...OAuthLoopbackRedirectURI[],
|
99
|
+
])
|
76
100
|
: undefined
|
77
101
|
|
78
|
-
if (redirect_uris) {
|
79
|
-
for (const uri of redirect_uris) {
|
80
|
-
const url = safeUrl(uri)
|
81
|
-
if (!url) {
|
82
|
-
throw new TypeError(`Invalid redirect_uri in client ID: ${uri}`)
|
83
|
-
}
|
84
|
-
if (url.protocol !== 'http:') {
|
85
|
-
throw new TypeError(
|
86
|
-
`Loopback ClientID must use "http:" redirect_uri's (got ${uri})`,
|
87
|
-
)
|
88
|
-
}
|
89
|
-
if (url.hostname === 'localhost') {
|
90
|
-
throw new TypeError(
|
91
|
-
`Loopback ClientID must not use "localhost" as redirect_uri hostname (got ${uri})`,
|
92
|
-
)
|
93
|
-
}
|
94
|
-
if (!isLoopbackHost(url.hostname)) {
|
95
|
-
throw new TypeError(
|
96
|
-
`Loopback ClientID must use loopback addresses as redirect_uri's (got ${uri})`,
|
97
|
-
)
|
98
|
-
}
|
99
|
-
}
|
100
|
-
}
|
101
|
-
|
102
102
|
return {
|
103
103
|
scope,
|
104
104
|
redirect_uris,
|
@@ -4,13 +4,23 @@ import { z } from 'zod'
|
|
4
4
|
import { oauthClientIdSchema } from './oauth-client-id.js'
|
5
5
|
import { oauthEndpointAuthMethod } from './oauth-endpoint-auth-method.js'
|
6
6
|
import { oauthGrantTypeSchema } from './oauth-grant-type.js'
|
7
|
+
import { oauthRedirectUriSchema } from './oauth-redirect-uri.js'
|
7
8
|
import { oauthResponseTypeSchema } from './oauth-response-type.js'
|
8
9
|
import { oauthScopeSchema } from './oauth-scope.js'
|
10
|
+
import { webUriSchema } from './uri.js'
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
/**
|
13
|
+
* @see {@link https://openid.net/specs/openid-connect-registration-1_0.html}
|
14
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7591}
|
15
|
+
* @note we do not enforce https: scheme in URIs to support development
|
16
|
+
* environments. Make sure to validate the URIs before using it in a production
|
17
|
+
* environment.
|
18
|
+
*/
|
12
19
|
export const oauthClientMetadataSchema = z.object({
|
13
|
-
|
20
|
+
/**
|
21
|
+
* @note redirect_uris require additional validation
|
22
|
+
*/
|
23
|
+
redirect_uris: z.array(oauthRedirectUriSchema).nonempty(),
|
14
24
|
response_types: z
|
15
25
|
.array(oauthResponseTypeSchema)
|
16
26
|
.nonempty()
|
@@ -30,7 +40,7 @@ export const oauthClientMetadataSchema = z.object({
|
|
30
40
|
token_endpoint_auth_signing_alg: z.string().optional(),
|
31
41
|
userinfo_signed_response_alg: z.string().optional(),
|
32
42
|
userinfo_encrypted_response_alg: z.string().optional(),
|
33
|
-
jwks_uri:
|
43
|
+
jwks_uri: webUriSchema.optional(),
|
34
44
|
jwks: jwksPubSchema.optional(),
|
35
45
|
application_type: z.enum(['web', 'native']).default('web').optional(), // default, per spec, is "web"
|
36
46
|
subject_type: z.enum(['public', 'pairwise']).default('public').optional(),
|
@@ -41,10 +51,10 @@ export const oauthClientMetadataSchema = z.object({
|
|
41
51
|
authorization_encrypted_response_alg: z.string().optional(),
|
42
52
|
client_id: oauthClientIdSchema.optional(),
|
43
53
|
client_name: z.string().optional(),
|
44
|
-
client_uri:
|
45
|
-
policy_uri:
|
46
|
-
tos_uri:
|
47
|
-
logo_uri:
|
54
|
+
client_uri: webUriSchema.optional(),
|
55
|
+
policy_uri: webUriSchema.optional(),
|
56
|
+
tos_uri: webUriSchema.optional(),
|
57
|
+
logo_uri: webUriSchema.optional(), // TODO: allow data: uri ?
|
48
58
|
|
49
59
|
/**
|
50
60
|
* Default Maximum Authentication Age. Specifies that the End-User MUST be
|