@inai-dev/shared 1.2.0 → 1.4.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 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,11 @@ 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 |
89
107
 
90
108
  ## Questions & Support
91
109
 
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,27 @@ 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 === 1) throw new Error("Invalid base64url string");
74
+ if (remainder === 2) padded += "==";
75
+ else if (remainder === 3) padded += "=";
76
+ try {
77
+ const binString = atob(padded);
78
+ return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));
79
+ } catch {
80
+ throw new Error("Invalid base64url encoding");
81
+ }
82
+ }
83
+ function base64urlToString(str) {
84
+ return new TextDecoder().decode(base64urlDecode(str));
85
+ }
65
86
  function decodeJWTPayload(token) {
66
87
  try {
67
88
  const parts = token.split(".");
68
89
  if (parts.length !== 3) return null;
69
- const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
70
- const decoded = atob(payload);
71
- return JSON.parse(decoded);
90
+ return JSON.parse(base64urlToString(parts[1]));
72
91
  } catch {
73
92
  return null;
74
93
  }
@@ -81,6 +100,114 @@ function isTokenExpired(token) {
81
100
  function getClaimsFromToken(token) {
82
101
  return decodeJWTPayload(token);
83
102
  }
103
+ function decodeJWTHeader(token) {
104
+ try {
105
+ const parts = token.split(".");
106
+ if (parts.length !== 3) return null;
107
+ return JSON.parse(base64urlToString(parts[0]));
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ async function importJWKPublicKey(jwk) {
113
+ return crypto.subtle.importKey(
114
+ "jwk",
115
+ jwk,
116
+ { name: "ECDSA", namedCurve: "P-256" },
117
+ false,
118
+ ["verify"]
119
+ );
120
+ }
121
+ async function verifyES256(token, publicKey) {
122
+ try {
123
+ const parts = token.split(".");
124
+ if (parts.length !== 3) return null;
125
+ const [headerB64, payloadB64, signatureB64] = parts;
126
+ const encoder = new TextEncoder();
127
+ const signingInput = encoder.encode(`${headerB64}.${payloadB64}`);
128
+ const signature = base64urlDecode(signatureB64);
129
+ const valid = await crypto.subtle.verify(
130
+ { name: "ECDSA", hash: "SHA-256" },
131
+ publicKey,
132
+ signature.buffer,
133
+ signingInput
134
+ );
135
+ if (!valid) return null;
136
+ const payloadJson = new TextDecoder().decode(base64urlDecode(payloadB64));
137
+ const payload = JSON.parse(payloadJson);
138
+ if (!payload.exp || typeof payload.exp !== "number") {
139
+ return null;
140
+ }
141
+ if (Date.now() >= payload.exp * 1e3) {
142
+ return null;
143
+ }
144
+ return payload;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ // src/jwks.ts
151
+ var JWKSClient = class {
152
+ cache = /* @__PURE__ */ new Map();
153
+ rawKeys = /* @__PURE__ */ new Map();
154
+ lastFetchedAt = 0;
155
+ lastAttemptedAt = 0;
156
+ pendingInvalidation = false;
157
+ jwksUrl;
158
+ cacheTTL;
159
+ minRefetchInterval;
160
+ constructor(jwksUrl, cacheTTL = 5 * 60 * 1e3, minRefetchInterval = 1e4) {
161
+ this.jwksUrl = jwksUrl;
162
+ this.cacheTTL = cacheTTL;
163
+ this.minRefetchInterval = minRefetchInterval;
164
+ }
165
+ async getKey(kid) {
166
+ if (this.isCacheFresh() && !this.pendingInvalidation) {
167
+ const cached = this.cache.get(kid);
168
+ if (cached) return cached;
169
+ }
170
+ if (Date.now() - this.lastAttemptedAt >= this.minRefetchInterval) {
171
+ await this.fetchKeys();
172
+ this.pendingInvalidation = false;
173
+ } else {
174
+ this.pendingInvalidation = false;
175
+ }
176
+ const key = this.cache.get(kid);
177
+ if (key) return key;
178
+ throw new Error(`Unknown key ID: ${kid}`);
179
+ }
180
+ isCacheFresh() {
181
+ return Date.now() - this.lastFetchedAt < this.cacheTTL;
182
+ }
183
+ async fetchKeys() {
184
+ this.lastAttemptedAt = Date.now();
185
+ const res = await fetch(this.jwksUrl);
186
+ if (!res.ok) {
187
+ throw new Error(`Failed to fetch JWKS: ${res.status}`);
188
+ }
189
+ const data = await res.json();
190
+ this.cache.clear();
191
+ this.rawKeys.clear();
192
+ for (const jwk of data.keys) {
193
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || jwk.alg !== "ES256") continue;
194
+ const publicKeyJwk = {
195
+ kty: jwk.kty,
196
+ crv: jwk.crv,
197
+ x: jwk.x,
198
+ y: jwk.y
199
+ };
200
+ const cryptoKey = await importJWKPublicKey(publicKeyJwk);
201
+ this.cache.set(jwk.kid, cryptoKey);
202
+ this.rawKeys.set(jwk.kid, publicKeyJwk);
203
+ }
204
+ this.lastFetchedAt = Date.now();
205
+ }
206
+ /** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */
207
+ invalidate() {
208
+ this.pendingInvalidation = true;
209
+ }
210
+ };
84
211
 
85
212
  // src/constants.ts
86
213
  var COOKIE_AUTH_TOKEN = "auth_token";
@@ -94,6 +221,7 @@ var HEADER_PUBLISHABLE_KEY = "X-Publishable-Key";
94
221
  var HEADER_AUTHORIZATION = "Authorization";
95
222
  var HEADER_INAI_AUTH = "x-inai-auth";
96
223
  var DEFAULT_API_URL = "https://apiauth.inai.dev";
224
+ var DEFAULT_JWKS_URL = "https://apiauth.inai.dev/.well-known/jwks.json";
97
225
 
98
226
  // src/url.ts
99
227
  function normalizeApiUrl(url) {
@@ -123,18 +251,23 @@ function isStrongPassword(password) {
123
251
  DEFAULT_AFTER_SIGN_IN_URL,
124
252
  DEFAULT_AFTER_SIGN_OUT_URL,
125
253
  DEFAULT_API_URL,
254
+ DEFAULT_JWKS_URL,
126
255
  DEFAULT_SIGN_IN_URL,
127
256
  DEFAULT_SIGN_UP_URL,
128
257
  HEADER_AUTHORIZATION,
129
258
  HEADER_INAI_AUTH,
130
259
  HEADER_PUBLISHABLE_KEY,
131
260
  InAIAuthError,
261
+ JWKSClient,
132
262
  buildEndpoint,
263
+ decodeJWTHeader,
133
264
  decodeJWTPayload,
134
265
  getClaimsFromToken,
266
+ importJWKPublicKey,
135
267
  isStrongPassword,
136
268
  isTokenExpired,
137
269
  isValidEmail,
138
- normalizeApiUrl
270
+ normalizeApiUrl,
271
+ verifyES256
139
272
  });
140
273
  //# sourceMappingURL=index.cjs.map
@@ -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 === 1) throw new Error(\"Invalid base64url string\");\n if (remainder === 2) padded += \"==\";\n else if (remainder === 3) padded += \"=\";\n try {\n const binString = atob(padded);\n return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));\n } catch {\n throw new Error(\"Invalid base64url encoding\");\n }\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 (required)\n if (!payload.exp || typeof payload.exp !== \"number\") {\n return null;\n }\n if (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,27 @@ 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 === 1) throw new Error("Invalid base64url string");
25
+ if (remainder === 2) padded += "==";
26
+ else if (remainder === 3) padded += "=";
27
+ try {
28
+ const binString = atob(padded);
29
+ return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));
30
+ } catch {
31
+ throw new Error("Invalid base64url encoding");
32
+ }
33
+ }
34
+ function base64urlToString(str) {
35
+ return new TextDecoder().decode(base64urlDecode(str));
36
+ }
21
37
  function decodeJWTPayload(token) {
22
38
  try {
23
39
  const parts = token.split(".");
24
40
  if (parts.length !== 3) return null;
25
- const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
26
- const decoded = atob(payload);
27
- return JSON.parse(decoded);
41
+ return JSON.parse(base64urlToString(parts[1]));
28
42
  } catch {
29
43
  return null;
30
44
  }
@@ -37,6 +51,114 @@ function isTokenExpired(token) {
37
51
  function getClaimsFromToken(token) {
38
52
  return decodeJWTPayload(token);
39
53
  }
54
+ function decodeJWTHeader(token) {
55
+ try {
56
+ const parts = token.split(".");
57
+ if (parts.length !== 3) return null;
58
+ return JSON.parse(base64urlToString(parts[0]));
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ async function importJWKPublicKey(jwk) {
64
+ return crypto.subtle.importKey(
65
+ "jwk",
66
+ jwk,
67
+ { name: "ECDSA", namedCurve: "P-256" },
68
+ false,
69
+ ["verify"]
70
+ );
71
+ }
72
+ async function verifyES256(token, publicKey) {
73
+ try {
74
+ const parts = token.split(".");
75
+ if (parts.length !== 3) return null;
76
+ const [headerB64, payloadB64, signatureB64] = parts;
77
+ const encoder = new TextEncoder();
78
+ const signingInput = encoder.encode(`${headerB64}.${payloadB64}`);
79
+ const signature = base64urlDecode(signatureB64);
80
+ const valid = await crypto.subtle.verify(
81
+ { name: "ECDSA", hash: "SHA-256" },
82
+ publicKey,
83
+ signature.buffer,
84
+ signingInput
85
+ );
86
+ if (!valid) return null;
87
+ const payloadJson = new TextDecoder().decode(base64urlDecode(payloadB64));
88
+ const payload = JSON.parse(payloadJson);
89
+ if (!payload.exp || typeof payload.exp !== "number") {
90
+ return null;
91
+ }
92
+ if (Date.now() >= payload.exp * 1e3) {
93
+ return null;
94
+ }
95
+ return payload;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ // src/jwks.ts
102
+ var JWKSClient = class {
103
+ cache = /* @__PURE__ */ new Map();
104
+ rawKeys = /* @__PURE__ */ new Map();
105
+ lastFetchedAt = 0;
106
+ lastAttemptedAt = 0;
107
+ pendingInvalidation = false;
108
+ jwksUrl;
109
+ cacheTTL;
110
+ minRefetchInterval;
111
+ constructor(jwksUrl, cacheTTL = 5 * 60 * 1e3, minRefetchInterval = 1e4) {
112
+ this.jwksUrl = jwksUrl;
113
+ this.cacheTTL = cacheTTL;
114
+ this.minRefetchInterval = minRefetchInterval;
115
+ }
116
+ async getKey(kid) {
117
+ if (this.isCacheFresh() && !this.pendingInvalidation) {
118
+ const cached = this.cache.get(kid);
119
+ if (cached) return cached;
120
+ }
121
+ if (Date.now() - this.lastAttemptedAt >= this.minRefetchInterval) {
122
+ await this.fetchKeys();
123
+ this.pendingInvalidation = false;
124
+ } else {
125
+ this.pendingInvalidation = false;
126
+ }
127
+ const key = this.cache.get(kid);
128
+ if (key) return key;
129
+ throw new Error(`Unknown key ID: ${kid}`);
130
+ }
131
+ isCacheFresh() {
132
+ return Date.now() - this.lastFetchedAt < this.cacheTTL;
133
+ }
134
+ async fetchKeys() {
135
+ this.lastAttemptedAt = Date.now();
136
+ const res = await fetch(this.jwksUrl);
137
+ if (!res.ok) {
138
+ throw new Error(`Failed to fetch JWKS: ${res.status}`);
139
+ }
140
+ const data = await res.json();
141
+ this.cache.clear();
142
+ this.rawKeys.clear();
143
+ for (const jwk of data.keys) {
144
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || jwk.alg !== "ES256") continue;
145
+ const publicKeyJwk = {
146
+ kty: jwk.kty,
147
+ crv: jwk.crv,
148
+ x: jwk.x,
149
+ y: jwk.y
150
+ };
151
+ const cryptoKey = await importJWKPublicKey(publicKeyJwk);
152
+ this.cache.set(jwk.kid, cryptoKey);
153
+ this.rawKeys.set(jwk.kid, publicKeyJwk);
154
+ }
155
+ this.lastFetchedAt = Date.now();
156
+ }
157
+ /** Mark cache as stale. Actual refetch respects minRefetchInterval to prevent DoS. */
158
+ invalidate() {
159
+ this.pendingInvalidation = true;
160
+ }
161
+ };
40
162
 
41
163
  // src/constants.ts
42
164
  var COOKIE_AUTH_TOKEN = "auth_token";
@@ -50,6 +172,7 @@ var HEADER_PUBLISHABLE_KEY = "X-Publishable-Key";
50
172
  var HEADER_AUTHORIZATION = "Authorization";
51
173
  var HEADER_INAI_AUTH = "x-inai-auth";
52
174
  var DEFAULT_API_URL = "https://apiauth.inai.dev";
175
+ var DEFAULT_JWKS_URL = "https://apiauth.inai.dev/.well-known/jwks.json";
53
176
 
54
177
  // src/url.ts
55
178
  function normalizeApiUrl(url) {
@@ -78,18 +201,23 @@ export {
78
201
  DEFAULT_AFTER_SIGN_IN_URL,
79
202
  DEFAULT_AFTER_SIGN_OUT_URL,
80
203
  DEFAULT_API_URL,
204
+ DEFAULT_JWKS_URL,
81
205
  DEFAULT_SIGN_IN_URL,
82
206
  DEFAULT_SIGN_UP_URL,
83
207
  HEADER_AUTHORIZATION,
84
208
  HEADER_INAI_AUTH,
85
209
  HEADER_PUBLISHABLE_KEY,
86
210
  InAIAuthError,
211
+ JWKSClient,
87
212
  buildEndpoint,
213
+ decodeJWTHeader,
88
214
  decodeJWTPayload,
89
215
  getClaimsFromToken,
216
+ importJWKPublicKey,
90
217
  isStrongPassword,
91
218
  isTokenExpired,
92
219
  isValidEmail,
93
- normalizeApiUrl
220
+ normalizeApiUrl,
221
+ verifyES256
94
222
  };
95
223
  //# 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 === 1) throw new Error(\"Invalid base64url string\");\n if (remainder === 2) padded += \"==\";\n else if (remainder === 3) padded += \"=\";\n try {\n const binString = atob(padded);\n return Uint8Array.from(binString, (ch) => ch.charCodeAt(0));\n } catch {\n throw new Error(\"Invalid base64url encoding\");\n }\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 (required)\n if (!payload.exp || typeof payload.exp !== \"number\") {\n return null;\n }\n if (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.2.0",
3
+ "version": "1.4.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.2.0"
27
+ "@inai-dev/types": "^1.3.0"
28
28
  },
29
29
  "sideEffects": false,
30
30
  "publishConfig": {