@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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [0.1.0] - 2026-01-26
9
+
10
+ ### Added
11
+
12
+ - Initial public release
13
+ - `OvixaAuth` client class for interacting with Ovixa Auth service
14
+ - JWT token verification with JWKS caching
15
+ - Authentication methods: `login`, `signup`, `logout`
16
+ - Token management: `refreshToken`, `verifyToken`
17
+ - Password reset flow: `forgotPassword`, `resetPassword`
18
+ - Email verification: `verifyEmail`, `resendVerification`
19
+ - OAuth support: `getOAuthUrl`
20
+ - Astro middleware integration (`@ovixa/auth-client/astro`)
21
+ - Express middleware integration (`@ovixa/auth-client/express`)
22
+ - Full TypeScript support with exported types
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jaehyun Yeom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,484 @@
1
+ # @ovixa/auth-client
2
+
3
+ Client SDK for the Ovixa Auth service. Provides authentication, token verification, and session management for applications using Ovixa's centralized identity provider.
4
+
5
+ ## Features
6
+
7
+ - Email/password authentication (signup, login, password reset)
8
+ - OAuth integration (Google, GitHub)
9
+ - JWT verification with JWKS caching
10
+ - Automatic token refresh
11
+ - Framework integrations (Astro, Express)
12
+ - TypeScript-first with full type definitions
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @ovixa/auth-client
18
+ # or
19
+ pnpm add @ovixa/auth-client
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { OvixaAuth } from '@ovixa/auth-client';
26
+
27
+ const auth = new OvixaAuth({
28
+ authUrl: 'https://auth.ovixa.io',
29
+ realmId: 'your-realm-id',
30
+ });
31
+
32
+ // Sign up a new user
33
+ await auth.signup({
34
+ email: 'user@example.com',
35
+ password: 'SecurePassword123!',
36
+ redirectUri: 'https://yourapp.com/verify-callback',
37
+ });
38
+
39
+ // Log in and get tokens
40
+ const tokens = await auth.login({
41
+ email: 'user@example.com',
42
+ password: 'SecurePassword123!',
43
+ });
44
+
45
+ // Convert to AuthResult for easier handling
46
+ const result = await auth.toAuthResult(tokens);
47
+ console.log('User:', result.user.email);
48
+ console.log('Expires:', result.session.expiresAt);
49
+
50
+ // Verify a JWT token
51
+ const verified = await auth.verifyToken(tokens.access_token);
52
+ console.log('User ID:', verified.payload.sub);
53
+ ```
54
+
55
+ ## API Reference
56
+
57
+ ### `OvixaAuth`
58
+
59
+ Main client class for interacting with the Ovixa Auth service.
60
+
61
+ #### Constructor
62
+
63
+ ```typescript
64
+ new OvixaAuth(config: AuthClientConfig)
65
+ ```
66
+
67
+ | Option | Type | Required | Description |
68
+ | -------------- | -------- | -------- | ------------------------------------------- |
69
+ | `authUrl` | `string` | Yes | Base URL of the Ovixa Auth service |
70
+ | `realmId` | `string` | Yes | The realm ID to authenticate against |
71
+ | `clientSecret` | `string` | No | Client secret (for server-side use) |
72
+ | `jwksCacheTtl` | `number` | No | JWKS cache duration in ms (default: 1 hour) |
73
+
74
+ ### Authentication Methods
75
+
76
+ #### `signup(options)`
77
+
78
+ Create a new user account. A verification email is sent after signup.
79
+
80
+ ```typescript
81
+ await auth.signup({
82
+ email: 'user@example.com',
83
+ password: 'SecurePassword123!',
84
+ redirectUri: 'https://yourapp.com/verify-callback', // Optional
85
+ });
86
+ ```
87
+
88
+ #### `login(options)`
89
+
90
+ Authenticate with email and password.
91
+
92
+ ```typescript
93
+ const tokens = await auth.login({
94
+ email: 'user@example.com',
95
+ password: 'SecurePassword123!',
96
+ });
97
+ // Returns: { access_token, refresh_token, token_type, expires_in }
98
+ ```
99
+
100
+ #### `logout(refreshToken)`
101
+
102
+ Revoke a refresh token to log out.
103
+
104
+ ```typescript
105
+ await auth.logout(refreshToken);
106
+ ```
107
+
108
+ ### Email Verification
109
+
110
+ #### `verifyEmail(options)`
111
+
112
+ Verify email using a token (returns tokens for automatic login).
113
+
114
+ ```typescript
115
+ const tokens = await auth.verifyEmail({
116
+ token: 'verification-token-from-email',
117
+ });
118
+ ```
119
+
120
+ #### `resendVerification(options)`
121
+
122
+ Resend the verification email.
123
+
124
+ ```typescript
125
+ await auth.resendVerification({
126
+ email: 'user@example.com',
127
+ redirectUri: 'https://yourapp.com/verify-callback', // Optional
128
+ });
129
+ ```
130
+
131
+ ### Password Reset
132
+
133
+ #### `forgotPassword(options)`
134
+
135
+ Request a password reset email.
136
+
137
+ ```typescript
138
+ await auth.forgotPassword({
139
+ email: 'user@example.com',
140
+ redirectUri: 'https://yourapp.com/reset-password', // Optional
141
+ });
142
+ ```
143
+
144
+ #### `resetPassword(options)`
145
+
146
+ Set a new password using a reset token.
147
+
148
+ ```typescript
149
+ await auth.resetPassword({
150
+ token: 'reset-token-from-email',
151
+ password: 'NewSecurePassword123!',
152
+ });
153
+ ```
154
+
155
+ ### Token Management
156
+
157
+ #### `verifyToken(token)`
158
+
159
+ Verify an access token and return the decoded payload.
160
+
161
+ ```typescript
162
+ const result = await auth.verifyToken(accessToken);
163
+ console.log('User ID:', result.payload.sub);
164
+ console.log('Email:', result.payload.email);
165
+ console.log('Verified:', result.payload.email_verified);
166
+ ```
167
+
168
+ #### `refreshToken(refreshToken)`
169
+
170
+ Exchange a refresh token for new tokens.
171
+
172
+ ```typescript
173
+ const newTokens = await auth.refreshToken(currentRefreshToken);
174
+ // Store the new tokens - refresh token rotation is used
175
+ ```
176
+
177
+ #### `toAuthResult(tokenResponse)`
178
+
179
+ Convert a token response to a structured `AuthResult` with user and session data.
180
+
181
+ ```typescript
182
+ const tokens = await auth.login({ email, password });
183
+ const result = await auth.toAuthResult(tokens);
184
+
185
+ // result.user: { id, email, emailVerified }
186
+ // result.session: { accessToken, refreshToken, expiresAt }
187
+ // result.isNewUser?: boolean (for OAuth flows)
188
+ ```
189
+
190
+ #### `clearJwksCache()`
191
+
192
+ Invalidate the cached JWKS to force a refresh on next verification.
193
+
194
+ ```typescript
195
+ auth.clearJwksCache();
196
+ ```
197
+
198
+ ### OAuth
199
+
200
+ #### `getOAuthUrl(options)`
201
+
202
+ Generate an OAuth authorization URL.
203
+
204
+ ```typescript
205
+ const googleUrl = auth.getOAuthUrl({
206
+ provider: 'google', // or 'github'
207
+ redirectUri: 'https://yourapp.com/auth/callback',
208
+ });
209
+
210
+ // Redirect user to start OAuth flow
211
+ window.location.href = googleUrl;
212
+ ```
213
+
214
+ After OAuth completes, the user is redirected to your `redirectUri` with tokens as URL hash parameters:
215
+
216
+ ```typescript
217
+ // Handle callback in your app
218
+ const hash = new URLSearchParams(window.location.hash.slice(1));
219
+ const accessToken = hash.get('access_token');
220
+ const refreshToken = hash.get('refresh_token');
221
+ ```
222
+
223
+ ## Framework Integrations
224
+
225
+ ### Astro Middleware
226
+
227
+ ```typescript
228
+ // src/middleware.ts
229
+ import { createAstroAuth } from '@ovixa/auth-client/astro';
230
+ import { OvixaAuth } from '@ovixa/auth-client';
231
+
232
+ const auth = new OvixaAuth({
233
+ authUrl: import.meta.env.AUTH_URL,
234
+ realmId: import.meta.env.AUTH_REALM_ID,
235
+ });
236
+
237
+ export const onRequest = createAstroAuth({
238
+ auth,
239
+ publicRoutes: ['/', '/login', '/signup', '/api/public/*'],
240
+ loginRedirect: '/login',
241
+ cookies: {
242
+ secure: import.meta.env.PROD,
243
+ },
244
+ });
245
+ ```
246
+
247
+ Access auth context in pages:
248
+
249
+ ```astro
250
+ ---
251
+ // src/pages/dashboard.astro
252
+ const { user, isAuthenticated } = Astro.locals.auth;
253
+
254
+ if (!isAuthenticated) {
255
+ return Astro.redirect('/login');
256
+ }
257
+ ---
258
+
259
+ <h1>Welcome, {user.email}</h1>
260
+ ```
261
+
262
+ Set cookies after login:
263
+
264
+ ```typescript
265
+ // src/pages/api/login.ts
266
+ import { setAstroAuthCookies } from '@ovixa/auth-client/astro';
267
+ import type { APIContext } from 'astro';
268
+
269
+ export async function POST({ request, cookies }: APIContext) {
270
+ const { email, password } = await request.json();
271
+ const tokens = await auth.login({ email, password });
272
+
273
+ setAstroAuthCookies({ cookies }, tokens);
274
+
275
+ return new Response(JSON.stringify({ success: true }));
276
+ }
277
+ ```
278
+
279
+ Clear cookies on logout:
280
+
281
+ ```typescript
282
+ // src/pages/api/logout.ts
283
+ import { clearAstroAuthCookies } from '@ovixa/auth-client/astro';
284
+
285
+ export async function POST({ cookies, locals }: APIContext) {
286
+ if (locals.auth?.session?.refreshToken) {
287
+ await auth.logout(locals.auth.session.refreshToken);
288
+ }
289
+
290
+ clearAstroAuthCookies({ cookies });
291
+
292
+ return new Response(JSON.stringify({ success: true }));
293
+ }
294
+ ```
295
+
296
+ ### Express Middleware
297
+
298
+ ```typescript
299
+ import express from 'express';
300
+ import cookieParser from 'cookie-parser';
301
+ import { createExpressAuth, requireAuth } from '@ovixa/auth-client/express';
302
+ import { OvixaAuth } from '@ovixa/auth-client';
303
+
304
+ const auth = new OvixaAuth({
305
+ authUrl: process.env.AUTH_URL!,
306
+ realmId: process.env.AUTH_REALM_ID!,
307
+ });
308
+
309
+ const app = express();
310
+ app.use(cookieParser());
311
+ app.use(
312
+ createExpressAuth({
313
+ auth,
314
+ publicRoutes: ['/', '/login', '/signup'],
315
+ loginRedirect: '/login',
316
+ cookies: {
317
+ secure: process.env.NODE_ENV === 'production',
318
+ },
319
+ })
320
+ );
321
+
322
+ // Access auth context in routes
323
+ app.get('/api/me', (req, res) => {
324
+ if (!req.auth?.isAuthenticated) {
325
+ return res.status(401).json({ error: 'Unauthorized' });
326
+ }
327
+ res.json({ user: req.auth.user });
328
+ });
329
+
330
+ // Use requireAuth middleware for protected routes
331
+ app.get('/dashboard', requireAuth({ redirect: '/login' }), (req, res) => {
332
+ res.render('dashboard', { user: req.auth.user });
333
+ });
334
+ ```
335
+
336
+ Set cookies after login:
337
+
338
+ ```typescript
339
+ import { setExpressAuthCookies } from '@ovixa/auth-client/express';
340
+
341
+ app.post('/api/login', async (req, res) => {
342
+ const { email, password } = req.body;
343
+ const tokens = await auth.login({ email, password });
344
+
345
+ setExpressAuthCookies(res, req, tokens);
346
+
347
+ res.json({ success: true });
348
+ });
349
+ ```
350
+
351
+ ### Custom Framework Integration
352
+
353
+ Implement the `CookieAdapter` interface to add support for other frameworks:
354
+
355
+ ```typescript
356
+ import type {
357
+ CookieAdapter,
358
+ SetCookieOptions,
359
+ DeleteCookieOptions,
360
+ } from '@ovixa/auth-client/astro';
361
+
362
+ class HonoCookieAdapter implements CookieAdapter {
363
+ constructor(private context: HonoContext) {}
364
+
365
+ getCookie(name: string): string | undefined {
366
+ return this.context.req.cookie(name);
367
+ }
368
+
369
+ setCookie(name: string, value: string, options: SetCookieOptions): void {
370
+ this.context.cookie(name, value, options);
371
+ }
372
+
373
+ deleteCookie(name: string, options: DeleteCookieOptions): void {
374
+ this.context.cookie(name, '', { ...options, maxAge: 0 });
375
+ }
376
+ }
377
+ ```
378
+
379
+ ## Middleware Configuration
380
+
381
+ | Option | Type | Default | Description |
382
+ | --------------- | ----------- | -------- | ---------------------------------------- |
383
+ | `auth` | `OvixaAuth` | Required | The OvixaAuth client instance |
384
+ | `publicRoutes` | `string[]` | `[]` | Routes that bypass authentication |
385
+ | `loginRedirect` | `string` | - | URL to redirect unauthenticated requests |
386
+ | `autoRefresh` | `boolean` | `true` | Automatically refresh expired tokens |
387
+ | `cookies` | `object` | - | Cookie configuration (see below) |
388
+
389
+ ### Cookie Options
390
+
391
+ | Option | Type | Default | Description |
392
+ | -------------------- | --------- | ----------------------- | ------------------------------- |
393
+ | `accessTokenCookie` | `string` | `'ovixa_access_token'` | Name of access token cookie |
394
+ | `refreshTokenCookie` | `string` | `'ovixa_refresh_token'` | Name of refresh token cookie |
395
+ | `path` | `string` | `'/'` | Cookie path |
396
+ | `secure` | `boolean` | `true` | Use secure cookies (HTTPS only) |
397
+ | `httpOnly` | `boolean` | `true` | HTTP-only cookies |
398
+ | `sameSite` | `string` | `'lax'` | SameSite policy |
399
+ | `domain` | `string` | - | Cookie domain |
400
+
401
+ ## Error Handling
402
+
403
+ All methods throw `OvixaAuthError` on failure:
404
+
405
+ ```typescript
406
+ import { OvixaAuth, OvixaAuthError } from '@ovixa/auth-client';
407
+
408
+ try {
409
+ await auth.login({ email, password });
410
+ } catch (error) {
411
+ if (error instanceof OvixaAuthError) {
412
+ console.error('Code:', error.code); // e.g., 'INVALID_CREDENTIALS'
413
+ console.error('Message:', error.message);
414
+ console.error('Status:', error.statusCode); // HTTP status code
415
+ }
416
+ }
417
+ ```
418
+
419
+ ### Error Codes
420
+
421
+ | Code | Description |
422
+ | --------------------- | ----------------------------------- |
423
+ | `INVALID_CREDENTIALS` | Wrong email or password |
424
+ | `EMAIL_NOT_VERIFIED` | User must verify email before login |
425
+ | `INVALID_TOKEN` | Token is invalid or malformed |
426
+ | `TOKEN_EXPIRED` | Token has expired |
427
+ | `INVALID_SIGNATURE` | Token signature verification failed |
428
+ | `INVALID_ISSUER` | Token issuer doesn't match |
429
+ | `INVALID_AUDIENCE` | Token audience doesn't match |
430
+ | `RATE_LIMITED` | Too many requests |
431
+ | `NETWORK_ERROR` | Failed to reach auth service |
432
+ | `BAD_REQUEST` | Invalid request parameters |
433
+ | `UNAUTHORIZED` | Authentication required |
434
+ | `FORBIDDEN` | Access denied |
435
+ | `NOT_FOUND` | Resource not found |
436
+ | `SERVER_ERROR` | Auth service error |
437
+
438
+ ## Types
439
+
440
+ ```typescript
441
+ // Token response from auth service
442
+ interface TokenResponse {
443
+ access_token: string;
444
+ refresh_token: string;
445
+ token_type: 'Bearer';
446
+ expires_in: number;
447
+ is_new_user?: boolean; // OAuth flows only
448
+ }
449
+
450
+ // User information (extracted from JWT claims)
451
+ interface User {
452
+ id: string;
453
+ email: string;
454
+ emailVerified: boolean;
455
+ // Note: createdAt is intentionally omitted. The JWT `iat` claim is when the
456
+ // *token* was issued, not when the user was created. If you need the user's
457
+ // creation date, fetch it from the /me endpoint or include it in custom claims.
458
+ }
459
+
460
+ // Session data
461
+ interface Session {
462
+ accessToken: string;
463
+ refreshToken: string;
464
+ expiresAt: Date;
465
+ }
466
+
467
+ // Combined auth result
468
+ interface AuthResult {
469
+ user: User;
470
+ session: Session;
471
+ isNewUser?: boolean;
472
+ }
473
+
474
+ // Auth context (available in middleware)
475
+ interface AuthContext {
476
+ user: User | null;
477
+ session: Session | null;
478
+ isAuthenticated: boolean;
479
+ }
480
+ ```
481
+
482
+ ## License
483
+
484
+ MIT