@atproto/oauth-types 0.1.5 → 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.
Files changed (90) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/atproto-loopback-client-metadata.d.ts +4 -1
  3. package/dist/atproto-loopback-client-metadata.d.ts.map +1 -1
  4. package/dist/atproto-loopback-client-metadata.js +1 -2
  5. package/dist/atproto-loopback-client-metadata.js.map +1 -1
  6. package/dist/constants.d.ts +0 -6
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +1 -17
  9. package/dist/constants.js.map +1 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/oauth-authorization-code-grant-token-request.d.ts +2 -2
  15. package/dist/oauth-authorization-code-grant-token-request.d.ts.map +1 -1
  16. package/dist/oauth-authorization-code-grant-token-request.js +2 -1
  17. package/dist/oauth-authorization-code-grant-token-request.js.map +1 -1
  18. package/dist/oauth-authorization-details.d.ts +42 -4
  19. package/dist/oauth-authorization-details.d.ts.map +1 -1
  20. package/dist/oauth-authorization-details.js +21 -1
  21. package/dist/oauth-authorization-details.js.map +1 -1
  22. package/dist/oauth-authorization-request-jar.d.ts +1 -1
  23. package/dist/oauth-authorization-request-par.d.ts +11 -11
  24. package/dist/oauth-authorization-request-parameters.d.ts +10 -10
  25. package/dist/oauth-authorization-request-parameters.d.ts.map +1 -1
  26. package/dist/oauth-authorization-request-parameters.js +3 -2
  27. package/dist/oauth-authorization-request-parameters.js.map +1 -1
  28. package/dist/oauth-authorization-request-query.d.ts +11 -11
  29. package/dist/oauth-authorization-server-metadata.d.ts +69 -66
  30. package/dist/oauth-authorization-server-metadata.d.ts.map +1 -1
  31. package/dist/oauth-authorization-server-metadata.js +14 -10
  32. package/dist/oauth-authorization-server-metadata.js.map +1 -1
  33. package/dist/oauth-client-id-discoverable.d.ts +3 -2
  34. package/dist/oauth-client-id-discoverable.d.ts.map +1 -1
  35. package/dist/oauth-client-id-discoverable.js +54 -31
  36. package/dist/oauth-client-id-discoverable.js.map +1 -1
  37. package/dist/oauth-client-id-loopback.d.ts +5 -5
  38. package/dist/oauth-client-id-loopback.d.ts.map +1 -1
  39. package/dist/oauth-client-id-loopback.js +32 -31
  40. package/dist/oauth-client-id-loopback.js.map +1 -1
  41. package/dist/oauth-client-metadata.d.ts +112 -102
  42. package/dist/oauth-client-metadata.d.ts.map +1 -1
  43. package/dist/oauth-client-metadata.js +18 -8
  44. package/dist/oauth-client-metadata.js.map +1 -1
  45. package/dist/oauth-issuer-identifier.d.ts +2 -1
  46. package/dist/oauth-issuer-identifier.d.ts.map +1 -1
  47. package/dist/oauth-issuer-identifier.js +8 -23
  48. package/dist/oauth-issuer-identifier.js.map +1 -1
  49. package/dist/oauth-protected-resource-metadata.d.ts +15 -12
  50. package/dist/oauth-protected-resource-metadata.d.ts.map +1 -1
  51. package/dist/oauth-protected-resource-metadata.js +15 -5
  52. package/dist/oauth-protected-resource-metadata.js.map +1 -1
  53. package/dist/oauth-redirect-uri.d.ts +10 -0
  54. package/dist/oauth-redirect-uri.d.ts.map +1 -0
  55. package/dist/oauth-redirect-uri.js +35 -0
  56. package/dist/oauth-redirect-uri.js.map +1 -0
  57. package/dist/oauth-refresh-token-grant-token-request.d.ts +0 -3
  58. package/dist/oauth-refresh-token-grant-token-request.d.ts.map +1 -1
  59. package/dist/oauth-refresh-token-grant-token-request.js +0 -2
  60. package/dist/oauth-refresh-token-grant-token-request.js.map +1 -1
  61. package/dist/oauth-token-request.d.ts +2 -5
  62. package/dist/oauth-token-request.d.ts.map +1 -1
  63. package/dist/oauth-token-response.d.ts +9 -12
  64. package/dist/oauth-token-response.d.ts.map +1 -1
  65. package/dist/oauth-token-response.js +4 -2
  66. package/dist/oauth-token-response.js.map +1 -1
  67. package/dist/uri.d.ts +20 -0
  68. package/dist/uri.d.ts.map +1 -0
  69. package/dist/uri.js +127 -0
  70. package/dist/uri.js.map +1 -0
  71. package/dist/util.js +5 -6
  72. package/dist/util.js.map +1 -1
  73. package/package.json +2 -2
  74. package/src/atproto-loopback-client-metadata.ts +8 -3
  75. package/src/constants.ts +0 -16
  76. package/src/index.ts +2 -0
  77. package/src/oauth-authorization-code-grant-token-request.ts +2 -1
  78. package/src/oauth-authorization-details.ts +21 -1
  79. package/src/oauth-authorization-request-parameters.ts +3 -2
  80. package/src/oauth-authorization-server-metadata.ts +14 -10
  81. package/src/oauth-client-id-discoverable.ts +69 -51
  82. package/src/oauth-client-id-loopback.ts +40 -40
  83. package/src/oauth-client-metadata.ts +18 -8
  84. package/src/oauth-issuer-identifier.ts +14 -24
  85. package/src/oauth-protected-resource-metadata.ts +15 -5
  86. package/src/oauth-redirect-uri.ts +56 -0
  87. package/src/oauth-refresh-token-grant-token-request.ts +0 -2
  88. package/src/oauth-token-response.ts +4 -2
  89. package/src/uri.ts +171 -0
  90. 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
@@ -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.extractUrlPath = exports.safeUrl = exports.isLoopbackUrl = exports.isLoopbackHost = exports.isHostnameIP = void 0;
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":";;;AAAA,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;AARD,oCAQC;AAID,SAAgB,cAAc,CAAC,IAAa;IAC1C,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,OAAO,CAAA;AACzE,CAAC;AAFD,wCAEC;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;AAHD,sCAGC;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;AAND,0BAMC;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;AAtCD,wCAsCC"}
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.5",
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.3.3"
32
+ "typescript": "^5.6.3"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc --build tsconfig.build.json"
@@ -1,16 +1,21 @@
1
- import { parseOAuthLoopbackClientId } from './oauth-client-id-loopback.js'
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: z.string().url(),
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
- locations: z.array(z.string().url()).optional(),
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: z.string().url().optional(),
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: z.string().url().optional(),
44
+ jwks_uri: webUriSchema.optional(),
41
45
 
42
- authorization_endpoint: z.string().url(), // .optional(),
46
+ authorization_endpoint: webUriSchema, // .optional(),
43
47
 
44
- token_endpoint: z.string().url(), // .optional(),
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: z.string().url().optional(),
51
- introspection_endpoint: z.string().url().optional(),
52
- pushed_authorization_request_endpoint: z.string().url().optional(),
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: z.string().url().optional(),
57
- end_session_endpoint: z.string().url().optional(),
58
- registration_endpoint: z.string().url().optional(),
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(z.string().url()).optional(),
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 { OAuthClientId } from './oauth-client-id.js'
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 type OAuthClientIdDiscoverable = OAuthClientId & `https://${string}`
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
- try {
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 parseOAuthDiscoverableClientId(value)
82
+ void oauthClientIdDiscoverableSchema.parse(value)
24
83
  }
25
84
 
26
85
  export function parseOAuthDiscoverableClientId(clientId: string): URL {
27
- const url = new URL(clientId)
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
  }