@atproto/oauth-types 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +28 -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 +1 -2
- package/dist/atproto-loopback-client-metadata.js.map +1 -1
- package/dist/constants.d.ts +0 -6
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -17
- package/dist/constants.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 +11 -11
- package/dist/oauth-authorization-request-parameters.d.ts +10 -10
- package/dist/oauth-authorization-request-parameters.d.ts.map +1 -1
- package/dist/oauth-authorization-request-parameters.js +3 -2
- package/dist/oauth-authorization-request-parameters.js.map +1 -1
- package/dist/oauth-authorization-request-query.d.ts +11 -11
- 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 +54 -31
- 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 +32 -31
- package/dist/oauth-client-id-loopback.js.map +1 -1
- package/dist/oauth-client-metadata.d.ts +112 -102
- 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 +2 -1
- package/dist/oauth-issuer-identifier.d.ts.map +1 -1
- package/dist/oauth-issuer-identifier.js +8 -23
- 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-refresh-token-grant-token-request.d.ts +0 -3
- package/dist/oauth-refresh-token-grant-token-request.d.ts.map +1 -1
- package/dist/oauth-refresh-token-grant-token-request.js +0 -2
- package/dist/oauth-refresh-token-grant-token-request.js.map +1 -1
- package/dist/oauth-token-request.d.ts +2 -5
- package/dist/oauth-token-request.d.ts.map +1 -1
- package/dist/oauth-token-response.d.ts +9 -12
- package/dist/oauth-token-response.d.ts.map +1 -1
- package/dist/oauth-token-response.js +4 -2
- package/dist/oauth-token-response.js.map +1 -1
- 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/dist/util.js +5 -6
- package/dist/util.js.map +1 -1
- package/package.json +2 -2
- package/src/atproto-loopback-client-metadata.ts +8 -3
- package/src/constants.ts +0 -16
- 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 +3 -2
- 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 +14 -24
- package/src/oauth-protected-resource-metadata.ts +15 -5
- package/src/oauth-redirect-uri.ts +56 -0
- package/src/oauth-refresh-token-grant-token-request.ts +0 -2
- package/src/oauth-token-response.ts +4 -2
- package/src/uri.ts +171 -0
- package/tsconfig.build.tsbuildinfo +1 -0
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/dist/util.js
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.isHostnameIP = isHostnameIP;
|
4
|
+
exports.isLoopbackHost = isLoopbackHost;
|
5
|
+
exports.isLoopbackUrl = isLoopbackUrl;
|
6
|
+
exports.safeUrl = safeUrl;
|
7
|
+
exports.extractUrlPath = extractUrlPath;
|
4
8
|
function isHostnameIP(hostname) {
|
5
9
|
// IPv4
|
6
10
|
if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/))
|
@@ -10,16 +14,13 @@ function isHostnameIP(hostname) {
|
|
10
14
|
return true;
|
11
15
|
return false;
|
12
16
|
}
|
13
|
-
exports.isHostnameIP = isHostnameIP;
|
14
17
|
function isLoopbackHost(host) {
|
15
18
|
return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
|
16
19
|
}
|
17
|
-
exports.isLoopbackHost = isLoopbackHost;
|
18
20
|
function isLoopbackUrl(input) {
|
19
21
|
const url = typeof input === 'string' ? new URL(input) : input;
|
20
22
|
return isLoopbackHost(url.hostname);
|
21
23
|
}
|
22
|
-
exports.isLoopbackUrl = isLoopbackUrl;
|
23
24
|
function safeUrl(input) {
|
24
25
|
try {
|
25
26
|
return new URL(input);
|
@@ -28,7 +29,6 @@ function safeUrl(input) {
|
|
28
29
|
return null;
|
29
30
|
}
|
30
31
|
}
|
31
|
-
exports.safeUrl = safeUrl;
|
32
32
|
function extractUrlPath(url) {
|
33
33
|
// Extracts the path from a URL, without relying on the URL constructor
|
34
34
|
// (because it normalizes the URL)
|
@@ -59,5 +59,4 @@ function extractUrlPath(url) {
|
|
59
59
|
}
|
60
60
|
return url.substring(pathStart, pathEnd);
|
61
61
|
}
|
62
|
-
exports.extractUrlPath = extractUrlPath;
|
63
62
|
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAAA,oCAQC;AAID,wCAEC;AAED,sCAGC;AAED,0BAMC;AAED,wCAsCC;AAnED,SAAgB,YAAY,CAAC,QAAgB;IAC3C,OAAO;IACP,IAAI,QAAQ,CAAC,KAAK,CAAC,sBAAsB,CAAC;QAAE,OAAO,IAAI,CAAA;IAEvD,OAAO;IACP,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAEnE,OAAO,KAAK,CAAA;AACd,CAAC;AAID,SAAgB,cAAc,CAAC,IAAa;IAC1C,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,OAAO,CAAA;AACzE,CAAC;AAED,SAAgB,aAAa,CAAC,KAAmB;IAC/C,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC9D,OAAO,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;AACrC,CAAC;AAED,SAAgB,OAAO,CAAC,KAAmB;IACzC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAgB,cAAc,CAAC,GAAG;IAChC,uEAAuE;IACvE,kCAAkC;IAClC,MAAM,aAAa,GAAG,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,CAAC,CAAC,CAAA;IACR,IAAI,aAAa,KAAK,CAAC,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CAAC,+CAA+C,CAAC,CAAA;IACtE,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAC/C,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAEnD,MAAM,WAAW,GACf,WAAW,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,WAAW,GAAG,OAAO,CAAC;QAC7D,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,CAAC,CAAC,CAAA;IAER,MAAM,OAAO,GACX,OAAO,KAAK,CAAC,CAAC;QACZ,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC;YAClB,CAAC,CAAC,GAAG,CAAC,MAAM;YACZ,CAAC,CAAC,WAAW;QACf,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC;YAClB,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAEtC,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAEhD,MAAM,SAAS,GAAG,QAAQ,KAAK,CAAC,CAAC,IAAI,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAA;IAE5E,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAA;IAChD,CAAC;IAED,OAAO,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;AAC1C,CAAC"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@atproto/oauth-types",
|
3
|
-
"version": "0.1
|
3
|
+
"version": "0.2.1",
|
4
4
|
"license": "MIT",
|
5
5
|
"description": "OAuth typing & validation library",
|
6
6
|
"keywords": [
|
@@ -29,7 +29,7 @@
|
|
29
29
|
"@atproto/jwk": "0.1.1"
|
30
30
|
},
|
31
31
|
"devDependencies": {
|
32
|
-
"typescript": "^5.
|
32
|
+
"typescript": "^5.6.3"
|
33
33
|
},
|
34
34
|
"scripts": {
|
35
35
|
"build": "tsc --build tsconfig.build.json"
|
@@ -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/constants.ts
CHANGED
@@ -1,18 +1,2 @@
|
|
1
|
-
/**
|
2
|
-
* A variable that allows to determine if unsecure origins should be allowed
|
3
|
-
* in OAuth related URI's. This variable is only set to `true` when NODE_ENV
|
4
|
-
* is either `development` or `test`.
|
5
|
-
*/
|
6
|
-
export const ALLOW_UNSECURE_ORIGINS = (() => {
|
7
|
-
// try/catch to support running in a browser, including when process.env is
|
8
|
-
// shimmed (e.g. by webpack)
|
9
|
-
try {
|
10
|
-
const env = process.env.NODE_ENV
|
11
|
-
return env === 'development' || env === 'test'
|
12
|
-
} catch {
|
13
|
-
return false
|
14
|
-
}
|
15
|
-
})()
|
16
|
-
|
17
1
|
export const CLIENT_ASSERTION_TYPE_JWT_BEARER =
|
18
2
|
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
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
|
|
@@ -73,7 +74,7 @@ export const oauthAuthorizationRequestParametersSchema = z.object({
|
|
73
74
|
id_token_hint: signedJwtSchema.optional(),
|
74
75
|
|
75
76
|
// Type of UI the AS is displayed on
|
76
|
-
display: z.enum(['page', 'popup', 'touch']).optional(),
|
77
|
+
display: z.enum(['page', 'popup', 'touch', 'wap']).optional(),
|
77
78
|
|
78
79
|
/**
|
79
80
|
* - "none" will only be allowed if the user already allowed the client on the same device
|
@@ -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
|
}
|