@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.
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
  }