@sdxc/jwt 0.0.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +450 -0
- package/dist/algorithm.d.ts +25 -0
- package/dist/algorithm.js +25 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/jwk.d.ts +176 -0
- package/dist/jwk.js +210 -0
- package/dist/jwt.d.ts +217 -0
- package/dist/jwt.js +322 -0
- package/dist/key-storage.d.ts +59 -0
- package/dist/key-storage.js +12 -0
- package/dist/lib/parser.d.ts +136 -0
- package/dist/lib/parser.js +190 -0
- package/package.json +24 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-checked reads over a decoded JWT claim set.
|
|
3
|
+
*
|
|
4
|
+
* A JWT payload arrives as `unknown`-shaped JSON, so every claim a token class
|
|
5
|
+
* exposes has to be both looked up and type-checked before it can be returned.
|
|
6
|
+
* This module is the one place that does it, and the errors it raises name the
|
|
7
|
+
* claim and the type mismatch so a malformed token is diagnosable from the log
|
|
8
|
+
* line alone.
|
|
9
|
+
*
|
|
10
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
11
|
+
* @copyright Sergio Xalambrí 2026
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Base class for every failure this parser raises.
|
|
15
|
+
*
|
|
16
|
+
* A single base lets a caller distinguish malformed claims from a genuine bug
|
|
17
|
+
* with one `instanceof` check; subclasses still tell missing from mistyped.
|
|
18
|
+
*/
|
|
19
|
+
export class ParserError extends Error {
|
|
20
|
+
name = "ParserError";
|
|
21
|
+
}
|
|
22
|
+
/** Raised when a claim a token class requires is absent from the payload. */
|
|
23
|
+
export class MissingKeyError extends ParserError {
|
|
24
|
+
name = "ParserMissingKeyError";
|
|
25
|
+
/**
|
|
26
|
+
* Builds the error for an absent claim.
|
|
27
|
+
*
|
|
28
|
+
* @param key - Name of the claim that was not present.
|
|
29
|
+
*/
|
|
30
|
+
constructor(key) {
|
|
31
|
+
super(`Key "${key}" does not exist`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Raised when a claim is present but holds a value of the wrong JSON type. */
|
|
35
|
+
export class InvalidTypeError extends ParserError {
|
|
36
|
+
name = "ParserInvalidTypeError";
|
|
37
|
+
/**
|
|
38
|
+
* Builds the error for a claim of an unexpected type.
|
|
39
|
+
*
|
|
40
|
+
* @param key - Name of the claim that was read.
|
|
41
|
+
* @param expected - Type the caller asked for.
|
|
42
|
+
* @param actual - Type the payload actually carried.
|
|
43
|
+
*/
|
|
44
|
+
constructor(key, expected, actual) {
|
|
45
|
+
super(`Key "${key}" expected ${expected} but got ${actual}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reads values out of a plain object, checking the type of each one.
|
|
50
|
+
*
|
|
51
|
+
* Throws on a mismatch, so a `JWT` subclass's getters can return the claim's
|
|
52
|
+
* own type directly.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* get email(): string {
|
|
56
|
+
* return this.parser.string("email");
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
export class ObjectParser {
|
|
60
|
+
/** The object being read from, kept as `object` so reads stay explicit. */
|
|
61
|
+
#value;
|
|
62
|
+
/**
|
|
63
|
+
* Wraps an object for type-checked reads.
|
|
64
|
+
*
|
|
65
|
+
* @param value - The decoded payload to read from.
|
|
66
|
+
* @throws {InvalidTypeError} When the value is not a non-null object.
|
|
67
|
+
*/
|
|
68
|
+
constructor(value) {
|
|
69
|
+
if (typeof value !== "object" || value === null) {
|
|
70
|
+
throw new InvalidTypeError("object", "object", value === null ? "null" : typeof value);
|
|
71
|
+
}
|
|
72
|
+
this.#value = value;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reports whether a key is present, including when it holds `null`.
|
|
76
|
+
*
|
|
77
|
+
* This is the guard an optional claim needs, since every typed read below throws
|
|
78
|
+
* on a key that is not there.
|
|
79
|
+
*
|
|
80
|
+
* @param key - Claim name to look for.
|
|
81
|
+
* @returns Whether the key exists on the object.
|
|
82
|
+
* @example
|
|
83
|
+
* if (this.parser.has("nonce")) return this.parser.string("nonce");
|
|
84
|
+
*/
|
|
85
|
+
has(key) {
|
|
86
|
+
return key in this.#value;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reads a key exactly as stored, leaving the type check to the caller.
|
|
90
|
+
*
|
|
91
|
+
* @param key - Claim name to read.
|
|
92
|
+
* @returns The raw value, which the typed readers narrow.
|
|
93
|
+
* @throws {MissingKeyError} When the key is not present.
|
|
94
|
+
*/
|
|
95
|
+
get(key) {
|
|
96
|
+
if (!(key in this.#value))
|
|
97
|
+
throw new MissingKeyError(key);
|
|
98
|
+
return this.#value[key];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Reads a key as a string.
|
|
102
|
+
*
|
|
103
|
+
* @param key - Claim name to read.
|
|
104
|
+
* @returns The string value.
|
|
105
|
+
* @throws {MissingKeyError} When the key is not present.
|
|
106
|
+
* @throws {InvalidTypeError} When the value is not a string.
|
|
107
|
+
* @example
|
|
108
|
+
* this.parser.string("sub"); // "user-123"
|
|
109
|
+
*/
|
|
110
|
+
string(key) {
|
|
111
|
+
let value = this.get(key);
|
|
112
|
+
if (typeof value === "string")
|
|
113
|
+
return value;
|
|
114
|
+
throw new InvalidTypeError(key, "string", typeOf(value));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Reads a key as a number.
|
|
118
|
+
*
|
|
119
|
+
* Time claims (`exp`, `iat`, `nbf`) are numbers in seconds, so this is what the
|
|
120
|
+
* date-shaped accessors are built on.
|
|
121
|
+
*
|
|
122
|
+
* @param key - Claim name to read.
|
|
123
|
+
* @returns The number value.
|
|
124
|
+
* @throws {MissingKeyError} When the key is not present.
|
|
125
|
+
* @throws {InvalidTypeError} When the value is not a number.
|
|
126
|
+
*/
|
|
127
|
+
number(key) {
|
|
128
|
+
let value = this.get(key);
|
|
129
|
+
if (typeof value === "number")
|
|
130
|
+
return value;
|
|
131
|
+
throw new InvalidTypeError(key, "number", typeOf(value));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Reads a key as a boolean.
|
|
135
|
+
*
|
|
136
|
+
* Strict: only an actual boolean passes, so a stringly-typed `email_verified`
|
|
137
|
+
* claim from an identity provider surfaces as a type error.
|
|
138
|
+
*
|
|
139
|
+
* @param key - Claim name to read.
|
|
140
|
+
* @returns The boolean value.
|
|
141
|
+
* @throws {MissingKeyError} When the key is not present.
|
|
142
|
+
* @throws {InvalidTypeError} When the value is not a boolean.
|
|
143
|
+
*/
|
|
144
|
+
boolean(key) {
|
|
145
|
+
let value = this.get(key);
|
|
146
|
+
if (typeof value === "boolean")
|
|
147
|
+
return value;
|
|
148
|
+
throw new InvalidTypeError(key, "boolean", typeOf(value));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reads a key as a nested object, wrapped for further type-checked reads.
|
|
152
|
+
*
|
|
153
|
+
* @param key - Claim name to read.
|
|
154
|
+
* @returns A parser over the nested object.
|
|
155
|
+
* @throws {MissingKeyError} When the key is not present.
|
|
156
|
+
* @throws {InvalidTypeError} When the value is not a non-null object.
|
|
157
|
+
* @example
|
|
158
|
+
* this.parser.object("events").has("http://schemas.openid.net/event/backchannel-logout");
|
|
159
|
+
*/
|
|
160
|
+
object(key) {
|
|
161
|
+
let value = this.get(key);
|
|
162
|
+
if (typeof value === "object" && value !== null)
|
|
163
|
+
return new ObjectParser(value);
|
|
164
|
+
throw new InvalidTypeError(key, "object", typeOf(value));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Returns the wrapped object, so a claim set can be handed on whole.
|
|
168
|
+
*
|
|
169
|
+
* @returns The object this parser reads from.
|
|
170
|
+
*/
|
|
171
|
+
valueOf() {
|
|
172
|
+
return this.#value;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Names the type of a value for an error message.
|
|
177
|
+
*
|
|
178
|
+
* `typeof null` is `"object"`, which reads as a lie in `expected object but got
|
|
179
|
+
* object`, so `null` gets its own name.
|
|
180
|
+
*
|
|
181
|
+
* @param value - The value whose type to name.
|
|
182
|
+
* @returns A short type name suitable for an error message.
|
|
183
|
+
*/
|
|
184
|
+
function typeOf(value) {
|
|
185
|
+
if (value === null)
|
|
186
|
+
return "null";
|
|
187
|
+
if (Array.isArray(value))
|
|
188
|
+
return "array";
|
|
189
|
+
return typeof value;
|
|
190
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sdxc/jwt",
|
|
3
|
+
"version": "0.0.0-pre.1",
|
|
4
|
+
"description": "JWT payload classes and the keys that sign them",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./algorithm": "./dist/algorithm.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@sdxc/duration": "0.0.0-pre.1",
|
|
13
|
+
"jose": "^6.2.10"
|
|
14
|
+
},
|
|
15
|
+
"gitHead": "bbc1db99f3fcc3c903251a70582c6b0cf8e808cb",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
22
|
+
"directory": "packages/jwt"
|
|
23
|
+
}
|
|
24
|
+
}
|