@ovixa/auth-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,544 @@
1
+ // src/index.ts
2
+ import {
3
+ createRemoteJWKSet,
4
+ jwtVerify
5
+ } from "jose";
6
+ var OvixaAuthError = class extends Error {
7
+ constructor(message, code, statusCode) {
8
+ super(message);
9
+ this.code = code;
10
+ this.statusCode = statusCode;
11
+ this.name = "OvixaAuthError";
12
+ }
13
+ };
14
+ var DEFAULT_JWKS_CACHE_TTL = 60 * 60 * 1e3;
15
+ var OvixaAuth = class {
16
+ config;
17
+ jwksCache = null;
18
+ constructor(config) {
19
+ const authUrl = config.authUrl.replace(/\/$/, "");
20
+ this.config = {
21
+ authUrl,
22
+ realmId: config.realmId,
23
+ clientSecret: config.clientSecret,
24
+ jwksCacheTtl: config.jwksCacheTtl ?? DEFAULT_JWKS_CACHE_TTL
25
+ };
26
+ }
27
+ /** Get the configured auth URL */
28
+ get authUrl() {
29
+ return this.config.authUrl;
30
+ }
31
+ /** Get the configured realm ID */
32
+ get realmId() {
33
+ return this.config.realmId;
34
+ }
35
+ /**
36
+ * Get the JWKS URL for the auth service.
37
+ */
38
+ get jwksUrl() {
39
+ return `${this.config.authUrl}/.well-known/jwks.json`;
40
+ }
41
+ /**
42
+ * Get the JWKS fetcher, creating a new one if necessary.
43
+ * The fetcher is cached based on the configured TTL.
44
+ */
45
+ getJwksFetcher() {
46
+ const now = Date.now();
47
+ if (this.jwksCache && now - this.jwksCache.createdAt < this.config.jwksCacheTtl) {
48
+ return this.jwksCache.fetcher;
49
+ }
50
+ const jwksUrl = new URL(this.jwksUrl);
51
+ const fetcher = createRemoteJWKSet(jwksUrl, {
52
+ // jose library has its own internal caching, but we add our own TTL layer
53
+ // to control when to refresh the JWKS set
54
+ cacheMaxAge: this.config.jwksCacheTtl
55
+ });
56
+ this.jwksCache = {
57
+ fetcher,
58
+ createdAt: now
59
+ };
60
+ return fetcher;
61
+ }
62
+ /**
63
+ * Verify an access token and return the decoded payload.
64
+ *
65
+ * This method fetches the public key from the JWKS endpoint (with caching)
66
+ * and verifies the token's signature and claims.
67
+ *
68
+ * @param token - The access token (JWT) to verify
69
+ * @returns The verified token payload and header
70
+ * @throws {OvixaAuthError} If verification fails
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * try {
75
+ * const result = await auth.verifyToken(accessToken);
76
+ * console.log('User ID:', result.payload.sub);
77
+ * console.log('Email:', result.payload.email);
78
+ * } catch (error) {
79
+ * if (error instanceof OvixaAuthError) {
80
+ * console.error('Token verification failed:', error.code);
81
+ * }
82
+ * }
83
+ * ```
84
+ */
85
+ async verifyToken(token) {
86
+ try {
87
+ const jwks = this.getJwksFetcher();
88
+ const result = await jwtVerify(token, jwks, {
89
+ algorithms: ["RS256"],
90
+ issuer: this.config.authUrl,
91
+ // Audience is the realm for this application
92
+ audience: this.config.realmId
93
+ });
94
+ const payload = result.payload;
95
+ if (!payload.sub || !payload.email || payload.email_verified === void 0) {
96
+ throw new OvixaAuthError("Token is missing required claims", "INVALID_CLAIMS");
97
+ }
98
+ return {
99
+ payload,
100
+ protectedHeader: result.protectedHeader
101
+ };
102
+ } catch (error) {
103
+ if (error instanceof OvixaAuthError) {
104
+ throw error;
105
+ }
106
+ if (error instanceof Error) {
107
+ const message = error.message;
108
+ if (message.includes("expired")) {
109
+ throw new OvixaAuthError("Token has expired", "TOKEN_EXPIRED");
110
+ }
111
+ if (message.includes("signature")) {
112
+ throw new OvixaAuthError("Invalid token signature", "INVALID_SIGNATURE");
113
+ }
114
+ if (message.includes("issuer")) {
115
+ throw new OvixaAuthError("Invalid token issuer", "INVALID_ISSUER");
116
+ }
117
+ if (message.includes("audience")) {
118
+ throw new OvixaAuthError("Invalid token audience", "INVALID_AUDIENCE");
119
+ }
120
+ if (message.includes("malformed")) {
121
+ throw new OvixaAuthError("Malformed token", "MALFORMED_TOKEN");
122
+ }
123
+ throw new OvixaAuthError(`Token verification failed: ${message}`, "VERIFICATION_FAILED");
124
+ }
125
+ throw new OvixaAuthError("Token verification failed", "VERIFICATION_FAILED");
126
+ }
127
+ }
128
+ /**
129
+ * Refresh an access token using a refresh token.
130
+ *
131
+ * This method exchanges a valid refresh token for a new access token
132
+ * and a new refresh token (token rotation).
133
+ *
134
+ * @param refreshToken - The refresh token to exchange
135
+ * @returns New token response with access and refresh tokens
136
+ * @throws {OvixaAuthError} If the refresh fails
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * try {
141
+ * const tokens = await auth.refreshToken(currentRefreshToken);
142
+ * // Store the new tokens
143
+ * saveTokens(tokens.access_token, tokens.refresh_token);
144
+ * } catch (error) {
145
+ * if (error instanceof OvixaAuthError) {
146
+ * // Refresh token is invalid or expired - user must re-authenticate
147
+ * redirectToLogin();
148
+ * }
149
+ * }
150
+ * ```
151
+ */
152
+ async refreshToken(refreshToken) {
153
+ const url = `${this.config.authUrl}/token/refresh`;
154
+ try {
155
+ const response = await fetch(url, {
156
+ method: "POST",
157
+ headers: {
158
+ "Content-Type": "application/json"
159
+ },
160
+ body: JSON.stringify({
161
+ refresh_token: refreshToken
162
+ })
163
+ });
164
+ if (!response.ok) {
165
+ const errorBody = await response.json().catch(() => ({}));
166
+ const errorMessage = errorBody.error || "Token refresh failed";
167
+ const errorCode = this.mapHttpStatusToErrorCode(response.status);
168
+ throw new OvixaAuthError(errorMessage, errorCode, response.status);
169
+ }
170
+ const data = await response.json();
171
+ return data;
172
+ } catch (error) {
173
+ if (error instanceof OvixaAuthError) {
174
+ throw error;
175
+ }
176
+ if (error instanceof Error) {
177
+ throw new OvixaAuthError(`Network error: ${error.message}`, "NETWORK_ERROR");
178
+ }
179
+ throw new OvixaAuthError("Token refresh failed", "REFRESH_FAILED");
180
+ }
181
+ }
182
+ /**
183
+ * Invalidate the cached JWKS fetcher.
184
+ * Call this if you need to force a refresh of the public keys.
185
+ */
186
+ clearJwksCache() {
187
+ this.jwksCache = null;
188
+ }
189
+ /**
190
+ * Create a new user account.
191
+ *
192
+ * After signup, a verification email is sent. The user must verify their
193
+ * email before they can log in.
194
+ *
195
+ * @param options - Signup options
196
+ * @returns Signup response indicating success
197
+ * @throws {OvixaAuthError} If signup fails
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * try {
202
+ * await auth.signup({
203
+ * email: 'user@example.com',
204
+ * password: 'SecurePassword123!',
205
+ * redirectUri: 'https://myapp.com/verify-callback',
206
+ * });
207
+ * console.log('Verification email sent!');
208
+ * } catch (error) {
209
+ * if (error instanceof OvixaAuthError) {
210
+ * if (error.code === 'EMAIL_ALREADY_EXISTS') {
211
+ * console.error('Email is already registered');
212
+ * }
213
+ * }
214
+ * }
215
+ * ```
216
+ */
217
+ async signup(options) {
218
+ const url = `${this.config.authUrl}/signup`;
219
+ const body = {
220
+ email: options.email,
221
+ password: options.password,
222
+ realm_id: this.config.realmId
223
+ };
224
+ if (options.redirectUri) {
225
+ body.redirect_uri = options.redirectUri;
226
+ }
227
+ return this.makeRequest(url, body);
228
+ }
229
+ /**
230
+ * Authenticate a user with email and password.
231
+ *
232
+ * @param options - Login options
233
+ * @returns Token response with access and refresh tokens
234
+ * @throws {OvixaAuthError} If login fails
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * try {
239
+ * const tokens = await auth.login({
240
+ * email: 'user@example.com',
241
+ * password: 'SecurePassword123!',
242
+ * });
243
+ * console.log('Logged in!', tokens.access_token);
244
+ * } catch (error) {
245
+ * if (error instanceof OvixaAuthError) {
246
+ * if (error.code === 'EMAIL_NOT_VERIFIED') {
247
+ * console.error('Please verify your email first');
248
+ * } else if (error.code === 'INVALID_CREDENTIALS') {
249
+ * console.error('Invalid email or password');
250
+ * }
251
+ * }
252
+ * }
253
+ * ```
254
+ */
255
+ async login(options) {
256
+ const url = `${this.config.authUrl}/login`;
257
+ const body = {
258
+ email: options.email,
259
+ password: options.password,
260
+ realm_id: this.config.realmId
261
+ };
262
+ return this.makeRequest(url, body);
263
+ }
264
+ /**
265
+ * Verify an email address using a verification token.
266
+ *
267
+ * This is the API flow that returns tokens. For browser redirect flow,
268
+ * use `GET /verify` directly.
269
+ *
270
+ * @param options - Verification options
271
+ * @returns Token response with access and refresh tokens
272
+ * @throws {OvixaAuthError} If verification fails
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * // Get token from URL query params after clicking email link
277
+ * const token = new URLSearchParams(window.location.search).get('token');
278
+ *
279
+ * try {
280
+ * const tokens = await auth.verifyEmail({ token });
281
+ * console.log('Email verified! Logged in.');
282
+ * } catch (error) {
283
+ * if (error instanceof OvixaAuthError && error.code === 'INVALID_TOKEN') {
284
+ * console.error('Invalid or expired verification link');
285
+ * }
286
+ * }
287
+ * ```
288
+ */
289
+ async verifyEmail(options) {
290
+ const url = `${this.config.authUrl}/verify`;
291
+ const body = {
292
+ token: options.token,
293
+ realm_id: this.config.realmId,
294
+ type: "email_verification"
295
+ };
296
+ return this.makeRequest(url, body);
297
+ }
298
+ /**
299
+ * Resend a verification email for an unverified account.
300
+ *
301
+ * @param options - Resend options
302
+ * @returns Success response
303
+ * @throws {OvixaAuthError} If request fails
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * await auth.resendVerification({
308
+ * email: 'user@example.com',
309
+ * redirectUri: 'https://myapp.com/verify-callback',
310
+ * });
311
+ * console.log('Verification email sent!');
312
+ * ```
313
+ */
314
+ async resendVerification(options) {
315
+ const url = `${this.config.authUrl}/verify/resend`;
316
+ const body = {
317
+ email: options.email,
318
+ realm_id: this.config.realmId
319
+ };
320
+ if (options.redirectUri) {
321
+ body.redirect_uri = options.redirectUri;
322
+ }
323
+ return this.makeRequest(url, body);
324
+ }
325
+ /**
326
+ * Request a password reset email.
327
+ *
328
+ * Note: This endpoint always returns success to prevent email enumeration.
329
+ *
330
+ * @param options - Forgot password options
331
+ * @returns Success response
332
+ * @throws {OvixaAuthError} If request fails
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * await auth.forgotPassword({
337
+ * email: 'user@example.com',
338
+ * redirectUri: 'https://myapp.com/reset-password',
339
+ * });
340
+ * console.log('If the email exists, a reset link has been sent.');
341
+ * ```
342
+ */
343
+ async forgotPassword(options) {
344
+ const url = `${this.config.authUrl}/forgot-password`;
345
+ const body = {
346
+ email: options.email,
347
+ realm_id: this.config.realmId
348
+ };
349
+ if (options.redirectUri) {
350
+ body.redirect_uri = options.redirectUri;
351
+ }
352
+ return this.makeRequest(url, body);
353
+ }
354
+ /**
355
+ * Reset password using a reset token.
356
+ *
357
+ * @param options - Reset password options
358
+ * @returns Success response
359
+ * @throws {OvixaAuthError} If reset fails
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * // Get token from URL query params
364
+ * const token = new URLSearchParams(window.location.search).get('token');
365
+ *
366
+ * try {
367
+ * await auth.resetPassword({
368
+ * token,
369
+ * password: 'NewSecurePassword123!',
370
+ * });
371
+ * console.log('Password reset successfully!');
372
+ * } catch (error) {
373
+ * if (error instanceof OvixaAuthError) {
374
+ * if (error.code === 'INVALID_TOKEN') {
375
+ * console.error('Invalid or expired reset link');
376
+ * } else if (error.code === 'WEAK_PASSWORD') {
377
+ * console.error('Password does not meet requirements');
378
+ * }
379
+ * }
380
+ * }
381
+ * ```
382
+ */
383
+ async resetPassword(options) {
384
+ const url = `${this.config.authUrl}/reset-password`;
385
+ const body = {
386
+ token: options.token,
387
+ password: options.password,
388
+ realm_id: this.config.realmId
389
+ };
390
+ return this.makeRequest(url, body);
391
+ }
392
+ /**
393
+ * Revoke a refresh token (logout).
394
+ *
395
+ * @param refreshToken - The refresh token to revoke
396
+ * @returns Success response
397
+ * @throws {OvixaAuthError} If logout fails
398
+ *
399
+ * @example
400
+ * ```typescript
401
+ * await auth.logout(currentRefreshToken);
402
+ * // Clear local token storage
403
+ * localStorage.removeItem('refresh_token');
404
+ * localStorage.removeItem('access_token');
405
+ * ```
406
+ */
407
+ async logout(refreshToken) {
408
+ const url = `${this.config.authUrl}/logout`;
409
+ const body = {
410
+ refresh_token: refreshToken
411
+ };
412
+ return this.makeRequest(url, body);
413
+ }
414
+ /**
415
+ * Generate an OAuth authorization URL.
416
+ *
417
+ * Redirect the user to this URL to start the OAuth flow. After authentication,
418
+ * the user will be redirected back to your `redirectUri` with tokens.
419
+ *
420
+ * @param options - OAuth URL options
421
+ * @returns The full OAuth authorization URL
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * const googleAuthUrl = auth.getOAuthUrl({
426
+ * provider: 'google',
427
+ * redirectUri: 'https://myapp.com/auth/callback',
428
+ * });
429
+ *
430
+ * // Redirect user to start OAuth flow
431
+ * window.location.href = googleAuthUrl;
432
+ * ```
433
+ */
434
+ getOAuthUrl(options) {
435
+ const url = new URL(`${this.config.authUrl}/oauth/${options.provider}`);
436
+ url.searchParams.set("realm_id", this.config.realmId);
437
+ url.searchParams.set("redirect_uri", options.redirectUri);
438
+ return url.toString();
439
+ }
440
+ /**
441
+ * Transform a token response into an AuthResult with user and session data.
442
+ *
443
+ * This method decodes the access token to extract user information and
444
+ * creates a structured result object.
445
+ *
446
+ * @param tokenResponse - The token response from login, verify, or refresh
447
+ * @returns AuthResult with user and session data
448
+ * @throws {OvixaAuthError} If the access token cannot be decoded
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * const tokens = await auth.login({ email, password });
453
+ * const result = await auth.toAuthResult(tokens);
454
+ *
455
+ * console.log('User ID:', result.user.id);
456
+ * console.log('Email:', result.user.email);
457
+ * console.log('Expires at:', result.session.expiresAt);
458
+ * ```
459
+ */
460
+ async toAuthResult(tokenResponse) {
461
+ const verified = await this.verifyToken(tokenResponse.access_token);
462
+ const user = {
463
+ id: verified.payload.sub,
464
+ email: verified.payload.email,
465
+ emailVerified: verified.payload.email_verified
466
+ };
467
+ const session = {
468
+ accessToken: tokenResponse.access_token,
469
+ refreshToken: tokenResponse.refresh_token,
470
+ expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1e3)
471
+ };
472
+ const result = { user, session };
473
+ if (tokenResponse.is_new_user !== void 0) {
474
+ result.isNewUser = tokenResponse.is_new_user;
475
+ }
476
+ return result;
477
+ }
478
+ /**
479
+ * Make an authenticated POST request to the auth service.
480
+ */
481
+ async makeRequest(url, body) {
482
+ try {
483
+ const response = await fetch(url, {
484
+ method: "POST",
485
+ headers: {
486
+ "Content-Type": "application/json"
487
+ },
488
+ body: JSON.stringify(body)
489
+ });
490
+ if (!response.ok) {
491
+ const errorBody = await response.json().catch(() => ({}));
492
+ const errorData = errorBody;
493
+ let errorCode;
494
+ let errorMessage;
495
+ if (typeof errorData.error === "object" && errorData.error) {
496
+ errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);
497
+ errorMessage = errorData.error.message || "Request failed";
498
+ } else {
499
+ errorCode = this.mapHttpStatusToErrorCode(response.status);
500
+ errorMessage = typeof errorData.error === "string" ? errorData.error : "Request failed";
501
+ }
502
+ throw new OvixaAuthError(errorMessage, errorCode, response.status);
503
+ }
504
+ return await response.json();
505
+ } catch (error) {
506
+ if (error instanceof OvixaAuthError) {
507
+ throw error;
508
+ }
509
+ if (error instanceof Error) {
510
+ throw new OvixaAuthError(`Network error: ${error.message}`, "NETWORK_ERROR");
511
+ }
512
+ throw new OvixaAuthError("Request failed", "REQUEST_FAILED");
513
+ }
514
+ }
515
+ /**
516
+ * Map HTTP status codes to error codes.
517
+ */
518
+ mapHttpStatusToErrorCode(status) {
519
+ switch (status) {
520
+ case 400:
521
+ return "BAD_REQUEST";
522
+ case 401:
523
+ return "UNAUTHORIZED";
524
+ case 403:
525
+ return "FORBIDDEN";
526
+ case 404:
527
+ return "NOT_FOUND";
528
+ case 429:
529
+ return "RATE_LIMITED";
530
+ case 500:
531
+ case 502:
532
+ case 503:
533
+ return "SERVER_ERROR";
534
+ default:
535
+ return "UNKNOWN_ERROR";
536
+ }
537
+ }
538
+ };
539
+
540
+ export {
541
+ OvixaAuthError,
542
+ OvixaAuth
543
+ };
544
+ //# sourceMappingURL=chunk-UHRF6AFJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @ovixa/auth-client\n *\n * Client SDK for Ovixa Auth service.\n * Provides authentication, token verification, and session management.\n */\n\nimport {\n createRemoteJWKSet,\n jwtVerify,\n type JWTPayload,\n type JWTVerifyResult,\n type JWTHeaderParameters,\n} from 'jose';\n\n/**\n * Configuration options for the OvixaAuth client.\n */\nexport interface AuthClientConfig {\n /** The base URL of the Ovixa Auth service */\n authUrl: string;\n /** The realm ID to authenticate against */\n realmId: string;\n /** Your application's client secret (for server-side use only) */\n clientSecret?: string;\n /** Cache duration for JWKS in milliseconds (defaults to 1 hour) */\n jwksCacheTtl?: number;\n}\n\n/**\n * JWT claims for an Ovixa access token.\n */\nexport interface AccessTokenPayload extends JWTPayload {\n /** Subject (user ID) */\n sub: string;\n /** User's email address */\n email: string;\n /** Whether the user's email has been verified */\n email_verified: boolean;\n /** Issued at timestamp */\n iat: number;\n /** Expiration timestamp */\n exp: number;\n /** Issuer */\n iss: string;\n /** Audience (realm ID) */\n aud: string;\n}\n\n/**\n * Result of a successful token verification.\n */\nexport interface VerifyResult {\n /** The decoded token payload */\n payload: AccessTokenPayload;\n /** The protected header */\n protectedHeader: JWTHeaderParameters;\n}\n\n/**\n * Token response from the auth service.\n */\nexport interface TokenResponse {\n /** The access token (JWT) */\n access_token: string;\n /** The refresh token (JWT) */\n refresh_token: string;\n /** Token type (always \"Bearer\") */\n token_type: 'Bearer';\n /** Access token expiration time in seconds */\n expires_in: number;\n /** Whether this is a new user (only present in OAuth callback) */\n is_new_user?: boolean;\n}\n\n/**\n * User information extracted from token payload.\n */\nexport interface User {\n /** User ID (UUID) */\n id: string;\n /** User's email address */\n email: string;\n /** Whether the email has been verified */\n emailVerified: boolean;\n}\n\n/**\n * Session information containing tokens and expiry.\n */\nexport interface Session {\n /** The access token (JWT) */\n accessToken: string;\n /** The refresh token for obtaining new access tokens */\n refreshToken: string;\n /** When the access token expires */\n expiresAt: Date;\n}\n\n/**\n * Combined authentication result with user and session data.\n */\nexport interface AuthResult {\n /** The authenticated user */\n user: User;\n /** The session tokens */\n session: Session;\n /** Whether this is a new user (only set for OAuth flows) */\n isNewUser?: boolean;\n}\n\n/**\n * Options for signup.\n */\nexport interface SignupOptions {\n /** User's email address */\n email: string;\n /** Password meeting requirements */\n password: string;\n /** Optional redirect URI after email verification */\n redirectUri?: string;\n}\n\n/**\n * Response from signup endpoint.\n */\nexport interface SignupResponse {\n /** Whether the operation succeeded */\n success: boolean;\n /** Status message */\n message: string;\n}\n\n/**\n * Options for login.\n */\nexport interface LoginOptions {\n /** User's email address */\n email: string;\n /** User's password */\n password: string;\n}\n\n/**\n * Options for email verification.\n */\nexport interface VerifyEmailOptions {\n /** Verification token from email */\n token: string;\n}\n\n/**\n * Options for resending verification email.\n */\nexport interface ResendVerificationOptions {\n /** User's email address */\n email: string;\n /** Optional redirect URI after verification */\n redirectUri?: string;\n}\n\n/**\n * Generic success response.\n */\nexport interface SuccessResponse {\n /** Whether the operation succeeded */\n success: boolean;\n /** Optional status message */\n message?: string;\n}\n\n/**\n * Options for forgot password request.\n */\nexport interface ForgotPasswordOptions {\n /** User's email address */\n email: string;\n /** Optional redirect URI for password reset page */\n redirectUri?: string;\n}\n\n/**\n * Options for password reset.\n */\nexport interface ResetPasswordOptions {\n /** Reset token from email */\n token: string;\n /** New password */\n password: string;\n}\n\n/**\n * Supported OAuth providers.\n */\nexport type OAuthProvider = 'google' | 'github';\n\n/**\n * Options for generating OAuth URL.\n */\nexport interface GetOAuthUrlOptions {\n /** OAuth provider */\n provider: OAuthProvider;\n /** Redirect URI after OAuth completes */\n redirectUri: string;\n}\n\n/**\n * Error thrown by OvixaAuth operations.\n */\nexport class OvixaAuthError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number\n ) {\n super(message);\n this.name = 'OvixaAuthError';\n }\n}\n\ninterface InternalConfig {\n authUrl: string;\n realmId: string;\n clientSecret?: string;\n jwksCacheTtl: number;\n}\n\n/** Default JWKS cache TTL: 1 hour */\nconst DEFAULT_JWKS_CACHE_TTL = 60 * 60 * 1000;\n\n/**\n * Custom fetch function that adds cache headers for JWKS requests.\n * Used to implement client-side caching behavior.\n */\ntype JWKSFetcher = ReturnType<typeof createRemoteJWKSet>;\n\ninterface JWKSCache {\n fetcher: JWKSFetcher;\n createdAt: number;\n}\n\n/**\n * OvixaAuth client for authenticating with the Ovixa Auth service.\n *\n * @example\n * ```typescript\n * const auth = new OvixaAuth({\n * authUrl: 'https://auth.ovixa.io',\n * realmId: 'your-realm-id',\n * clientSecret: 'your-client-secret', // Optional, for server-side use\n * });\n *\n * // Verify a token\n * const result = await auth.verifyToken(accessToken);\n * console.log(result.payload.email);\n *\n * // Refresh tokens\n * const tokens = await auth.refreshToken(refreshToken);\n * ```\n */\nexport class OvixaAuth {\n private config: InternalConfig;\n private jwksCache: JWKSCache | null = null;\n\n constructor(config: AuthClientConfig) {\n // Normalize authUrl by removing trailing slash\n const authUrl = config.authUrl.replace(/\\/$/, '');\n\n this.config = {\n authUrl,\n realmId: config.realmId,\n clientSecret: config.clientSecret,\n jwksCacheTtl: config.jwksCacheTtl ?? DEFAULT_JWKS_CACHE_TTL,\n };\n }\n\n /** Get the configured auth URL */\n get authUrl(): string {\n return this.config.authUrl;\n }\n\n /** Get the configured realm ID */\n get realmId(): string {\n return this.config.realmId;\n }\n\n /**\n * Get the JWKS URL for the auth service.\n */\n get jwksUrl(): string {\n return `${this.config.authUrl}/.well-known/jwks.json`;\n }\n\n /**\n * Get the JWKS fetcher, creating a new one if necessary.\n * The fetcher is cached based on the configured TTL.\n */\n private getJwksFetcher(): JWKSFetcher {\n const now = Date.now();\n\n // Return cached fetcher if still valid\n if (this.jwksCache && now - this.jwksCache.createdAt < this.config.jwksCacheTtl) {\n return this.jwksCache.fetcher;\n }\n\n // Create new JWKS fetcher\n const jwksUrl = new URL(this.jwksUrl);\n const fetcher = createRemoteJWKSet(jwksUrl, {\n // jose library has its own internal caching, but we add our own TTL layer\n // to control when to refresh the JWKS set\n cacheMaxAge: this.config.jwksCacheTtl,\n });\n\n this.jwksCache = {\n fetcher,\n createdAt: now,\n };\n\n return fetcher;\n }\n\n /**\n * Verify an access token and return the decoded payload.\n *\n * This method fetches the public key from the JWKS endpoint (with caching)\n * and verifies the token's signature and claims.\n *\n * @param token - The access token (JWT) to verify\n * @returns The verified token payload and header\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * try {\n * const result = await auth.verifyToken(accessToken);\n * console.log('User ID:', result.payload.sub);\n * console.log('Email:', result.payload.email);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * console.error('Token verification failed:', error.code);\n * }\n * }\n * ```\n */\n async verifyToken(token: string): Promise<VerifyResult> {\n try {\n const jwks = this.getJwksFetcher();\n\n const result: JWTVerifyResult<AccessTokenPayload> = await jwtVerify(token, jwks, {\n algorithms: ['RS256'],\n issuer: this.config.authUrl,\n // Audience is the realm for this application\n audience: this.config.realmId,\n });\n\n // Validate required claims\n const payload = result.payload;\n if (!payload.sub || !payload.email || payload.email_verified === undefined) {\n throw new OvixaAuthError('Token is missing required claims', 'INVALID_CLAIMS');\n }\n\n return {\n payload: payload as AccessTokenPayload,\n protectedHeader: result.protectedHeader,\n };\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n // Handle jose library errors\n if (error instanceof Error) {\n const message = error.message;\n\n if (message.includes('expired')) {\n throw new OvixaAuthError('Token has expired', 'TOKEN_EXPIRED');\n }\n if (message.includes('signature')) {\n throw new OvixaAuthError('Invalid token signature', 'INVALID_SIGNATURE');\n }\n if (message.includes('issuer')) {\n throw new OvixaAuthError('Invalid token issuer', 'INVALID_ISSUER');\n }\n if (message.includes('audience')) {\n throw new OvixaAuthError('Invalid token audience', 'INVALID_AUDIENCE');\n }\n if (message.includes('malformed')) {\n throw new OvixaAuthError('Malformed token', 'MALFORMED_TOKEN');\n }\n\n throw new OvixaAuthError(`Token verification failed: ${message}`, 'VERIFICATION_FAILED');\n }\n\n throw new OvixaAuthError('Token verification failed', 'VERIFICATION_FAILED');\n }\n }\n\n /**\n * Refresh an access token using a refresh token.\n *\n * This method exchanges a valid refresh token for a new access token\n * and a new refresh token (token rotation).\n *\n * @param refreshToken - The refresh token to exchange\n * @returns New token response with access and refresh tokens\n * @throws {OvixaAuthError} If the refresh fails\n *\n * @example\n * ```typescript\n * try {\n * const tokens = await auth.refreshToken(currentRefreshToken);\n * // Store the new tokens\n * saveTokens(tokens.access_token, tokens.refresh_token);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * // Refresh token is invalid or expired - user must re-authenticate\n * redirectToLogin();\n * }\n * }\n * ```\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/token/refresh`;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorMessage = (errorBody as { error?: string }).error || 'Token refresh failed';\n const errorCode = this.mapHttpStatusToErrorCode(response.status);\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n const data = (await response.json()) as TokenResponse;\n return data;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Token refresh failed', 'REFRESH_FAILED');\n }\n }\n\n /**\n * Invalidate the cached JWKS fetcher.\n * Call this if you need to force a refresh of the public keys.\n */\n clearJwksCache(): void {\n this.jwksCache = null;\n }\n\n /**\n * Create a new user account.\n *\n * After signup, a verification email is sent. The user must verify their\n * email before they can log in.\n *\n * @param options - Signup options\n * @returns Signup response indicating success\n * @throws {OvixaAuthError} If signup fails\n *\n * @example\n * ```typescript\n * try {\n * await auth.signup({\n * email: 'user@example.com',\n * password: 'SecurePassword123!',\n * redirectUri: 'https://myapp.com/verify-callback',\n * });\n * console.log('Verification email sent!');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'EMAIL_ALREADY_EXISTS') {\n * console.error('Email is already registered');\n * }\n * }\n * }\n * ```\n */\n async signup(options: SignupOptions): Promise<SignupResponse> {\n const url = `${this.config.authUrl}/signup`;\n\n const body: Record<string, string> = {\n email: options.email,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SignupResponse>(url, body);\n }\n\n /**\n * Authenticate a user with email and password.\n *\n * @param options - Login options\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If login fails\n *\n * @example\n * ```typescript\n * try {\n * const tokens = await auth.login({\n * email: 'user@example.com',\n * password: 'SecurePassword123!',\n * });\n * console.log('Logged in!', tokens.access_token);\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'EMAIL_NOT_VERIFIED') {\n * console.error('Please verify your email first');\n * } else if (error.code === 'INVALID_CREDENTIALS') {\n * console.error('Invalid email or password');\n * }\n * }\n * }\n * ```\n */\n async login(options: LoginOptions): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/login`;\n\n const body = {\n email: options.email,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Verify an email address using a verification token.\n *\n * This is the API flow that returns tokens. For browser redirect flow,\n * use `GET /verify` directly.\n *\n * @param options - Verification options\n * @returns Token response with access and refresh tokens\n * @throws {OvixaAuthError} If verification fails\n *\n * @example\n * ```typescript\n * // Get token from URL query params after clicking email link\n * const token = new URLSearchParams(window.location.search).get('token');\n *\n * try {\n * const tokens = await auth.verifyEmail({ token });\n * console.log('Email verified! Logged in.');\n * } catch (error) {\n * if (error instanceof OvixaAuthError && error.code === 'INVALID_TOKEN') {\n * console.error('Invalid or expired verification link');\n * }\n * }\n * ```\n */\n async verifyEmail(options: VerifyEmailOptions): Promise<TokenResponse> {\n const url = `${this.config.authUrl}/verify`;\n\n const body = {\n token: options.token,\n realm_id: this.config.realmId,\n type: 'email_verification',\n };\n\n return this.makeRequest<TokenResponse>(url, body);\n }\n\n /**\n * Resend a verification email for an unverified account.\n *\n * @param options - Resend options\n * @returns Success response\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * await auth.resendVerification({\n * email: 'user@example.com',\n * redirectUri: 'https://myapp.com/verify-callback',\n * });\n * console.log('Verification email sent!');\n * ```\n */\n async resendVerification(options: ResendVerificationOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/verify/resend`;\n\n const body: Record<string, string> = {\n email: options.email,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Request a password reset email.\n *\n * Note: This endpoint always returns success to prevent email enumeration.\n *\n * @param options - Forgot password options\n * @returns Success response\n * @throws {OvixaAuthError} If request fails\n *\n * @example\n * ```typescript\n * await auth.forgotPassword({\n * email: 'user@example.com',\n * redirectUri: 'https://myapp.com/reset-password',\n * });\n * console.log('If the email exists, a reset link has been sent.');\n * ```\n */\n async forgotPassword(options: ForgotPasswordOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/forgot-password`;\n\n const body: Record<string, string> = {\n email: options.email,\n realm_id: this.config.realmId,\n };\n\n if (options.redirectUri) {\n body.redirect_uri = options.redirectUri;\n }\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Reset password using a reset token.\n *\n * @param options - Reset password options\n * @returns Success response\n * @throws {OvixaAuthError} If reset fails\n *\n * @example\n * ```typescript\n * // Get token from URL query params\n * const token = new URLSearchParams(window.location.search).get('token');\n *\n * try {\n * await auth.resetPassword({\n * token,\n * password: 'NewSecurePassword123!',\n * });\n * console.log('Password reset successfully!');\n * } catch (error) {\n * if (error instanceof OvixaAuthError) {\n * if (error.code === 'INVALID_TOKEN') {\n * console.error('Invalid or expired reset link');\n * } else if (error.code === 'WEAK_PASSWORD') {\n * console.error('Password does not meet requirements');\n * }\n * }\n * }\n * ```\n */\n async resetPassword(options: ResetPasswordOptions): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/reset-password`;\n\n const body = {\n token: options.token,\n password: options.password,\n realm_id: this.config.realmId,\n };\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Revoke a refresh token (logout).\n *\n * @param refreshToken - The refresh token to revoke\n * @returns Success response\n * @throws {OvixaAuthError} If logout fails\n *\n * @example\n * ```typescript\n * await auth.logout(currentRefreshToken);\n * // Clear local token storage\n * localStorage.removeItem('refresh_token');\n * localStorage.removeItem('access_token');\n * ```\n */\n async logout(refreshToken: string): Promise<SuccessResponse> {\n const url = `${this.config.authUrl}/logout`;\n\n const body = {\n refresh_token: refreshToken,\n };\n\n return this.makeRequest<SuccessResponse>(url, body);\n }\n\n /**\n * Generate an OAuth authorization URL.\n *\n * Redirect the user to this URL to start the OAuth flow. After authentication,\n * the user will be redirected back to your `redirectUri` with tokens.\n *\n * @param options - OAuth URL options\n * @returns The full OAuth authorization URL\n *\n * @example\n * ```typescript\n * const googleAuthUrl = auth.getOAuthUrl({\n * provider: 'google',\n * redirectUri: 'https://myapp.com/auth/callback',\n * });\n *\n * // Redirect user to start OAuth flow\n * window.location.href = googleAuthUrl;\n * ```\n */\n getOAuthUrl(options: GetOAuthUrlOptions): string {\n const url = new URL(`${this.config.authUrl}/oauth/${options.provider}`);\n url.searchParams.set('realm_id', this.config.realmId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n return url.toString();\n }\n\n /**\n * Transform a token response into an AuthResult with user and session data.\n *\n * This method decodes the access token to extract user information and\n * creates a structured result object.\n *\n * @param tokenResponse - The token response from login, verify, or refresh\n * @returns AuthResult with user and session data\n * @throws {OvixaAuthError} If the access token cannot be decoded\n *\n * @example\n * ```typescript\n * const tokens = await auth.login({ email, password });\n * const result = await auth.toAuthResult(tokens);\n *\n * console.log('User ID:', result.user.id);\n * console.log('Email:', result.user.email);\n * console.log('Expires at:', result.session.expiresAt);\n * ```\n */\n async toAuthResult(tokenResponse: TokenResponse): Promise<AuthResult> {\n // Verify and decode the access token\n const verified = await this.verifyToken(tokenResponse.access_token);\n\n const user: User = {\n id: verified.payload.sub,\n email: verified.payload.email,\n emailVerified: verified.payload.email_verified,\n };\n\n const session: Session = {\n accessToken: tokenResponse.access_token,\n refreshToken: tokenResponse.refresh_token,\n expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000),\n };\n\n const result: AuthResult = { user, session };\n\n if (tokenResponse.is_new_user !== undefined) {\n result.isNewUser = tokenResponse.is_new_user;\n }\n\n return result;\n }\n\n /**\n * Make an authenticated POST request to the auth service.\n */\n private async makeRequest<T>(url: string, body: Record<string, unknown>): Promise<T> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n const errorData = errorBody as { error?: { code?: string; message?: string } | string };\n\n let errorCode: string;\n let errorMessage: string;\n\n if (typeof errorData.error === 'object' && errorData.error) {\n errorCode = errorData.error.code || this.mapHttpStatusToErrorCode(response.status);\n errorMessage = errorData.error.message || 'Request failed';\n } else {\n errorCode = this.mapHttpStatusToErrorCode(response.status);\n errorMessage = typeof errorData.error === 'string' ? errorData.error : 'Request failed';\n }\n\n throw new OvixaAuthError(errorMessage, errorCode, response.status);\n }\n\n return (await response.json()) as T;\n } catch (error) {\n if (error instanceof OvixaAuthError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new OvixaAuthError(`Network error: ${error.message}`, 'NETWORK_ERROR');\n }\n\n throw new OvixaAuthError('Request failed', 'REQUEST_FAILED');\n }\n }\n\n /**\n * Map HTTP status codes to error codes.\n */\n private mapHttpStatusToErrorCode(status: number): string {\n switch (status) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED';\n case 403:\n return 'FORBIDDEN';\n case 404:\n return 'NOT_FOUND';\n case 429:\n return 'RATE_LIMITED';\n case 500:\n case 502:\n case 503:\n return 'SERVER_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n }\n}\n"],"mappings":"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","names":[]}