@atproto/oauth-types 0.2.0 → 0.2.1
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 +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
|