@inai-dev/shared 1.1.0 → 1.3.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.
- package/README.md +22 -0
- package/dist/index.cjs +130 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +124 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,6 +36,19 @@ const expired = isTokenExpired(token); // boolean
|
|
|
36
36
|
const claims = getClaimsFromToken(token); // JWTClaims or null
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### JWT Verification (ES256)
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { JWKSClient, decodeJWTHeader, verifyES256 } from "@inai-dev/shared";
|
|
43
|
+
|
|
44
|
+
const jwks = new JWKSClient("https://apiauth.inai.dev/.well-known/jwks.json");
|
|
45
|
+
const header = decodeJWTHeader(token);
|
|
46
|
+
const publicKey = await jwks.getKey(header.kid);
|
|
47
|
+
const claims = await verifyES256(token, publicKey);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `JWKSClient` fetches and caches public keys from the JWKS endpoint (default TTL: 5 minutes) with built-in rate limiting to prevent refetch abuse. Failed fetch attempts are also throttled to avoid amplifying traffic against a downed endpoint.
|
|
51
|
+
|
|
39
52
|
### Validators
|
|
40
53
|
|
|
41
54
|
```ts
|
|
@@ -86,6 +99,15 @@ import { normalizeApiUrl, buildEndpoint } from "@inai-dev/shared";
|
|
|
86
99
|
| `COOKIE_AUTH_SESSION` | Constant | Session cookie name |
|
|
87
100
|
| `DEFAULT_API_URL` | Constant | Default API URL (internal) |
|
|
88
101
|
| `HEADER_PUBLISHABLE_KEY` | Constant | Publishable key header name |
|
|
102
|
+
| `DEFAULT_JWKS_URL` | Constant | Default JWKS endpoint URL |
|
|
103
|
+
| `JWKSClient` | Class | Client for fetching and caching JWKS public keys |
|
|
104
|
+
| `decodeJWTHeader` | Function | Decode JWT header to extract `alg` and `kid` |
|
|
105
|
+
| `verifyES256` | Function | Verify ES256 (ECDSA P-256) JWT signature |
|
|
106
|
+
| `importJWKPublicKey` | Function | Import JWK public key for ES256 verification |
|
|
107
|
+
|
|
108
|
+
## Questions & Support
|
|
109
|
+
|
|
110
|
+
Visit [https://inai.dev](https://inai.dev) for documentation, guides, and support.
|
|
89
111
|
|
|
90
112
|
## License
|
|
91
113
|
|
package/dist/index.cjs
CHANGED
|
@@ -26,19 +26,24 @@ __export(index_exports, {
|
|
|
26
26
|
DEFAULT_AFTER_SIGN_IN_URL: () => DEFAULT_AFTER_SIGN_IN_URL,
|
|
27
27
|
DEFAULT_AFTER_SIGN_OUT_URL: () => DEFAULT_AFTER_SIGN_OUT_URL,
|
|
28
28
|
DEFAULT_API_URL: () => DEFAULT_API_URL,
|
|
29
|
+
DEFAULT_JWKS_URL: () => DEFAULT_JWKS_URL,
|
|
29
30
|
DEFAULT_SIGN_IN_URL: () => DEFAULT_SIGN_IN_URL,
|
|
30
31
|
DEFAULT_SIGN_UP_URL: () => DEFAULT_SIGN_UP_URL,
|
|
31
32
|
HEADER_AUTHORIZATION: () => HEADER_AUTHORIZATION,
|
|
32
33
|
HEADER_INAI_AUTH: () => HEADER_INAI_AUTH,
|
|
33
34
|
HEADER_PUBLISHABLE_KEY: () => HEADER_PUBLISHABLE_KEY,
|
|
34
35
|
InAIAuthError: () => InAIAuthError,
|
|
36
|
+
JWKSClient: () => JWKSClient,
|
|
35
37
|
buildEndpoint: () => buildEndpoint,
|
|
38
|
+
decodeJWTHeader: () => decodeJWTHeader,
|
|
36
39
|
decodeJWTPayload: () => decodeJWTPayload,
|
|
37
40
|
getClaimsFromToken: () => getClaimsFromToken,
|
|
41
|
+
importJWKPublicKey: () => importJWKPublicKey,
|
|
38
42
|
isStrongPassword: () => isStrongPassword,
|
|
39
43
|
isTokenExpired: () => isTokenExpired,
|
|
40
44
|
isValidEmail: () => isValidEmail,
|
|
41
|
-
normalizeApiUrl: () => normalizeApiUrl
|
|
45
|
+
normalizeApiUrl: () => normalizeApiUrl,
|
|
46
|
+
verifyES256: () => verifyES256
|
|
42
47
|
});
|
|
43
48
|
module.exports = __toCommonJS(index_exports);
|
|
44
49
|
|
|
@@ -62,13 +67,22 @@ var InAIAuthError = class extends Error {
|
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
// src/jwt.ts
|
|
70
|
+
function base64urlDecode(str) {
|
|
71
|
+
let padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
72
|
+
const remainder = padded.length % 4;
|
|
73
|
+
if (remainder === 2) padded += "==";
|
|
74
|
+
else if (remainder === 3) padded += "=";
|
|
75
|
+
const binString = atob(padded);
|
|
76
|
+
return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));
|
|
77
|
+
}
|
|
78
|
+
function base64urlToString(str) {
|
|
79
|
+
return new TextDecoder().decode(base64urlDecode(str));
|
|
80
|
+
}
|
|
65
81
|
function decodeJWTPayload(token) {
|
|
66
82
|
try {
|
|
67
83
|
const parts = token.split(".");
|
|
68
84
|
if (parts.length !== 3) return null;
|
|
69
|
-
|
|
70
|
-
const decoded = atob(payload);
|
|
71
|
-
return JSON.parse(decoded);
|
|
85
|
+
return JSON.parse(base64urlToString(parts[1]));
|
|
72
86
|
} catch {
|
|
73
87
|
return null;
|
|
74
88
|
}
|
|
@@ -81,6 +95,111 @@ function isTokenExpired(token) {
|
|
|
81
95
|
function getClaimsFromToken(token) {
|
|
82
96
|
return decodeJWTPayload(token);
|
|
83
97
|
}
|
|
98
|
+
function decodeJWTHeader(token) {
|
|
99
|
+
try {
|
|
100
|
+
const parts = token.split(".");
|
|
101
|
+
if (parts.length !== 3) return null;
|
|
102
|
+
return JSON.parse(base64urlToString(parts[0]));
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function importJWKPublicKey(jwk) {
|
|
108
|
+
return crypto.subtle.importKey(
|
|
109
|
+
"jwk",
|
|
110
|
+
jwk,
|
|
111
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
112
|
+
false,
|
|
113
|
+
["verify"]
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
async function verifyES256(token, publicKey) {
|
|
117
|
+
try {
|
|
118
|
+
const parts = token.split(".");
|
|
119
|
+
if (parts.length !== 3) return null;
|
|
120
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
121
|
+
const encoder = new TextEncoder();
|
|
122
|
+
const signingInput = encoder.encode(`${headerB64}.${payloadB64}`);
|
|
123
|
+
const signature = base64urlDecode(signatureB64);
|
|
124
|
+
const valid = await crypto.subtle.verify(
|
|
125
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
126
|
+
publicKey,
|
|
127
|
+
signature.buffer,
|
|
128
|
+
signingInput
|
|
129
|
+
);
|
|
130
|
+
if (!valid) return null;
|
|
131
|
+
const payloadJson = new TextDecoder().decode(base64urlDecode(payloadB64));
|
|
132
|
+
const payload = JSON.parse(payloadJson);
|
|
133
|
+
if (payload.exp && Date.now() >= payload.exp * 1e3) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return payload;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/jwks.ts
|
|
143
|
+
var JWKSClient = class {
|
|
144
|
+
cache = /* @__PURE__ */ new Map();
|
|
145
|
+
rawKeys = /* @__PURE__ */ new Map();
|
|
146
|
+
lastFetchedAt = 0;
|
|
147
|
+
lastAttemptedAt = 0;
|
|
148
|
+
pendingInvalidation = false;
|
|
149
|
+
jwksUrl;
|
|
150
|
+
cacheTTL;
|
|
151
|
+
minRefetchInterval;
|
|
152
|
+
constructor(jwksUrl, cacheTTL = 5 * 60 * 1e3, minRefetchInterval = 1e4) {
|
|
153
|
+
this.jwksUrl = jwksUrl;
|
|
154
|
+
this.cacheTTL = cacheTTL;
|
|
155
|
+
this.minRefetchInterval = minRefetchInterval;
|
|
156
|
+
}
|
|
157
|
+
async getKey(kid) {
|
|
158
|
+
if (this.isCacheFresh() && !this.pendingInvalidation) {
|
|
159
|
+
const cached = this.cache.get(kid);
|
|
160
|
+
if (cached) return cached;
|
|
161
|
+
}
|
|
162
|
+
if (Date.now() - this.lastAttemptedAt >= this.minRefetchInterval) {
|
|
163
|
+
await this.fetchKeys();
|
|
164
|
+
this.pendingInvalidation = false;
|
|
165
|
+
} else {
|
|
166
|
+
this.pendingInvalidation = false;
|
|
167
|
+
}
|
|
168
|
+
const key = this.cache.get(kid);
|
|
169
|
+
if (key) return key;
|
|
170
|
+
throw new Error(`Unknown key ID: ${kid}`);
|
|
171
|
+
}
|
|
172
|
+
isCacheFresh() {
|
|
173
|
+
return Date.now() - this.lastFetchedAt < this.cacheTTL;
|
|
174
|
+
}
|
|
175
|
+
async fetchKeys() {
|
|
176
|
+
this.lastAttemptedAt = Date.now();
|
|
177
|
+
const res = await fetch(this.jwksUrl);
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
throw new Error(`Failed to fetch JWKS: ${res.status}`);
|
|
180
|
+
}
|
|
181
|
+
const data = await res.json();
|
|
182
|
+
this.cache.clear();
|
|
183
|
+
this.rawKeys.clear();
|
|
184
|
+
for (const jwk of data.keys) {
|
|
185
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || jwk.alg !== "ES256") continue;
|
|
186
|
+
const publicKeyJwk = {
|
|
187
|
+
kty: jwk.kty,
|
|
188
|
+
crv: jwk.crv,
|
|
189
|
+
x: jwk.x,
|
|
190
|
+
y: jwk.y
|
|
191
|
+
};
|
|
192
|
+
const cryptoKey = await importJWKPublicKey(publicKeyJwk);
|
|
193
|
+
this.cache.set(jwk.kid, cryptoKey);
|
|
194
|
+
this.rawKeys.set(jwk.kid, publicKeyJwk);
|
|
195
|
+
}
|
|
196
|
+
this.lastFetchedAt = Date.now();
|
|
197
|
+
}
|
|
198
|
+
/** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */
|
|
199
|
+
invalidate() {
|
|
200
|
+
this.pendingInvalidation = true;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
84
203
|
|
|
85
204
|
// src/constants.ts
|
|
86
205
|
var COOKIE_AUTH_TOKEN = "auth_token";
|
|
@@ -94,6 +213,7 @@ var HEADER_PUBLISHABLE_KEY = "X-Publishable-Key";
|
|
|
94
213
|
var HEADER_AUTHORIZATION = "Authorization";
|
|
95
214
|
var HEADER_INAI_AUTH = "x-inai-auth";
|
|
96
215
|
var DEFAULT_API_URL = "https://apiauth.inai.dev";
|
|
216
|
+
var DEFAULT_JWKS_URL = "https://apiauth.inai.dev/.well-known/jwks.json";
|
|
97
217
|
|
|
98
218
|
// src/url.ts
|
|
99
219
|
function normalizeApiUrl(url) {
|
|
@@ -123,18 +243,23 @@ function isStrongPassword(password) {
|
|
|
123
243
|
DEFAULT_AFTER_SIGN_IN_URL,
|
|
124
244
|
DEFAULT_AFTER_SIGN_OUT_URL,
|
|
125
245
|
DEFAULT_API_URL,
|
|
246
|
+
DEFAULT_JWKS_URL,
|
|
126
247
|
DEFAULT_SIGN_IN_URL,
|
|
127
248
|
DEFAULT_SIGN_UP_URL,
|
|
128
249
|
HEADER_AUTHORIZATION,
|
|
129
250
|
HEADER_INAI_AUTH,
|
|
130
251
|
HEADER_PUBLISHABLE_KEY,
|
|
131
252
|
InAIAuthError,
|
|
253
|
+
JWKSClient,
|
|
132
254
|
buildEndpoint,
|
|
255
|
+
decodeJWTHeader,
|
|
133
256
|
decodeJWTPayload,
|
|
134
257
|
getClaimsFromToken,
|
|
258
|
+
importJWKPublicKey,
|
|
135
259
|
isStrongPassword,
|
|
136
260
|
isTokenExpired,
|
|
137
261
|
isValidEmail,
|
|
138
|
-
normalizeApiUrl
|
|
262
|
+
normalizeApiUrl,
|
|
263
|
+
verifyES256
|
|
139
264
|
});
|
|
140
265
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/jwt.ts","../src/constants.ts","../src/url.ts","../src/validators.ts"],"sourcesContent":["export { InAIAuthError } from \"./errors\";\nexport {\n decodeJWTPayload,\n isTokenExpired,\n getClaimsFromToken,\n} from \"./jwt\";\nexport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n DEFAULT_SIGN_IN_URL,\n DEFAULT_SIGN_UP_URL,\n DEFAULT_AFTER_SIGN_IN_URL,\n DEFAULT_AFTER_SIGN_OUT_URL,\n HEADER_PUBLISHABLE_KEY,\n HEADER_AUTHORIZATION,\n HEADER_INAI_AUTH,\n DEFAULT_API_URL,\n} from \"./constants\";\nexport { normalizeApiUrl, buildEndpoint } from \"./url\";\nexport { isValidEmail, isStrongPassword } from \"./validators\";\n","import type { InAIAuthErrorBody } from \"@inai-dev/types\";\n\nexport class InAIAuthError extends Error {\n status: number;\n body: InAIAuthErrorBody;\n code: string;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"InAIAuthError\";\n this.status = status;\n const parsed = body as Record<string, unknown> | null;\n this.body = {\n code: (parsed?.code as string) ?? \"UNKNOWN_ERROR\",\n detail: (parsed?.detail as string) ?? message,\n field: (parsed?.field as string) ?? undefined,\n };\n this.code = this.body.code;\n }\n}\n","import type { JWTClaims } from \"@inai-dev/types\";\n\nexport function decodeJWTPayload(token: string): JWTClaims | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const decoded = atob(payload);\n return JSON.parse(decoded);\n } catch {\n return null;\n }\n}\n\nexport function isTokenExpired(token: string): boolean {\n const claims = decodeJWTPayload(token);\n if (!claims?.exp) return true;\n return Date.now() >= claims.exp * 1000;\n}\n\nexport function getClaimsFromToken(token: string): JWTClaims | null {\n return decodeJWTPayload(token);\n}\n","export const COOKIE_AUTH_TOKEN = \"auth_token\";\nexport const COOKIE_REFRESH_TOKEN = \"refresh_token\";\nexport const COOKIE_AUTH_SESSION = \"auth_session\";\n\nexport const DEFAULT_SIGN_IN_URL = \"/login\";\nexport const DEFAULT_SIGN_UP_URL = \"/register\";\nexport const DEFAULT_AFTER_SIGN_IN_URL = \"/\";\nexport const DEFAULT_AFTER_SIGN_OUT_URL = \"/login\";\n\nexport const HEADER_PUBLISHABLE_KEY = \"X-Publishable-Key\";\nexport const HEADER_AUTHORIZATION = \"Authorization\";\nexport const HEADER_INAI_AUTH = \"x-inai-auth\";\n\nexport const DEFAULT_API_URL = \"https://apiauth.inai.dev\";\n","export function normalizeApiUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nexport function buildEndpoint(apiUrl: string, path: string): string {\n return `${normalizeApiUrl(apiUrl)}${path}`;\n}\n","export function isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nexport function isStrongPassword(password: string): {\n valid: boolean;\n errors: string[];\n} {\n const errors: string[] = [];\n if (password.length < 8) errors.push(\"Password must be at least 8 characters\");\n if (!/[A-Z]/.test(password)) errors.push(\"Password must contain an uppercase letter\");\n if (!/[a-z]/.test(password)) errors.push(\"Password must contain a lowercase letter\");\n if (!/[0-9]/.test(password)) errors.push(\"Password must contain a number\");\n return { valid: errors.length === 0, errors };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,UAAM,SAAS;AACf,SAAK,OAAO;AAAA,MACV,MAAO,QAAQ,QAAmB;AAAA,MAClC,QAAS,QAAQ,UAAqB;AAAA,MACtC,OAAQ,QAAQ,SAAoB;AAAA,IACtC;AACA,SAAK,OAAO,KAAK,KAAK;AAAA,EACxB;AACF;;;ACjBO,SAAS,iBAAiB,OAAiC;AAChE,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,UAAM,UAAU,KAAK,OAAO;AAC5B,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,OAAwB;AACrD,QAAM,SAAS,iBAAiB,KAAK;AACrC,MAAI,CAAC,QAAQ,IAAK,QAAO;AACzB,SAAO,KAAK,IAAI,KAAK,OAAO,MAAM;AACpC;AAEO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,iBAAiB,KAAK;AAC/B;;;ACtBO,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAE5B,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,4BAA4B;AAClC,IAAM,6BAA6B;AAEnC,IAAM,yBAAyB;AAC/B,IAAM,uBAAuB;AAC7B,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;;;ACbxB,SAAS,gBAAgB,KAAqB;AACnD,SAAO,IAAI,QAAQ,OAAO,EAAE;AAC9B;AAEO,SAAS,cAAc,QAAgB,MAAsB;AAClE,SAAO,GAAG,gBAAgB,MAAM,CAAC,GAAG,IAAI;AAC1C;;;ACNO,SAAS,aAAa,OAAwB;AACnD,SAAO,6BAA6B,KAAK,KAAK;AAChD;AAEO,SAAS,iBAAiB,UAG/B;AACA,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS,SAAS,EAAG,QAAO,KAAK,wCAAwC;AAC7E,MAAI,CAAC,QAAQ,KAAK,QAAQ,EAAG,QAAO,KAAK,2CAA2C;AACpF,MAAI,CAAC,QAAQ,KAAK,QAAQ,EAAG,QAAO,KAAK,0CAA0C;AACnF,MAAI,CAAC,QAAQ,KAAK,QAAQ,EAAG,QAAO,KAAK,gCAAgC;AACzE,SAAO,EAAE,OAAO,OAAO,WAAW,GAAG,OAAO;AAC9C;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/jwt.ts","../src/jwks.ts","../src/constants.ts","../src/url.ts","../src/validators.ts"],"sourcesContent":["export { InAIAuthError } from \"./errors\";\nexport {\n decodeJWTPayload,\n isTokenExpired,\n getClaimsFromToken,\n decodeJWTHeader,\n verifyES256,\n importJWKPublicKey,\n} from \"./jwt\";\nexport { JWKSClient } from \"./jwks\";\nexport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n DEFAULT_SIGN_IN_URL,\n DEFAULT_SIGN_UP_URL,\n DEFAULT_AFTER_SIGN_IN_URL,\n DEFAULT_AFTER_SIGN_OUT_URL,\n HEADER_PUBLISHABLE_KEY,\n HEADER_AUTHORIZATION,\n HEADER_INAI_AUTH,\n DEFAULT_API_URL,\n DEFAULT_JWKS_URL,\n} from \"./constants\";\nexport { normalizeApiUrl, buildEndpoint } from \"./url\";\nexport { isValidEmail, isStrongPassword } from \"./validators\";\n","import type { InAIAuthErrorBody } from \"@inai-dev/types\";\n\nexport class InAIAuthError extends Error {\n status: number;\n body: InAIAuthErrorBody;\n code: string;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"InAIAuthError\";\n this.status = status;\n const parsed = body as Record<string, unknown> | null;\n this.body = {\n code: (parsed?.code as string) ?? \"UNKNOWN_ERROR\",\n detail: (parsed?.detail as string) ?? message,\n field: (parsed?.field as string) ?? undefined,\n };\n this.code = this.body.code;\n }\n}\n","import type { JWTClaims } from \"@inai-dev/types\";\n\nfunction base64urlDecode(str: string): Uint8Array {\n let padded = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const remainder = padded.length % 4;\n if (remainder === 2) padded += \"==\";\n else if (remainder === 3) padded += \"=\";\n const binString = atob(padded);\n return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));\n}\n\nfunction base64urlToString(str: string): string {\n return new TextDecoder().decode(base64urlDecode(str));\n}\n\nexport function decodeJWTPayload(token: string): JWTClaims | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n return JSON.parse(base64urlToString(parts[1]));\n } catch {\n return null;\n }\n}\n\nexport function isTokenExpired(token: string): boolean {\n const claims = decodeJWTPayload(token);\n if (!claims?.exp) return true;\n return Date.now() >= claims.exp * 1000;\n}\n\nexport function getClaimsFromToken(token: string): JWTClaims | null {\n return decodeJWTPayload(token);\n}\n\nexport function decodeJWTHeader(token: string): { alg: string; kid: string; typ?: string } | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n return JSON.parse(base64urlToString(parts[0]));\n } catch {\n return null;\n }\n}\n\nexport async function importJWKPublicKey(jwk: JsonWebKey): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"verify\"],\n );\n}\n\nexport async function verifyES256(token: string, publicKey: CryptoKey): Promise<JWTClaims | null> {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n\n const [headerB64, payloadB64, signatureB64] = parts;\n\n const encoder = new TextEncoder();\n const signingInput = encoder.encode(`${headerB64}.${payloadB64}`);\n const signature = base64urlDecode(signatureB64);\n\n const valid = await crypto.subtle.verify(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n publicKey,\n signature.buffer as ArrayBuffer,\n signingInput,\n );\n\n if (!valid) return null;\n\n const payloadJson = new TextDecoder().decode(base64urlDecode(payloadB64));\n const payload = JSON.parse(payloadJson) as JWTClaims;\n\n // Check expiration\n if (payload.exp && Date.now() >= payload.exp * 1000) {\n return null;\n }\n\n return payload;\n } catch {\n return null;\n }\n}\n","import { importJWKPublicKey } from \"./jwt\";\n\ninterface JWKSResponse {\n keys: Array<{\n kty: string;\n crv: string;\n kid: string;\n use: string;\n alg: string;\n x: string;\n y: string;\n }>;\n}\n\nexport class JWKSClient {\n private cache: Map<string, CryptoKey> = new Map();\n private rawKeys: Map<string, JsonWebKey> = new Map();\n private lastFetchedAt = 0;\n private lastAttemptedAt = 0;\n private pendingInvalidation = false;\n private jwksUrl: string;\n private cacheTTL: number;\n private minRefetchInterval: number;\n\n constructor(jwksUrl: string, cacheTTL = 5 * 60 * 1000, minRefetchInterval = 10_000) {\n this.jwksUrl = jwksUrl;\n this.cacheTTL = cacheTTL;\n this.minRefetchInterval = minRefetchInterval;\n }\n\n async getKey(kid: string): Promise<CryptoKey> {\n // Return from cache if fresh AND not invalidated\n if (this.isCacheFresh() && !this.pendingInvalidation) {\n const cached = this.cache.get(kid);\n if (cached) return cached;\n }\n\n // Only refetch if minRefetchInterval has passed since last attempt\n if (Date.now() - this.lastAttemptedAt >= this.minRefetchInterval) {\n await this.fetchKeys();\n this.pendingInvalidation = false;\n } else {\n // Can't refetch yet — drop the invalidation, use stale cache\n this.pendingInvalidation = false;\n }\n\n const key = this.cache.get(kid);\n if (key) return key;\n\n throw new Error(`Unknown key ID: ${kid}`);\n }\n\n private isCacheFresh(): boolean {\n return Date.now() - this.lastFetchedAt < this.cacheTTL;\n }\n\n private async fetchKeys(): Promise<void> {\n this.lastAttemptedAt = Date.now();\n const res = await fetch(this.jwksUrl);\n if (!res.ok) {\n throw new Error(`Failed to fetch JWKS: ${res.status}`);\n }\n\n const data = (await res.json()) as JWKSResponse;\n\n this.cache.clear();\n this.rawKeys.clear();\n\n for (const jwk of data.keys) {\n if (jwk.kty !== \"EC\" || jwk.crv !== \"P-256\" || jwk.alg !== \"ES256\") continue;\n\n const publicKeyJwk: JsonWebKey = {\n kty: jwk.kty,\n crv: jwk.crv,\n x: jwk.x,\n y: jwk.y,\n };\n\n const cryptoKey = await importJWKPublicKey(publicKeyJwk);\n this.cache.set(jwk.kid, cryptoKey);\n this.rawKeys.set(jwk.kid, publicKeyJwk);\n }\n\n this.lastFetchedAt = Date.now();\n }\n\n /** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */\n invalidate(): void {\n this.pendingInvalidation = true;\n }\n}\n","export const COOKIE_AUTH_TOKEN = \"auth_token\";\nexport const COOKIE_REFRESH_TOKEN = \"refresh_token\";\nexport const COOKIE_AUTH_SESSION = \"auth_session\";\n\nexport const DEFAULT_SIGN_IN_URL = \"/login\";\nexport const DEFAULT_SIGN_UP_URL = \"/register\";\nexport const DEFAULT_AFTER_SIGN_IN_URL = \"/\";\nexport const DEFAULT_AFTER_SIGN_OUT_URL = \"/login\";\n\nexport const HEADER_PUBLISHABLE_KEY = \"X-Publishable-Key\";\nexport const HEADER_AUTHORIZATION = \"Authorization\";\nexport const HEADER_INAI_AUTH = \"x-inai-auth\";\n\nexport const DEFAULT_API_URL = \"https://apiauth.inai.dev\";\nexport const DEFAULT_JWKS_URL = \"https://apiauth.inai.dev/.well-known/jwks.json\";\n","export function normalizeApiUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nexport function buildEndpoint(apiUrl: string, path: string): string {\n return `${normalizeApiUrl(apiUrl)}${path}`;\n}\n","export function isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nexport function isStrongPassword(password: string): {\n valid: boolean;\n errors: string[];\n} {\n const errors: string[] = [];\n if (password.length < 8) errors.push(\"Password must be at least 8 characters\");\n if (!/[A-Z]/.test(password)) errors.push(\"Password must contain an uppercase letter\");\n if (!/[a-z]/.test(password)) errors.push(\"Password must contain a lowercase letter\");\n if (!/[0-9]/.test(password)) errors.push(\"Password must contain a number\");\n return { valid: errors.length === 0, errors };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,30 @@ declare class InAIAuthError extends Error {
|
|
|
10
10
|
declare function decodeJWTPayload(token: string): JWTClaims | null;
|
|
11
11
|
declare function isTokenExpired(token: string): boolean;
|
|
12
12
|
declare function getClaimsFromToken(token: string): JWTClaims | null;
|
|
13
|
+
declare function decodeJWTHeader(token: string): {
|
|
14
|
+
alg: string;
|
|
15
|
+
kid: string;
|
|
16
|
+
typ?: string;
|
|
17
|
+
} | null;
|
|
18
|
+
declare function importJWKPublicKey(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
19
|
+
declare function verifyES256(token: string, publicKey: CryptoKey): Promise<JWTClaims | null>;
|
|
20
|
+
|
|
21
|
+
declare class JWKSClient {
|
|
22
|
+
private cache;
|
|
23
|
+
private rawKeys;
|
|
24
|
+
private lastFetchedAt;
|
|
25
|
+
private lastAttemptedAt;
|
|
26
|
+
private pendingInvalidation;
|
|
27
|
+
private jwksUrl;
|
|
28
|
+
private cacheTTL;
|
|
29
|
+
private minRefetchInterval;
|
|
30
|
+
constructor(jwksUrl: string, cacheTTL?: number, minRefetchInterval?: number);
|
|
31
|
+
getKey(kid: string): Promise<CryptoKey>;
|
|
32
|
+
private isCacheFresh;
|
|
33
|
+
private fetchKeys;
|
|
34
|
+
/** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */
|
|
35
|
+
invalidate(): void;
|
|
36
|
+
}
|
|
13
37
|
|
|
14
38
|
declare const COOKIE_AUTH_TOKEN = "auth_token";
|
|
15
39
|
declare const COOKIE_REFRESH_TOKEN = "refresh_token";
|
|
@@ -22,6 +46,7 @@ declare const HEADER_PUBLISHABLE_KEY = "X-Publishable-Key";
|
|
|
22
46
|
declare const HEADER_AUTHORIZATION = "Authorization";
|
|
23
47
|
declare const HEADER_INAI_AUTH = "x-inai-auth";
|
|
24
48
|
declare const DEFAULT_API_URL = "https://apiauth.inai.dev";
|
|
49
|
+
declare const DEFAULT_JWKS_URL = "https://apiauth.inai.dev/.well-known/jwks.json";
|
|
25
50
|
|
|
26
51
|
declare function normalizeApiUrl(url: string): string;
|
|
27
52
|
declare function buildEndpoint(apiUrl: string, path: string): string;
|
|
@@ -32,4 +57,4 @@ declare function isStrongPassword(password: string): {
|
|
|
32
57
|
errors: string[];
|
|
33
58
|
};
|
|
34
59
|
|
|
35
|
-
export { COOKIE_AUTH_SESSION, COOKIE_AUTH_TOKEN, COOKIE_REFRESH_TOKEN, DEFAULT_AFTER_SIGN_IN_URL, DEFAULT_AFTER_SIGN_OUT_URL, DEFAULT_API_URL, DEFAULT_SIGN_IN_URL, DEFAULT_SIGN_UP_URL, HEADER_AUTHORIZATION, HEADER_INAI_AUTH, HEADER_PUBLISHABLE_KEY, InAIAuthError, buildEndpoint, decodeJWTPayload, getClaimsFromToken, isStrongPassword, isTokenExpired, isValidEmail, normalizeApiUrl };
|
|
60
|
+
export { COOKIE_AUTH_SESSION, COOKIE_AUTH_TOKEN, COOKIE_REFRESH_TOKEN, DEFAULT_AFTER_SIGN_IN_URL, DEFAULT_AFTER_SIGN_OUT_URL, DEFAULT_API_URL, DEFAULT_JWKS_URL, DEFAULT_SIGN_IN_URL, DEFAULT_SIGN_UP_URL, HEADER_AUTHORIZATION, HEADER_INAI_AUTH, HEADER_PUBLISHABLE_KEY, InAIAuthError, JWKSClient, buildEndpoint, decodeJWTHeader, decodeJWTPayload, getClaimsFromToken, importJWKPublicKey, isStrongPassword, isTokenExpired, isValidEmail, normalizeApiUrl, verifyES256 };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,30 @@ declare class InAIAuthError extends Error {
|
|
|
10
10
|
declare function decodeJWTPayload(token: string): JWTClaims | null;
|
|
11
11
|
declare function isTokenExpired(token: string): boolean;
|
|
12
12
|
declare function getClaimsFromToken(token: string): JWTClaims | null;
|
|
13
|
+
declare function decodeJWTHeader(token: string): {
|
|
14
|
+
alg: string;
|
|
15
|
+
kid: string;
|
|
16
|
+
typ?: string;
|
|
17
|
+
} | null;
|
|
18
|
+
declare function importJWKPublicKey(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
19
|
+
declare function verifyES256(token: string, publicKey: CryptoKey): Promise<JWTClaims | null>;
|
|
20
|
+
|
|
21
|
+
declare class JWKSClient {
|
|
22
|
+
private cache;
|
|
23
|
+
private rawKeys;
|
|
24
|
+
private lastFetchedAt;
|
|
25
|
+
private lastAttemptedAt;
|
|
26
|
+
private pendingInvalidation;
|
|
27
|
+
private jwksUrl;
|
|
28
|
+
private cacheTTL;
|
|
29
|
+
private minRefetchInterval;
|
|
30
|
+
constructor(jwksUrl: string, cacheTTL?: number, minRefetchInterval?: number);
|
|
31
|
+
getKey(kid: string): Promise<CryptoKey>;
|
|
32
|
+
private isCacheFresh;
|
|
33
|
+
private fetchKeys;
|
|
34
|
+
/** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */
|
|
35
|
+
invalidate(): void;
|
|
36
|
+
}
|
|
13
37
|
|
|
14
38
|
declare const COOKIE_AUTH_TOKEN = "auth_token";
|
|
15
39
|
declare const COOKIE_REFRESH_TOKEN = "refresh_token";
|
|
@@ -22,6 +46,7 @@ declare const HEADER_PUBLISHABLE_KEY = "X-Publishable-Key";
|
|
|
22
46
|
declare const HEADER_AUTHORIZATION = "Authorization";
|
|
23
47
|
declare const HEADER_INAI_AUTH = "x-inai-auth";
|
|
24
48
|
declare const DEFAULT_API_URL = "https://apiauth.inai.dev";
|
|
49
|
+
declare const DEFAULT_JWKS_URL = "https://apiauth.inai.dev/.well-known/jwks.json";
|
|
25
50
|
|
|
26
51
|
declare function normalizeApiUrl(url: string): string;
|
|
27
52
|
declare function buildEndpoint(apiUrl: string, path: string): string;
|
|
@@ -32,4 +57,4 @@ declare function isStrongPassword(password: string): {
|
|
|
32
57
|
errors: string[];
|
|
33
58
|
};
|
|
34
59
|
|
|
35
|
-
export { COOKIE_AUTH_SESSION, COOKIE_AUTH_TOKEN, COOKIE_REFRESH_TOKEN, DEFAULT_AFTER_SIGN_IN_URL, DEFAULT_AFTER_SIGN_OUT_URL, DEFAULT_API_URL, DEFAULT_SIGN_IN_URL, DEFAULT_SIGN_UP_URL, HEADER_AUTHORIZATION, HEADER_INAI_AUTH, HEADER_PUBLISHABLE_KEY, InAIAuthError, buildEndpoint, decodeJWTPayload, getClaimsFromToken, isStrongPassword, isTokenExpired, isValidEmail, normalizeApiUrl };
|
|
60
|
+
export { COOKIE_AUTH_SESSION, COOKIE_AUTH_TOKEN, COOKIE_REFRESH_TOKEN, DEFAULT_AFTER_SIGN_IN_URL, DEFAULT_AFTER_SIGN_OUT_URL, DEFAULT_API_URL, DEFAULT_JWKS_URL, DEFAULT_SIGN_IN_URL, DEFAULT_SIGN_UP_URL, HEADER_AUTHORIZATION, HEADER_INAI_AUTH, HEADER_PUBLISHABLE_KEY, InAIAuthError, JWKSClient, buildEndpoint, decodeJWTHeader, decodeJWTPayload, getClaimsFromToken, importJWKPublicKey, isStrongPassword, isTokenExpired, isValidEmail, normalizeApiUrl, verifyES256 };
|
package/dist/index.js
CHANGED
|
@@ -18,13 +18,22 @@ var InAIAuthError = class extends Error {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
// src/jwt.ts
|
|
21
|
+
function base64urlDecode(str) {
|
|
22
|
+
let padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
23
|
+
const remainder = padded.length % 4;
|
|
24
|
+
if (remainder === 2) padded += "==";
|
|
25
|
+
else if (remainder === 3) padded += "=";
|
|
26
|
+
const binString = atob(padded);
|
|
27
|
+
return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));
|
|
28
|
+
}
|
|
29
|
+
function base64urlToString(str) {
|
|
30
|
+
return new TextDecoder().decode(base64urlDecode(str));
|
|
31
|
+
}
|
|
21
32
|
function decodeJWTPayload(token) {
|
|
22
33
|
try {
|
|
23
34
|
const parts = token.split(".");
|
|
24
35
|
if (parts.length !== 3) return null;
|
|
25
|
-
|
|
26
|
-
const decoded = atob(payload);
|
|
27
|
-
return JSON.parse(decoded);
|
|
36
|
+
return JSON.parse(base64urlToString(parts[1]));
|
|
28
37
|
} catch {
|
|
29
38
|
return null;
|
|
30
39
|
}
|
|
@@ -37,6 +46,111 @@ function isTokenExpired(token) {
|
|
|
37
46
|
function getClaimsFromToken(token) {
|
|
38
47
|
return decodeJWTPayload(token);
|
|
39
48
|
}
|
|
49
|
+
function decodeJWTHeader(token) {
|
|
50
|
+
try {
|
|
51
|
+
const parts = token.split(".");
|
|
52
|
+
if (parts.length !== 3) return null;
|
|
53
|
+
return JSON.parse(base64urlToString(parts[0]));
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function importJWKPublicKey(jwk) {
|
|
59
|
+
return crypto.subtle.importKey(
|
|
60
|
+
"jwk",
|
|
61
|
+
jwk,
|
|
62
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
63
|
+
false,
|
|
64
|
+
["verify"]
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
async function verifyES256(token, publicKey) {
|
|
68
|
+
try {
|
|
69
|
+
const parts = token.split(".");
|
|
70
|
+
if (parts.length !== 3) return null;
|
|
71
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
const signingInput = encoder.encode(`${headerB64}.${payloadB64}`);
|
|
74
|
+
const signature = base64urlDecode(signatureB64);
|
|
75
|
+
const valid = await crypto.subtle.verify(
|
|
76
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
77
|
+
publicKey,
|
|
78
|
+
signature.buffer,
|
|
79
|
+
signingInput
|
|
80
|
+
);
|
|
81
|
+
if (!valid) return null;
|
|
82
|
+
const payloadJson = new TextDecoder().decode(base64urlDecode(payloadB64));
|
|
83
|
+
const payload = JSON.parse(payloadJson);
|
|
84
|
+
if (payload.exp && Date.now() >= payload.exp * 1e3) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return payload;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/jwks.ts
|
|
94
|
+
var JWKSClient = class {
|
|
95
|
+
cache = /* @__PURE__ */ new Map();
|
|
96
|
+
rawKeys = /* @__PURE__ */ new Map();
|
|
97
|
+
lastFetchedAt = 0;
|
|
98
|
+
lastAttemptedAt = 0;
|
|
99
|
+
pendingInvalidation = false;
|
|
100
|
+
jwksUrl;
|
|
101
|
+
cacheTTL;
|
|
102
|
+
minRefetchInterval;
|
|
103
|
+
constructor(jwksUrl, cacheTTL = 5 * 60 * 1e3, minRefetchInterval = 1e4) {
|
|
104
|
+
this.jwksUrl = jwksUrl;
|
|
105
|
+
this.cacheTTL = cacheTTL;
|
|
106
|
+
this.minRefetchInterval = minRefetchInterval;
|
|
107
|
+
}
|
|
108
|
+
async getKey(kid) {
|
|
109
|
+
if (this.isCacheFresh() && !this.pendingInvalidation) {
|
|
110
|
+
const cached = this.cache.get(kid);
|
|
111
|
+
if (cached) return cached;
|
|
112
|
+
}
|
|
113
|
+
if (Date.now() - this.lastAttemptedAt >= this.minRefetchInterval) {
|
|
114
|
+
await this.fetchKeys();
|
|
115
|
+
this.pendingInvalidation = false;
|
|
116
|
+
} else {
|
|
117
|
+
this.pendingInvalidation = false;
|
|
118
|
+
}
|
|
119
|
+
const key = this.cache.get(kid);
|
|
120
|
+
if (key) return key;
|
|
121
|
+
throw new Error(`Unknown key ID: ${kid}`);
|
|
122
|
+
}
|
|
123
|
+
isCacheFresh() {
|
|
124
|
+
return Date.now() - this.lastFetchedAt < this.cacheTTL;
|
|
125
|
+
}
|
|
126
|
+
async fetchKeys() {
|
|
127
|
+
this.lastAttemptedAt = Date.now();
|
|
128
|
+
const res = await fetch(this.jwksUrl);
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
throw new Error(`Failed to fetch JWKS: ${res.status}`);
|
|
131
|
+
}
|
|
132
|
+
const data = await res.json();
|
|
133
|
+
this.cache.clear();
|
|
134
|
+
this.rawKeys.clear();
|
|
135
|
+
for (const jwk of data.keys) {
|
|
136
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || jwk.alg !== "ES256") continue;
|
|
137
|
+
const publicKeyJwk = {
|
|
138
|
+
kty: jwk.kty,
|
|
139
|
+
crv: jwk.crv,
|
|
140
|
+
x: jwk.x,
|
|
141
|
+
y: jwk.y
|
|
142
|
+
};
|
|
143
|
+
const cryptoKey = await importJWKPublicKey(publicKeyJwk);
|
|
144
|
+
this.cache.set(jwk.kid, cryptoKey);
|
|
145
|
+
this.rawKeys.set(jwk.kid, publicKeyJwk);
|
|
146
|
+
}
|
|
147
|
+
this.lastFetchedAt = Date.now();
|
|
148
|
+
}
|
|
149
|
+
/** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */
|
|
150
|
+
invalidate() {
|
|
151
|
+
this.pendingInvalidation = true;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
40
154
|
|
|
41
155
|
// src/constants.ts
|
|
42
156
|
var COOKIE_AUTH_TOKEN = "auth_token";
|
|
@@ -50,6 +164,7 @@ var HEADER_PUBLISHABLE_KEY = "X-Publishable-Key";
|
|
|
50
164
|
var HEADER_AUTHORIZATION = "Authorization";
|
|
51
165
|
var HEADER_INAI_AUTH = "x-inai-auth";
|
|
52
166
|
var DEFAULT_API_URL = "https://apiauth.inai.dev";
|
|
167
|
+
var DEFAULT_JWKS_URL = "https://apiauth.inai.dev/.well-known/jwks.json";
|
|
53
168
|
|
|
54
169
|
// src/url.ts
|
|
55
170
|
function normalizeApiUrl(url) {
|
|
@@ -78,18 +193,23 @@ export {
|
|
|
78
193
|
DEFAULT_AFTER_SIGN_IN_URL,
|
|
79
194
|
DEFAULT_AFTER_SIGN_OUT_URL,
|
|
80
195
|
DEFAULT_API_URL,
|
|
196
|
+
DEFAULT_JWKS_URL,
|
|
81
197
|
DEFAULT_SIGN_IN_URL,
|
|
82
198
|
DEFAULT_SIGN_UP_URL,
|
|
83
199
|
HEADER_AUTHORIZATION,
|
|
84
200
|
HEADER_INAI_AUTH,
|
|
85
201
|
HEADER_PUBLISHABLE_KEY,
|
|
86
202
|
InAIAuthError,
|
|
203
|
+
JWKSClient,
|
|
87
204
|
buildEndpoint,
|
|
205
|
+
decodeJWTHeader,
|
|
88
206
|
decodeJWTPayload,
|
|
89
207
|
getClaimsFromToken,
|
|
208
|
+
importJWKPublicKey,
|
|
90
209
|
isStrongPassword,
|
|
91
210
|
isTokenExpired,
|
|
92
211
|
isValidEmail,
|
|
93
|
-
normalizeApiUrl
|
|
212
|
+
normalizeApiUrl,
|
|
213
|
+
verifyES256
|
|
94
214
|
};
|
|
95
215
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/jwt.ts","../src/constants.ts","../src/url.ts","../src/validators.ts"],"sourcesContent":["import type { InAIAuthErrorBody } from \"@inai-dev/types\";\n\nexport class InAIAuthError extends Error {\n status: number;\n body: InAIAuthErrorBody;\n code: string;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"InAIAuthError\";\n this.status = status;\n const parsed = body as Record<string, unknown> | null;\n this.body = {\n code: (parsed?.code as string) ?? \"UNKNOWN_ERROR\",\n detail: (parsed?.detail as string) ?? message,\n field: (parsed?.field as string) ?? undefined,\n };\n this.code = this.body.code;\n }\n}\n","import type { JWTClaims } from \"@inai-dev/types\";\n\nexport function decodeJWTPayload(token: string): JWTClaims | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const decoded = atob(payload);\n return JSON.parse(decoded);\n } catch {\n return null;\n }\n}\n\nexport function isTokenExpired(token: string): boolean {\n const claims = decodeJWTPayload(token);\n if (!claims?.exp) return true;\n return Date.now() >= claims.exp * 1000;\n}\n\nexport function getClaimsFromToken(token: string): JWTClaims | null {\n return decodeJWTPayload(token);\n}\n","export const COOKIE_AUTH_TOKEN = \"auth_token\";\nexport const COOKIE_REFRESH_TOKEN = \"refresh_token\";\nexport const COOKIE_AUTH_SESSION = \"auth_session\";\n\nexport const DEFAULT_SIGN_IN_URL = \"/login\";\nexport const DEFAULT_SIGN_UP_URL = \"/register\";\nexport const DEFAULT_AFTER_SIGN_IN_URL = \"/\";\nexport const DEFAULT_AFTER_SIGN_OUT_URL = \"/login\";\n\nexport const HEADER_PUBLISHABLE_KEY = \"X-Publishable-Key\";\nexport const HEADER_AUTHORIZATION = \"Authorization\";\nexport const HEADER_INAI_AUTH = \"x-inai-auth\";\n\nexport const DEFAULT_API_URL = \"https://apiauth.inai.dev\";\n","export function normalizeApiUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nexport function buildEndpoint(apiUrl: string, path: string): string {\n return `${normalizeApiUrl(apiUrl)}${path}`;\n}\n","export function isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nexport function isStrongPassword(password: string): {\n valid: boolean;\n errors: string[];\n} {\n const errors: string[] = [];\n if (password.length < 8) errors.push(\"Password must be at least 8 characters\");\n if (!/[A-Z]/.test(password)) errors.push(\"Password must contain an uppercase letter\");\n if (!/[a-z]/.test(password)) errors.push(\"Password must contain a lowercase letter\");\n if (!/[0-9]/.test(password)) errors.push(\"Password must contain a number\");\n return { valid: errors.length === 0, errors };\n}\n"],"mappings":";AAEO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,UAAM,SAAS;AACf,SAAK,OAAO;AAAA,MACV,MAAO,QAAQ,QAAmB;AAAA,MAClC,QAAS,QAAQ,UAAqB;AAAA,MACtC,OAAQ,QAAQ,SAAoB;AAAA,IACtC;AACA,SAAK,OAAO,KAAK,KAAK;AAAA,EACxB;AACF;;;ACjBO,SAAS,iBAAiB,OAAiC;AAChE,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,UAAU,MAAM,CAAC,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,UAAM,UAAU,KAAK,OAAO;AAC5B,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,OAAwB;AACrD,QAAM,SAAS,iBAAiB,KAAK;AACrC,MAAI,CAAC,QAAQ,IAAK,QAAO;AACzB,SAAO,KAAK,IAAI,KAAK,OAAO,MAAM;AACpC;AAEO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,iBAAiB,KAAK;AAC/B;;;ACtBO,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAE5B,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,4BAA4B;AAClC,IAAM,6BAA6B;AAEnC,IAAM,yBAAyB;AAC/B,IAAM,uBAAuB;AAC7B,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;;;ACbxB,SAAS,gBAAgB,KAAqB;AACnD,SAAO,IAAI,QAAQ,OAAO,EAAE;AAC9B;AAEO,SAAS,cAAc,QAAgB,MAAsB;AAClE,SAAO,GAAG,gBAAgB,MAAM,CAAC,GAAG,IAAI;AAC1C;;;ACNO,SAAS,aAAa,OAAwB;AACnD,SAAO,6BAA6B,KAAK,KAAK;AAChD;AAEO,SAAS,iBAAiB,UAG/B;AACA,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS,SAAS,EAAG,QAAO,KAAK,wCAAwC;AAC7E,MAAI,CAAC,QAAQ,KAAK,QAAQ,EAAG,QAAO,KAAK,2CAA2C;AACpF,MAAI,CAAC,QAAQ,KAAK,QAAQ,EAAG,QAAO,KAAK,0CAA0C;AACnF,MAAI,CAAC,QAAQ,KAAK,QAAQ,EAAG,QAAO,KAAK,gCAAgC;AACzE,SAAO,EAAE,OAAO,OAAO,WAAW,GAAG,OAAO;AAC9C;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/jwt.ts","../src/jwks.ts","../src/constants.ts","../src/url.ts","../src/validators.ts"],"sourcesContent":["import type { InAIAuthErrorBody } from \"@inai-dev/types\";\n\nexport class InAIAuthError extends Error {\n status: number;\n body: InAIAuthErrorBody;\n code: string;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"InAIAuthError\";\n this.status = status;\n const parsed = body as Record<string, unknown> | null;\n this.body = {\n code: (parsed?.code as string) ?? \"UNKNOWN_ERROR\",\n detail: (parsed?.detail as string) ?? message,\n field: (parsed?.field as string) ?? undefined,\n };\n this.code = this.body.code;\n }\n}\n","import type { JWTClaims } from \"@inai-dev/types\";\n\nfunction base64urlDecode(str: string): Uint8Array {\n let padded = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const remainder = padded.length % 4;\n if (remainder === 2) padded += \"==\";\n else if (remainder === 3) padded += \"=\";\n const binString = atob(padded);\n return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));\n}\n\nfunction base64urlToString(str: string): string {\n return new TextDecoder().decode(base64urlDecode(str));\n}\n\nexport function decodeJWTPayload(token: string): JWTClaims | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n return JSON.parse(base64urlToString(parts[1]));\n } catch {\n return null;\n }\n}\n\nexport function isTokenExpired(token: string): boolean {\n const claims = decodeJWTPayload(token);\n if (!claims?.exp) return true;\n return Date.now() >= claims.exp * 1000;\n}\n\nexport function getClaimsFromToken(token: string): JWTClaims | null {\n return decodeJWTPayload(token);\n}\n\nexport function decodeJWTHeader(token: string): { alg: string; kid: string; typ?: string } | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n return JSON.parse(base64urlToString(parts[0]));\n } catch {\n return null;\n }\n}\n\nexport async function importJWKPublicKey(jwk: JsonWebKey): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"verify\"],\n );\n}\n\nexport async function verifyES256(token: string, publicKey: CryptoKey): Promise<JWTClaims | null> {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n\n const [headerB64, payloadB64, signatureB64] = parts;\n\n const encoder = new TextEncoder();\n const signingInput = encoder.encode(`${headerB64}.${payloadB64}`);\n const signature = base64urlDecode(signatureB64);\n\n const valid = await crypto.subtle.verify(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n publicKey,\n signature.buffer as ArrayBuffer,\n signingInput,\n );\n\n if (!valid) return null;\n\n const payloadJson = new TextDecoder().decode(base64urlDecode(payloadB64));\n const payload = JSON.parse(payloadJson) as JWTClaims;\n\n // Check expiration\n if (payload.exp && Date.now() >= payload.exp * 1000) {\n return null;\n }\n\n return payload;\n } catch {\n return null;\n }\n}\n","import { importJWKPublicKey } from \"./jwt\";\n\ninterface JWKSResponse {\n keys: Array<{\n kty: string;\n crv: string;\n kid: string;\n use: string;\n alg: string;\n x: string;\n y: string;\n }>;\n}\n\nexport class JWKSClient {\n private cache: Map<string, CryptoKey> = new Map();\n private rawKeys: Map<string, JsonWebKey> = new Map();\n private lastFetchedAt = 0;\n private lastAttemptedAt = 0;\n private pendingInvalidation = false;\n private jwksUrl: string;\n private cacheTTL: number;\n private minRefetchInterval: number;\n\n constructor(jwksUrl: string, cacheTTL = 5 * 60 * 1000, minRefetchInterval = 10_000) {\n this.jwksUrl = jwksUrl;\n this.cacheTTL = cacheTTL;\n this.minRefetchInterval = minRefetchInterval;\n }\n\n async getKey(kid: string): Promise<CryptoKey> {\n // Return from cache if fresh AND not invalidated\n if (this.isCacheFresh() && !this.pendingInvalidation) {\n const cached = this.cache.get(kid);\n if (cached) return cached;\n }\n\n // Only refetch if minRefetchInterval has passed since last attempt\n if (Date.now() - this.lastAttemptedAt >= this.minRefetchInterval) {\n await this.fetchKeys();\n this.pendingInvalidation = false;\n } else {\n // Can't refetch yet — drop the invalidation, use stale cache\n this.pendingInvalidation = false;\n }\n\n const key = this.cache.get(kid);\n if (key) return key;\n\n throw new Error(`Unknown key ID: ${kid}`);\n }\n\n private isCacheFresh(): boolean {\n return Date.now() - this.lastFetchedAt < this.cacheTTL;\n }\n\n private async fetchKeys(): Promise<void> {\n this.lastAttemptedAt = Date.now();\n const res = await fetch(this.jwksUrl);\n if (!res.ok) {\n throw new Error(`Failed to fetch JWKS: ${res.status}`);\n }\n\n const data = (await res.json()) as JWKSResponse;\n\n this.cache.clear();\n this.rawKeys.clear();\n\n for (const jwk of data.keys) {\n if (jwk.kty !== \"EC\" || jwk.crv !== \"P-256\" || jwk.alg !== \"ES256\") continue;\n\n const publicKeyJwk: JsonWebKey = {\n kty: jwk.kty,\n crv: jwk.crv,\n x: jwk.x,\n y: jwk.y,\n };\n\n const cryptoKey = await importJWKPublicKey(publicKeyJwk);\n this.cache.set(jwk.kid, cryptoKey);\n this.rawKeys.set(jwk.kid, publicKeyJwk);\n }\n\n this.lastFetchedAt = Date.now();\n }\n\n /** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */\n invalidate(): void {\n this.pendingInvalidation = true;\n }\n}\n","export const COOKIE_AUTH_TOKEN = \"auth_token\";\nexport const COOKIE_REFRESH_TOKEN = \"refresh_token\";\nexport const COOKIE_AUTH_SESSION = \"auth_session\";\n\nexport const DEFAULT_SIGN_IN_URL = \"/login\";\nexport const DEFAULT_SIGN_UP_URL = \"/register\";\nexport const DEFAULT_AFTER_SIGN_IN_URL = \"/\";\nexport const DEFAULT_AFTER_SIGN_OUT_URL = \"/login\";\n\nexport const HEADER_PUBLISHABLE_KEY = \"X-Publishable-Key\";\nexport const HEADER_AUTHORIZATION = \"Authorization\";\nexport const HEADER_INAI_AUTH = \"x-inai-auth\";\n\nexport const DEFAULT_API_URL = \"https://apiauth.inai.dev\";\nexport const DEFAULT_JWKS_URL = \"https://apiauth.inai.dev/.well-known/jwks.json\";\n","export function normalizeApiUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nexport function buildEndpoint(apiUrl: string, path: string): string {\n return `${normalizeApiUrl(apiUrl)}${path}`;\n}\n","export function isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nexport function isStrongPassword(password: string): {\n valid: boolean;\n errors: string[];\n} {\n const errors: string[] = [];\n if (password.length < 8) errors.push(\"Password must be at least 8 characters\");\n if (!/[A-Z]/.test(password)) errors.push(\"Password must contain an uppercase letter\");\n if (!/[a-z]/.test(password)) errors.push(\"Password must contain a lowercase letter\");\n if (!/[0-9]/.test(password)) errors.push(\"Password must contain a number\");\n return { valid: errors.length === 0, errors };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inai-dev/shared",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Shared utilities for the InAI Auth SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@inai-dev/types": "^1.
|
|
27
|
+
"@inai-dev/types": "^1.3.0"
|
|
28
28
|
},
|
|
29
29
|
"sideEffects": false,
|
|
30
30
|
"publishConfig": {
|