@inai-dev/shared 1.2.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 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,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
- const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
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
@@ -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
- const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
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.2.0",
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.2.0"
27
+ "@inai-dev/types": "^1.3.0"
28
28
  },
29
29
  "sideEffects": false,
30
30
  "publishConfig": {