@atproto/jwk 0.1.0

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 (60) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.txt +7 -0
  3. package/dist/alg.d.ts +3 -0
  4. package/dist/alg.d.ts.map +1 -0
  5. package/dist/alg.js +90 -0
  6. package/dist/alg.js.map +1 -0
  7. package/dist/errors.d.ts +24 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +62 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +27 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/jwk.d.ts +2424 -0
  16. package/dist/jwk.d.ts.map +1 -0
  17. package/dist/jwk.js +112 -0
  18. package/dist/jwk.js.map +1 -0
  19. package/dist/jwks.d.ts +1770 -0
  20. package/dist/jwks.d.ts.map +1 -0
  21. package/dist/jwks.js +12 -0
  22. package/dist/jwks.js.map +1 -0
  23. package/dist/jwt-decode.d.ts +6 -0
  24. package/dist/jwt-decode.d.ts.map +1 -0
  25. package/dist/jwt-decode.js +20 -0
  26. package/dist/jwt-decode.js.map +1 -0
  27. package/dist/jwt-verify.d.ts +20 -0
  28. package/dist/jwt-verify.d.ts.map +1 -0
  29. package/dist/jwt-verify.js +3 -0
  30. package/dist/jwt-verify.js.map +1 -0
  31. package/dist/jwt.d.ts +1785 -0
  32. package/dist/jwt.d.ts.map +1 -0
  33. package/dist/jwt.js +150 -0
  34. package/dist/jwt.js.map +1 -0
  35. package/dist/key.d.ts +38 -0
  36. package/dist/key.d.ts.map +1 -0
  37. package/dist/key.js +131 -0
  38. package/dist/key.js.map +1 -0
  39. package/dist/keyset.d.ts +41 -0
  40. package/dist/keyset.d.ts.map +1 -0
  41. package/dist/keyset.js +234 -0
  42. package/dist/keyset.js.map +1 -0
  43. package/dist/util.d.ts +48 -0
  44. package/dist/util.d.ts.map +1 -0
  45. package/dist/util.js +143 -0
  46. package/dist/util.js.map +1 -0
  47. package/package.json +38 -0
  48. package/src/alg.ts +98 -0
  49. package/src/errors.ts +56 -0
  50. package/src/index.ts +10 -0
  51. package/src/jwk.ts +141 -0
  52. package/src/jwks.ts +15 -0
  53. package/src/jwt-decode.ts +27 -0
  54. package/src/jwt-verify.ts +22 -0
  55. package/src/jwt.ts +173 -0
  56. package/src/key.ts +93 -0
  57. package/src/keyset.ts +240 -0
  58. package/src/util.ts +181 -0
  59. package/tsconfig.build.json +8 -0
  60. package/tsconfig.json +4 -0
package/dist/util.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { RefinementCtx } from 'zod';
2
+ export type Simplify<T> = {
3
+ [K in keyof T]: T[K];
4
+ } & {};
5
+ export type Override<T, V> = Simplify<V & Omit<T, keyof V>>;
6
+ export type RequiredKey<T, K extends string> = Simplify<string extends K ? T : {
7
+ [L in K]: Exclude<L extends keyof T ? T[L] : unknown, undefined>;
8
+ } & Omit<T, K>>;
9
+ export declare const isDefined: <T>(i: T | undefined) => i is T;
10
+ export declare const preferredOrderCmp: <T>(order: readonly T[]) => (a: T, b: T) => number;
11
+ export declare function matchesAny<T extends string | number | symbol | boolean>(value: null | undefined | T | readonly T[]): (v: unknown) => v is T;
12
+ /**
13
+ * Decorator to cache the result of a getter on a class instance.
14
+ */
15
+ export declare const cachedGetter: <T extends object, V>(target: (this: T) => V, _context: ClassGetterDecoratorContext<T, V>) => (this: T) => V;
16
+ export declare function parseB64uJson(input: string): unknown;
17
+ /**
18
+ * @example
19
+ * ```ts
20
+ * // jwtSchema will only allow base64url chars & "." (dot)
21
+ * const jwtSchema = z.string().superRefine(jwtCharsRefinement)
22
+ * ```
23
+ */
24
+ export declare const jwtCharsRefinement: (data: string, ctx: RefinementCtx) => void;
25
+ /**
26
+ * @example
27
+ * ```ts
28
+ * type SegmentedString3 = SegmentedString<3> // `${string}.${string}.${string}`
29
+ * type SegmentedString4 = SegmentedString<4> // `${string}.${string}.${string}.${string}`
30
+ * ```
31
+ *
32
+ * @note
33
+ * This utility only provides one way type safety (A SegmentedString<4> can be
34
+ * assigned to SegmentedString<3> but not vice versa). The purpose of this
35
+ * utility is to improve DX by avoiding as many potential errors as build time.
36
+ * DO NOT rely on this to enforce security or data integrity.
37
+ */
38
+ type SegmentedString<C extends number, Acc extends string[] = [string]> = Acc['length'] extends C ? `${Acc[0]}` : `${Acc[0]}.${SegmentedString<C, [string, ...Acc]>}`;
39
+ /**
40
+ * @example
41
+ * ```ts
42
+ * const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3))
43
+ * type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}`
44
+ * ```
45
+ */
46
+ export declare const segmentedStringRefinementFactory: <C extends number>(count: C, minPartLength?: number) => (data: string, ctx: RefinementCtx) => data is SegmentedString<C, [string]>;
47
+ export {};
48
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAgB,MAAM,KAAK,CAAA;AAGjD,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAA;AACvD,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAE3D,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,QAAQ,CACrD,MAAM,SAAS,CAAC,GACZ,CAAC,GACD;KACG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,CAAC;CACjE,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CACnB,CAAA;AAED,eAAO,MAAM,SAAS,SAAU,CAAC,GAAG,SAAS,WAA4B,CAAA;AAEzE,eAAO,MAAM,iBAAiB,aACjB,SAAS,CAAC,EAAE,SACnB,CAAC,KAAK,CAAC,WAOV,CAAA;AAEH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EACrE,KAAK,EAAE,IAAI,GAAG,SAAS,GAAG,CAAC,GAAG,SAAS,CAAC,EAAE,GACzC,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,IAAI,CAAC,CAMxB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,gCACf,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,YACZ,4BAA4B,CAAC,EAAE,CAAC,CAAC,YAEpB,CAAC,MASzB,CAAA;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIpD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,SAAU,MAAM,OAAO,aAAa,KAAG,IA2BrE,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,eAAe,CAClB,CAAC,SAAS,MAAM,EAChB,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAC7B,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,GACvB,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GACX,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAA;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,4BACpC,CAAC,oCAUM,MAAM,OAAO,aAAa,yCA2CzC,CAAA"}
package/dist/util.js ADDED
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.segmentedStringRefinementFactory = exports.jwtCharsRefinement = exports.parseB64uJson = exports.cachedGetter = exports.matchesAny = exports.preferredOrderCmp = exports.isDefined = void 0;
4
+ const base64_1 = require("multiformats/bases/base64");
5
+ const zod_1 = require("zod");
6
+ const isDefined = (i) => i !== undefined;
7
+ exports.isDefined = isDefined;
8
+ const preferredOrderCmp = (order) => (a, b) => {
9
+ const aIdx = order.indexOf(a);
10
+ const bIdx = order.indexOf(b);
11
+ if (aIdx === bIdx)
12
+ return 0;
13
+ if (aIdx === -1)
14
+ return 1;
15
+ if (bIdx === -1)
16
+ return -1;
17
+ return aIdx - bIdx;
18
+ };
19
+ exports.preferredOrderCmp = preferredOrderCmp;
20
+ function matchesAny(value) {
21
+ return value == null
22
+ ? (v) => true
23
+ : Array.isArray(value)
24
+ ? (v) => value.includes(v)
25
+ : (v) => v === value;
26
+ }
27
+ exports.matchesAny = matchesAny;
28
+ /**
29
+ * Decorator to cache the result of a getter on a class instance.
30
+ */
31
+ const cachedGetter = (target, _context) => {
32
+ return function () {
33
+ const value = target.call(this);
34
+ Object.defineProperty(this, target.name, {
35
+ get: () => value,
36
+ enumerable: true,
37
+ configurable: true,
38
+ });
39
+ return value;
40
+ };
41
+ };
42
+ exports.cachedGetter = cachedGetter;
43
+ const decoder = new TextDecoder();
44
+ function parseB64uJson(input) {
45
+ const inputBytes = base64_1.base64url.baseDecode(input);
46
+ const json = decoder.decode(inputBytes);
47
+ return JSON.parse(json);
48
+ }
49
+ exports.parseB64uJson = parseB64uJson;
50
+ /**
51
+ * @example
52
+ * ```ts
53
+ * // jwtSchema will only allow base64url chars & "." (dot)
54
+ * const jwtSchema = z.string().superRefine(jwtCharsRefinement)
55
+ * ```
56
+ */
57
+ const jwtCharsRefinement = (data, ctx) => {
58
+ // Note: this is a hot path, let's avoid using a RegExp
59
+ let char;
60
+ for (let i = 0; i < data.length; i++) {
61
+ char = data.charCodeAt(i);
62
+ if (
63
+ // Base64 URL encoding (most frequent)
64
+ (65 <= char && char <= 90) || // A-Z
65
+ (97 <= char && char <= 122) || // a-z
66
+ (48 <= char && char <= 57) || // 0-9
67
+ char === 45 || // -
68
+ char === 95 || // _
69
+ // Boundary (least frequent, check last)
70
+ char === 46 // .
71
+ ) {
72
+ // continue
73
+ }
74
+ else {
75
+ // Invalid char might be a surrogate pair
76
+ const invalidChar = String.fromCodePoint(data.codePointAt(i));
77
+ return ctx.addIssue({
78
+ code: zod_1.ZodIssueCode.custom,
79
+ message: `Invalid character "${invalidChar}" in JWT at position ${i}`,
80
+ });
81
+ }
82
+ }
83
+ };
84
+ exports.jwtCharsRefinement = jwtCharsRefinement;
85
+ /**
86
+ * @example
87
+ * ```ts
88
+ * const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3))
89
+ * type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}`
90
+ * ```
91
+ */
92
+ const segmentedStringRefinementFactory = (count, minPartLength = 2) => {
93
+ if (!Number.isFinite(count) || count < 1 || (count | 0) !== count) {
94
+ throw new TypeError(`Count must be a natural number (got ${count})`);
95
+ }
96
+ const minTotalLength = count * minPartLength + (count - 1);
97
+ const errorPrefix = `Invalid JWT format`;
98
+ return (data, ctx) => {
99
+ if (data.length < minTotalLength) {
100
+ ctx.addIssue({
101
+ code: zod_1.ZodIssueCode.custom,
102
+ message: `${errorPrefix}: too short`,
103
+ });
104
+ return false;
105
+ }
106
+ let currentStart = 0;
107
+ for (let i = 0; i < count - 1; i++) {
108
+ const nextDot = data.indexOf('.', currentStart);
109
+ if (nextDot === -1) {
110
+ ctx.addIssue({
111
+ code: zod_1.ZodIssueCode.custom,
112
+ message: `${errorPrefix}: expected ${count} segments, got ${i + 1}`,
113
+ });
114
+ return false;
115
+ }
116
+ if (nextDot - currentStart < minPartLength) {
117
+ ctx.addIssue({
118
+ code: zod_1.ZodIssueCode.custom,
119
+ message: `${errorPrefix}: segment ${i + 1} is too short`,
120
+ });
121
+ return false;
122
+ }
123
+ currentStart = nextDot + 1;
124
+ }
125
+ if (data.indexOf('.', currentStart) !== -1) {
126
+ ctx.addIssue({
127
+ code: zod_1.ZodIssueCode.custom,
128
+ message: `${errorPrefix}: too many segments`,
129
+ });
130
+ return false;
131
+ }
132
+ if (data.length - currentStart < minPartLength) {
133
+ ctx.addIssue({
134
+ code: zod_1.ZodIssueCode.custom,
135
+ message: `${errorPrefix}: last segment is too short`,
136
+ });
137
+ return false;
138
+ }
139
+ return true;
140
+ };
141
+ };
142
+ exports.segmentedStringRefinementFactory = segmentedStringRefinementFactory;
143
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@atproto/jwk",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "A library for working with JSON Web Keys (JWKs) in TypeScript. This is meant to be extended by environment-specific libraries like @atproto/jwk-jose.",
6
+ "keywords": [
7
+ "atproto",
8
+ "jwk",
9
+ "jwks",
10
+ "jwt",
11
+ "json web key"
12
+ ],
13
+ "homepage": "https://atproto.com",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/bluesky-social/atproto",
17
+ "directory": "packages/oauth/jwk"
18
+ },
19
+ "type": "commonjs",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ }
27
+ },
28
+ "dependencies": {
29
+ "multiformats": "^9.9.0",
30
+ "zod": "^3.23.8"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.3.3"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc --build tsconfig.json"
37
+ }
38
+ }
package/src/alg.ts ADDED
@@ -0,0 +1,98 @@
1
+ import { JwkError } from './errors.js'
2
+ import { Jwk } from './jwk.js'
3
+
4
+ declare const process: undefined | { versions?: { node?: string } }
5
+ const IS_NODE_RUNTIME =
6
+ typeof process !== 'undefined' && typeof process?.versions?.node === 'string'
7
+
8
+ export function* jwkAlgorithms(jwk: Jwk): Generator<string> {
9
+ // Ed25519, Ed448, and secp256k1 always have "alg"
10
+ // OKP always has "use"
11
+ if (jwk.alg) {
12
+ yield jwk.alg
13
+ return
14
+ }
15
+
16
+ switch (jwk.kty) {
17
+ case 'EC': {
18
+ if (jwk.use === 'enc' || jwk.use === undefined) {
19
+ yield 'ECDH-ES'
20
+ yield 'ECDH-ES+A128KW'
21
+ yield 'ECDH-ES+A192KW'
22
+ yield 'ECDH-ES+A256KW'
23
+ }
24
+
25
+ if (jwk.use === 'sig' || jwk.use === undefined) {
26
+ const crv = 'crv' in jwk ? jwk.crv : undefined
27
+ switch (crv) {
28
+ case 'P-256':
29
+ case 'P-384':
30
+ yield `ES${crv.slice(-3)}`
31
+ break
32
+ case 'P-521':
33
+ yield 'ES512'
34
+ break
35
+ case 'secp256k1':
36
+ if (IS_NODE_RUNTIME) yield 'ES256K'
37
+ break
38
+ default:
39
+ throw new JwkError(`Unsupported crv "${crv}"`)
40
+ }
41
+ }
42
+
43
+ return
44
+ }
45
+
46
+ case 'OKP': {
47
+ if (!jwk.use) throw new JwkError('Missing "use" Parameter value')
48
+ yield 'ECDH-ES'
49
+ yield 'ECDH-ES+A128KW'
50
+ yield 'ECDH-ES+A192KW'
51
+ yield 'ECDH-ES+A256KW'
52
+ return
53
+ }
54
+
55
+ case 'RSA': {
56
+ if (jwk.use === 'enc' || jwk.use === undefined) {
57
+ yield 'RSA-OAEP'
58
+ yield 'RSA-OAEP-256'
59
+ yield 'RSA-OAEP-384'
60
+ yield 'RSA-OAEP-512'
61
+ if (IS_NODE_RUNTIME) yield 'RSA1_5'
62
+ }
63
+
64
+ if (jwk.use === 'sig' || jwk.use === undefined) {
65
+ yield 'PS256'
66
+ yield 'PS384'
67
+ yield 'PS512'
68
+ yield 'RS256'
69
+ yield 'RS384'
70
+ yield 'RS512'
71
+ }
72
+
73
+ return
74
+ }
75
+
76
+ case 'oct': {
77
+ if (jwk.use === 'enc' || jwk.use === undefined) {
78
+ yield 'A128GCMKW'
79
+ yield 'A192GCMKW'
80
+ yield 'A256GCMKW'
81
+ yield 'A128KW'
82
+ yield 'A192KW'
83
+ yield 'A256KW'
84
+ }
85
+
86
+ if (jwk.use === 'sig' || jwk.use === undefined) {
87
+ yield 'HS256'
88
+ yield 'HS384'
89
+ yield 'HS512'
90
+ }
91
+
92
+ return
93
+ }
94
+
95
+ default:
96
+ throw new JwkError(`Unsupported kty "${jwk.kty}"`)
97
+ }
98
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,56 @@
1
+ export type ErrorOptions = { cause?: unknown }
2
+
3
+ export const ERR_JWKS_NO_MATCHING_KEY = 'ERR_JWKS_NO_MATCHING_KEY'
4
+ export const ERR_JWK_INVALID = 'ERR_JWK_INVALID'
5
+ export const ERR_JWK_NOT_FOUND = 'ERR_JWK_NOT_FOUND'
6
+ export const ERR_JWT_INVALID = 'ERR_JWT_INVALID'
7
+ export const ERR_JWT_CREATE = 'ERR_JWT_CREATE'
8
+ export const ERR_JWT_VERIFY = 'ERR_JWT_VERIFY'
9
+
10
+ export class JwkError extends TypeError {
11
+ constructor(
12
+ message = 'JWK error',
13
+ public readonly code = ERR_JWK_INVALID,
14
+ options?: ErrorOptions,
15
+ ) {
16
+ super(message, options)
17
+ }
18
+ }
19
+
20
+ export class JwtCreateError extends Error {
21
+ constructor(
22
+ message = 'Unable to create JWT',
23
+ public readonly code = ERR_JWT_CREATE,
24
+ options?: ErrorOptions,
25
+ ) {
26
+ super(message, options)
27
+ }
28
+
29
+ static from(cause: unknown, code?: string, message?: string): JwtCreateError {
30
+ if (cause instanceof JwtCreateError) return cause
31
+ if (cause instanceof JwkError) {
32
+ return new JwtCreateError(message, cause.code, { cause })
33
+ }
34
+
35
+ return new JwtCreateError(message, code, { cause })
36
+ }
37
+ }
38
+
39
+ export class JwtVerifyError extends Error {
40
+ constructor(
41
+ message = 'Invalid JWT',
42
+ public readonly code = ERR_JWT_VERIFY,
43
+ options?: ErrorOptions,
44
+ ) {
45
+ super(message, options)
46
+ }
47
+
48
+ static from(cause: unknown, code?: string, message?: string): JwtVerifyError {
49
+ if (cause instanceof JwtVerifyError) return cause
50
+ if (cause instanceof JwkError) {
51
+ return new JwtVerifyError(message, cause.code, { cause })
52
+ }
53
+
54
+ return new JwtVerifyError(message, code, { cause })
55
+ }
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './alg.js'
2
+ export * from './errors.js'
3
+ export * from './jwk.js'
4
+ export * from './jwks.js'
5
+ export * from './jwt-decode.js'
6
+ export * from './jwt-verify.js'
7
+ export * from './jwt.js'
8
+ export * from './key.js'
9
+ export * from './keyset.js'
10
+ export * from './util.js'
package/src/jwk.ts ADDED
@@ -0,0 +1,141 @@
1
+ import { z } from 'zod'
2
+
3
+ export const keyUsageSchema = z.enum([
4
+ 'sign',
5
+ 'verify',
6
+ 'encrypt',
7
+ 'decrypt',
8
+ 'wrapKey',
9
+ 'unwrapKey',
10
+ 'deriveKey',
11
+ 'deriveBits',
12
+ ])
13
+
14
+ export type KeyUsage = z.infer<typeof keyUsageSchema>
15
+
16
+ /**
17
+ * The "use" and "key_ops" JWK members SHOULD NOT be used together;
18
+ * however, if both are used, the information they convey MUST be
19
+ * consistent. Applications should specify which of these members they
20
+ * use, if either is to be used by the application.
21
+ *
22
+ * @todo Actually check that "use" and "key_ops" are consistent when both are present.
23
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7517#section-4.3}
24
+ */
25
+ export const jwkBaseSchema = z.object({
26
+ kty: z.string().min(1),
27
+ alg: z.string().min(1).optional(),
28
+ kid: z.string().min(1).optional(),
29
+ ext: z.boolean().optional(),
30
+ use: z.enum(['sig', 'enc']).optional(),
31
+ key_ops: z.array(keyUsageSchema).optional(),
32
+
33
+ x5c: z.array(z.string()).optional(), // X.509 Certificate Chain
34
+ x5t: z.string().min(1).optional(), // X.509 Certificate SHA-1 Thumbprint
35
+ 'x5t#S256': z.string().min(1).optional(), // X.509 Certificate SHA-256 Thumbprint
36
+ x5u: z.string().url().optional(), // X.509 URL
37
+ })
38
+
39
+ /**
40
+ * @todo: properly implement this
41
+ */
42
+ export const jwkRsaKeySchema = jwkBaseSchema.extend({
43
+ kty: z.literal('RSA'),
44
+ alg: z
45
+ .enum(['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512'])
46
+ .optional(),
47
+
48
+ n: z.string().min(1), // Modulus
49
+ e: z.string().min(1), // Exponent
50
+
51
+ d: z.string().min(1).optional(), // Private Exponent
52
+ p: z.string().min(1).optional(), // First Prime Factor
53
+ q: z.string().min(1).optional(), // Second Prime Factor
54
+ dp: z.string().min(1).optional(), // First Factor CRT Exponent
55
+ dq: z.string().min(1).optional(), // Second Factor CRT Exponent
56
+ qi: z.string().min(1).optional(), // First CRT Coefficient
57
+ oth: z
58
+ .array(
59
+ z.object({
60
+ r: z.string().optional(),
61
+ d: z.string().optional(),
62
+ t: z.string().optional(),
63
+ }),
64
+ )
65
+ .nonempty()
66
+
67
+ .optional(), // Other Primes Info
68
+ })
69
+
70
+ export const jwkEcKeySchema = jwkBaseSchema.extend({
71
+ kty: z.literal('EC'),
72
+ alg: z.enum(['ES256', 'ES384', 'ES512']).optional(),
73
+ crv: z.enum(['P-256', 'P-384', 'P-521']),
74
+
75
+ x: z.string().min(1),
76
+ y: z.string().min(1),
77
+
78
+ d: z.string().min(1).optional(), // ECC Private Key
79
+ })
80
+
81
+ export const jwkEcSecp256k1KeySchema = jwkBaseSchema.extend({
82
+ kty: z.literal('EC'),
83
+ alg: z.enum(['ES256K']).optional(),
84
+ crv: z.enum(['secp256k1']),
85
+
86
+ x: z.string().min(1),
87
+ y: z.string().min(1),
88
+
89
+ d: z.string().min(1).optional(), // ECC Private Key
90
+ })
91
+
92
+ export const jwkOkpKeySchema = jwkBaseSchema.extend({
93
+ kty: z.literal('OKP'),
94
+ alg: z.enum(['EdDSA']).optional(),
95
+ crv: z.enum(['Ed25519', 'Ed448']),
96
+
97
+ x: z.string().min(1),
98
+ d: z.string().min(1).optional(), // ECC Private Key
99
+ })
100
+
101
+ export const jwkSymKeySchema = jwkBaseSchema.extend({
102
+ kty: z.literal('oct'), // Octet Sequence (used to represent symmetric keys)
103
+ alg: z.enum(['HS256', 'HS384', 'HS512']).optional(),
104
+
105
+ k: z.string(), // Key Value (base64url encoded)
106
+ })
107
+
108
+ export const jwkUnknownKeySchema = jwkBaseSchema.extend({
109
+ kty: z
110
+ .string()
111
+ .refine((v) => v !== 'RSA' && v !== 'EC' && v !== 'OKP' && v !== 'oct'),
112
+ })
113
+
114
+ export const jwkSchema = z.union([
115
+ jwkUnknownKeySchema,
116
+ jwkRsaKeySchema,
117
+ jwkEcKeySchema,
118
+ jwkEcSecp256k1KeySchema,
119
+ jwkOkpKeySchema,
120
+ jwkSymKeySchema,
121
+ ])
122
+
123
+ export type Jwk = z.infer<typeof jwkSchema>
124
+
125
+ export const jwkValidator = jwkSchema
126
+ .refine((k) => k.use != null || k.key_ops != null, 'use or key_ops required')
127
+ .refine(
128
+ (k) =>
129
+ !k.use ||
130
+ !k.key_ops ||
131
+ k.key_ops.every((o) =>
132
+ k.use === 'sig'
133
+ ? o === 'sign' || o === 'verify'
134
+ : o === 'encrypt' || o === 'decrypt',
135
+ ),
136
+ 'use and key_ops must be consistent',
137
+ )
138
+
139
+ export const jwkPubSchema = jwkValidator
140
+ .refine((k) => k.kid != null, 'kid is required')
141
+ .refine((k) => !('k' in k) && !('d' in k), 'private key not allowed')
package/src/jwks.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod'
2
+
3
+ import { jwkPubSchema, jwkSchema } from './jwk.js'
4
+
5
+ export const jwksSchema = z.object({
6
+ keys: z.array(jwkSchema),
7
+ })
8
+
9
+ export type Jwks = z.infer<typeof jwksSchema>
10
+
11
+ export const jwksPubSchema = z.object({
12
+ keys: z.array(jwkPubSchema),
13
+ })
14
+
15
+ export type JwksPub = z.infer<typeof jwksPubSchema>
@@ -0,0 +1,27 @@
1
+ import { ERR_JWT_INVALID, JwtVerifyError } from './errors.js'
2
+ import {
3
+ JwtHeader,
4
+ JwtPayload,
5
+ jwtHeaderSchema,
6
+ jwtPayloadSchema,
7
+ } from './jwt.js'
8
+ import { parseB64uJson } from './util.js'
9
+
10
+ export function unsafeDecodeJwt(jwt: string): {
11
+ header: JwtHeader
12
+ payload: JwtPayload
13
+ } {
14
+ const { 0: headerEnc, 1: payloadEnc, length } = jwt.split('.')
15
+ if (length > 3 || length < 2) {
16
+ throw new JwtVerifyError(undefined, ERR_JWT_INVALID)
17
+ }
18
+
19
+ const header = jwtHeaderSchema.parse(parseB64uJson(headerEnc!))
20
+ if (length === 2 && header?.alg !== 'none') {
21
+ throw new JwtVerifyError(undefined, ERR_JWT_INVALID)
22
+ }
23
+
24
+ const payload = jwtPayloadSchema.parse(parseB64uJson(payloadEnc!))
25
+
26
+ return { header, payload }
27
+ }
@@ -0,0 +1,22 @@
1
+ import { JwtHeader, JwtPayload } from './jwt.js'
2
+ import { RequiredKey } from './util.js'
3
+
4
+ export type VerifyOptions<C extends string = string> = {
5
+ audience?: string | readonly string[]
6
+ /** in seconds */
7
+ clockTolerance?: number
8
+ issuer?: string | readonly string[]
9
+ /** in seconds */
10
+ maxTokenAge?: number
11
+ subject?: string
12
+ typ?: string
13
+ currentDate?: Date
14
+ requiredClaims?: readonly C[]
15
+ }
16
+
17
+ export type VerifyPayload = Record<string, unknown>
18
+
19
+ export type VerifyResult<P extends VerifyPayload, C extends string> = {
20
+ payload: RequiredKey<P & JwtPayload, C>
21
+ protectedHeader: JwtHeader
22
+ }