@nik2208/node-auth 1.0.2

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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +636 -0
  3. package/dist/abstract/base-auth-strategy.abstract.d.ts +7 -0
  4. package/dist/abstract/base-auth-strategy.abstract.d.ts.map +1 -0
  5. package/dist/abstract/base-auth-strategy.abstract.js +7 -0
  6. package/dist/abstract/base-auth-strategy.abstract.js.map +1 -0
  7. package/dist/abstract/base-oauth-strategy.abstract.d.ts +26 -0
  8. package/dist/abstract/base-oauth-strategy.abstract.d.ts.map +1 -0
  9. package/dist/abstract/base-oauth-strategy.abstract.js +11 -0
  10. package/dist/abstract/base-oauth-strategy.abstract.js.map +1 -0
  11. package/dist/auth-configurator.d.ts +24 -0
  12. package/dist/auth-configurator.d.ts.map +1 -0
  13. package/dist/auth-configurator.js +42 -0
  14. package/dist/auth-configurator.js.map +1 -0
  15. package/dist/index.d.ts +25 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +36 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/interfaces/auth-strategy.interface.d.ts +6 -0
  20. package/dist/interfaces/auth-strategy.interface.d.ts.map +1 -0
  21. package/dist/interfaces/auth-strategy.interface.js +3 -0
  22. package/dist/interfaces/auth-strategy.interface.js.map +1 -0
  23. package/dist/interfaces/token-store.interface.d.ts +10 -0
  24. package/dist/interfaces/token-store.interface.d.ts.map +1 -0
  25. package/dist/interfaces/token-store.interface.js +3 -0
  26. package/dist/interfaces/token-store.interface.js.map +1 -0
  27. package/dist/interfaces/user-store.interface.d.ts +23 -0
  28. package/dist/interfaces/user-store.interface.d.ts.map +1 -0
  29. package/dist/interfaces/user-store.interface.js +3 -0
  30. package/dist/interfaces/user-store.interface.js.map +1 -0
  31. package/dist/middleware/auth.middleware.d.ts +12 -0
  32. package/dist/middleware/auth.middleware.d.ts.map +1 -0
  33. package/dist/middleware/auth.middleware.js +23 -0
  34. package/dist/middleware/auth.middleware.js.map +1 -0
  35. package/dist/models/auth-config.model.d.ts +96 -0
  36. package/dist/models/auth-config.model.d.ts.map +1 -0
  37. package/dist/models/auth-config.model.js +3 -0
  38. package/dist/models/auth-config.model.js.map +1 -0
  39. package/dist/models/errors.d.ts +6 -0
  40. package/dist/models/errors.d.ts.map +1 -0
  41. package/dist/models/errors.js +13 -0
  42. package/dist/models/errors.js.map +1 -0
  43. package/dist/models/token.model.d.ts +12 -0
  44. package/dist/models/token.model.d.ts.map +1 -0
  45. package/dist/models/token.model.js +3 -0
  46. package/dist/models/token.model.js.map +1 -0
  47. package/dist/models/user.model.d.ts +18 -0
  48. package/dist/models/user.model.d.ts.map +1 -0
  49. package/dist/models/user.model.js +3 -0
  50. package/dist/models/user.model.js.map +1 -0
  51. package/dist/router/auth.router.d.ts +13 -0
  52. package/dist/router/auth.router.d.ts.map +1 -0
  53. package/dist/router/auth.router.js +329 -0
  54. package/dist/router/auth.router.js.map +1 -0
  55. package/dist/services/mailer.service.d.ts +11 -0
  56. package/dist/services/mailer.service.d.ts.map +1 -0
  57. package/dist/services/mailer.service.js +138 -0
  58. package/dist/services/mailer.service.js.map +1 -0
  59. package/dist/services/password.service.d.ts +5 -0
  60. package/dist/services/password.service.d.ts.map +1 -0
  61. package/dist/services/password.service.js +17 -0
  62. package/dist/services/password.service.js.map +1 -0
  63. package/dist/services/sms.service.d.ts +14 -0
  64. package/dist/services/sms.service.d.ts.map +1 -0
  65. package/dist/services/sms.service.js +51 -0
  66. package/dist/services/sms.service.js.map +1 -0
  67. package/dist/services/token.service.d.ts +13 -0
  68. package/dist/services/token.service.d.ts.map +1 -0
  69. package/dist/services/token.service.js +78 -0
  70. package/dist/services/token.service.js.map +1 -0
  71. package/dist/strategies/local/local.strategy.d.ts +19 -0
  72. package/dist/strategies/local/local.strategy.d.ts.map +1 -0
  73. package/dist/strategies/local/local.strategy.js +29 -0
  74. package/dist/strategies/local/local.strategy.js.map +1 -0
  75. package/dist/strategies/magic-link/magic-link.strategy.d.ts +8 -0
  76. package/dist/strategies/magic-link/magic-link.strategy.d.ts.map +1 -0
  77. package/dist/strategies/magic-link/magic-link.strategy.js +50 -0
  78. package/dist/strategies/magic-link/magic-link.strategy.js.map +1 -0
  79. package/dist/strategies/oauth/github.strategy.d.ts +29 -0
  80. package/dist/strategies/oauth/github.strategy.d.ts.map +1 -0
  81. package/dist/strategies/oauth/github.strategy.js +69 -0
  82. package/dist/strategies/oauth/github.strategy.js.map +1 -0
  83. package/dist/strategies/oauth/google.strategy.d.ts +29 -0
  84. package/dist/strategies/oauth/google.strategy.d.ts.map +1 -0
  85. package/dist/strategies/oauth/google.strategy.js +61 -0
  86. package/dist/strategies/oauth/google.strategy.js.map +1 -0
  87. package/dist/strategies/sms/sms.strategy.d.ts +7 -0
  88. package/dist/strategies/sms/sms.strategy.d.ts.map +1 -0
  89. package/dist/strategies/sms/sms.strategy.js +39 -0
  90. package/dist/strategies/sms/sms.strategy.js.map +1 -0
  91. package/dist/strategies/two-factor/totp.strategy.d.ts +12 -0
  92. package/dist/strategies/two-factor/totp.strategy.d.ts.map +1 -0
  93. package/dist/strategies/two-factor/totp.strategy.js +32 -0
  94. package/dist/strategies/two-factor/totp.strategy.js.map +1 -0
  95. package/package.json +48 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nik2208
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,636 @@
1
+ # node-auth
2
+
3
+ A production-ready, **database-agnostic** JWT authentication library for Node.js written in TypeScript. Compatible with any Node.js framework (NestJS, Next.js, Express, Fastify, etc.) and any database through a simple interface pattern.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **JWT Authentication** – Access & refresh token pair management with HttpOnly cookies
8
+ - 🏠 **Local Strategy** – Email/password authentication with bcrypt hashing
9
+ - 🔄 **OAuth 2.0** – Extensible Google & GitHub OAuth strategies
10
+ - 🪄 **Magic Links** – Passwordless email authentication
11
+ - 📱 **SMS OTP** – Phone number verification codes
12
+ - 🔑 **TOTP 2FA** – Time-based one-time passwords (Google Authenticator compatible)
13
+ - 🗃️ **Database Agnostic** – Implement a simple interface to use any database
14
+ - 🧩 **Strategy Pattern** – Plug in only the auth methods you need
15
+ - 🛡️ **Middleware** – Express-compatible JWT verification middleware
16
+ - 🚀 **Express Router** – Drop-in `/auth` router with all endpoints
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install node-auth
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import express from 'express';
28
+ import { AuthConfigurator } from 'node-auth';
29
+ import { myUserStore } from './my-user-store'; // Your IUserStore implementation
30
+
31
+ const app = express();
32
+ app.use(express.json());
33
+
34
+ const auth = new AuthConfigurator(
35
+ {
36
+ accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
37
+ refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
38
+ accessTokenExpiresIn: '15m',
39
+ refreshTokenExpiresIn: '7d',
40
+ },
41
+ myUserStore
42
+ );
43
+
44
+ // Mount the auth router at /auth
45
+ app.use('/auth', auth.router());
46
+
47
+ // Protect routes
48
+ app.get('/protected', auth.middleware(), (req, res) => {
49
+ res.json({ user: req.user });
50
+ });
51
+
52
+ app.listen(3000);
53
+ ```
54
+
55
+ ## Database Integration — Implementing IUserStore
56
+
57
+ The library is **completely database-agnostic**. The only coupling point to your database is the
58
+ `IUserStore` interface. Implement it once for your DB and pass the instance to `AuthConfigurator`.
59
+
60
+ ### Interface contract
61
+
62
+ ```typescript
63
+ import { IUserStore, BaseUser } from 'node-auth';
64
+
65
+ export class MyUserStore implements IUserStore {
66
+ // ---- Required: core CRUD ---------------------------------------------------
67
+
68
+ /** Find a user by email address (used for login, magic link, password reset). */
69
+ async findByEmail(email: string): Promise<BaseUser | null> { /* ... */ }
70
+
71
+ /** Find a user by primary key (used for token refresh, 2FA, SMS). */
72
+ async findById(id: string): Promise<BaseUser | null> { /* ... */ }
73
+
74
+ /** Create a new user (used by OAuth strategies when user doesn't exist yet). */
75
+ async create(data: Partial<BaseUser>): Promise<BaseUser> { /* ... */ }
76
+
77
+ // ---- Required: token field updates ----------------------------------------
78
+
79
+ async updateRefreshToken(userId: string, token: string | null, expiry: Date | null): Promise<void> { /* ... */ }
80
+ async updateResetToken(userId: string, token: string | null, expiry: Date | null): Promise<void> { /* ... */ }
81
+ async updatePassword(userId: string, hashedPassword: string): Promise<void> { /* ... */ }
82
+ async updateTotpSecret(userId: string, secret: string | null): Promise<void> { /* ... */ }
83
+ async updateMagicLinkToken(userId: string, token: string | null, expiry: Date | null): Promise<void> { /* ... */ }
84
+ async updateSmsCode(userId: string, code: string | null, expiry: Date | null): Promise<void> { /* ... */ }
85
+
86
+ // ---- Optional: token look-ups (required for specific features) ------------
87
+
88
+ /**
89
+ * Required for: POST /auth/reset-password
90
+ * Find a user whose `resetToken` field matches the given token.
91
+ */
92
+ async findByResetToken(token: string): Promise<BaseUser | null> { /* ... */ }
93
+
94
+ /**
95
+ * Required for: POST /auth/magic-link/verify
96
+ * Find a user whose `magicLinkToken` field matches the given token.
97
+ */
98
+ async findByMagicLinkToken(token: string): Promise<BaseUser | null> { /* ... */ }
99
+ }
100
+ ```
101
+
102
+ ### Ready-to-use example implementations
103
+
104
+ The `examples/` directory contains complete implementations for the most common databases and frameworks:
105
+
106
+ | File | Description |
107
+ |------|-------------|
108
+ | `examples/in-memory-user-store.ts` | In-memory store — ideal for testing and prototyping |
109
+ | `examples/sqlite-user-store.example.ts` | `better-sqlite3` store — production-ready SQL example |
110
+ | `examples/mysql-user-store.example.ts` | `mysql2` store — MySQL / MariaDB example |
111
+ | `examples/mongodb-user-store.example.ts` | `mongodb` store — MongoDB example |
112
+ | `examples/nestjs-integration.example.ts` | NestJS module, guard, controller and DI integration |
113
+ | `examples/nextjs-integration.example.ts` | Next.js App Router & Pages Router integration |
114
+
115
+ Copy the relevant file(s) into your project and adapt the schema to your needs.
116
+
117
+ **In-memory store (testing/prototyping):**
118
+ ```typescript
119
+ import { InMemoryUserStore } from './examples/in-memory-user-store';
120
+ const userStore = new InMemoryUserStore();
121
+ const auth = new AuthConfigurator(config, userStore);
122
+ ```
123
+
124
+ **SQLite with `better-sqlite3`:**
125
+ ```typescript
126
+ import Database from 'better-sqlite3';
127
+ import { SqliteUserStore } from './examples/sqlite-user-store.example';
128
+
129
+ const db = new Database('app.db');
130
+ db.pragma('journal_mode = WAL');
131
+ db.pragma('foreign_keys = ON');
132
+
133
+ const userStore = new SqliteUserStore(db); // creates the `users` table automatically
134
+ const auth = new AuthConfigurator(config, userStore);
135
+ ```
136
+
137
+ **MySQL / MariaDB with `mysql2`:**
138
+ ```typescript
139
+ import mysql from 'mysql2/promise';
140
+ import { MySqlUserStore } from './examples/mysql-user-store.example';
141
+
142
+ const pool = mysql.createPool({
143
+ host: process.env.DB_HOST,
144
+ user: process.env.DB_USER,
145
+ password: process.env.DB_PASSWORD,
146
+ database: process.env.DB_NAME,
147
+ });
148
+
149
+ const userStore = new MySqlUserStore(pool);
150
+ await userStore.init(); // creates the `users` table automatically
151
+ const auth = new AuthConfigurator(config, userStore);
152
+ ```
153
+
154
+ **MongoDB with the `mongodb` driver:**
155
+ ```typescript
156
+ import { MongoClient } from 'mongodb';
157
+ import { MongoDbUserStore } from './examples/mongodb-user-store.example';
158
+
159
+ const client = new MongoClient(process.env.MONGODB_URI!);
160
+ await client.connect();
161
+
162
+ const userStore = new MongoDbUserStore(client.db('myapp'));
163
+ await userStore.init(); // creates indexes automatically
164
+ const auth = new AuthConfigurator(config, userStore);
165
+ ```
166
+
167
+ **PostgreSQL (example skeleton):**
168
+ ```typescript
169
+ import { Pool } from 'pg';
170
+ import { IUserStore, BaseUser } from 'node-auth';
171
+
172
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
173
+
174
+ export class PgUserStore implements IUserStore {
175
+ async findByEmail(email: string) {
176
+ const { rows } = await pool.query('SELECT * FROM users WHERE email=$1', [email]);
177
+ return rows[0] ?? null;
178
+ }
179
+ async findById(id: string) {
180
+ const { rows } = await pool.query('SELECT * FROM users WHERE id=$1', [id]);
181
+ return rows[0] ?? null;
182
+ }
183
+ // ... implement remaining methods
184
+ }
185
+ ```
186
+
187
+
188
+ ## Framework Integration
189
+
190
+ ### NestJS
191
+
192
+ See `examples/nestjs-integration.example.ts` for a full working example that includes:
193
+
194
+ - **`AuthModule.forRoot()`** — NestJS DynamicModule wrapping `AuthConfigurator`
195
+ - **`JwtAuthGuard`** — NestJS `CanActivate` guard backed by `auth.middleware()`
196
+ - **`@CurrentUser()`** — parameter decorator that extracts `req.user`
197
+ - **`AuthController`** — catch-all controller that forwards `/auth/*` traffic to `auth.router()`
198
+
199
+ ```typescript
200
+ // app.module.ts
201
+ import { AuthModule } from './auth.module';
202
+ import { MyUserStore } from './my-user-store';
203
+
204
+ @Module({
205
+ imports: [
206
+ AuthModule.forRoot({
207
+ config: authConfig,
208
+ userStore: new MyUserStore(),
209
+ }),
210
+ ],
211
+ })
212
+ export class AppModule {}
213
+
214
+ // Protect a route
215
+ @Controller('profile')
216
+ export class ProfileController {
217
+ @Get()
218
+ @UseGuards(JwtAuthGuard)
219
+ getProfile(@CurrentUser() user: BaseUser) {
220
+ return user;
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### Next.js
226
+
227
+ See `examples/nextjs-integration.example.ts` for a full working example that covers both the **App Router** (Next.js 13+) and the legacy **Pages Router**.
228
+
229
+ **Pages Router (simplest approach):**
230
+
231
+ ```typescript
232
+ // pages/api/auth/[...auth].ts
233
+ import type { NextApiRequest, NextApiResponse } from 'next';
234
+ import { getAuth } from '../../lib/auth';
235
+
236
+ export const config = { api: { bodyParser: false } };
237
+
238
+ export default function handler(req: NextApiRequest, res: NextApiResponse) {
239
+ const router = getAuth().router();
240
+ req.url = req.url!.replace(/^\/api\/auth/, '') || '/';
241
+ router(req as any, res as any, () => res.status(404).end());
242
+ }
243
+ ```
244
+
245
+ **Protecting a Server Component (App Router):**
246
+
247
+ ```typescript
248
+ // app/dashboard/page.tsx
249
+ import { cookies } from 'next/headers';
250
+ import { redirect } from 'next/navigation';
251
+ import { TokenService } from 'node-auth';
252
+ import { authConfig } from '../../lib/auth';
253
+
254
+ export default async function DashboardPage() {
255
+ const token = cookies().get('access_token')?.value;
256
+ if (!token) redirect('/login');
257
+
258
+ const payload = new TokenService().verifyAccessToken(token, authConfig);
259
+ if (!payload) redirect('/login');
260
+
261
+ return <div>Welcome, {payload.email}!</div>;
262
+ }
263
+ ```
264
+
265
+ ## Auth Router Endpoints
266
+
267
+ When you mount `auth.router()`, the following endpoints are available:
268
+
269
+ | Method | Path | Description |
270
+ |--------|------|-------------|
271
+ | `POST` | `/auth/login` | Login with email/password |
272
+ | `POST` | `/auth/logout` | Logout and clear cookies |
273
+ | `POST` | `/auth/refresh` | Refresh access token |
274
+ | `GET` | `/auth/me` | Get current user (protected) |
275
+ | `POST` | `/auth/forgot-password` | Send password reset email |
276
+ | `POST` | `/auth/reset-password` | Reset password with token |
277
+ | `POST` | `/auth/2fa/setup` | Get TOTP secret + QR code (protected) |
278
+ | `POST` | `/auth/2fa/verify-setup` | Verify TOTP code and enable 2FA (protected) |
279
+ | `POST` | `/auth/2fa/verify` | Complete 2FA login |
280
+ | `POST` | `/auth/2fa/disable` | Disable 2FA (protected) |
281
+ | `POST` | `/auth/magic-link/send` | Send magic link email |
282
+ | `POST` | `/auth/magic-link/verify` | Verify magic link token |
283
+ | `POST` | `/auth/sms/send` | Send SMS verification code |
284
+ | `POST` | `/auth/sms/verify` | Verify SMS code |
285
+ | `GET` | `/auth/oauth/google` | Initiate Google OAuth |
286
+ | `GET` | `/auth/oauth/google/callback` | Google OAuth callback |
287
+ | `GET` | `/auth/oauth/github` | Initiate GitHub OAuth |
288
+ | `GET` | `/auth/oauth/github/callback` | GitHub OAuth callback |
289
+
290
+ ## Configuration
291
+
292
+ ```typescript
293
+ import { AuthConfig } from 'node-auth';
294
+
295
+ const config: AuthConfig = {
296
+ // Required
297
+ accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
298
+ refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
299
+
300
+ // Token lifetimes (default: 15m / 7d)
301
+ accessTokenExpiresIn: '15m',
302
+ refreshTokenExpiresIn: '7d',
303
+
304
+ // Cookie options
305
+ cookieOptions: {
306
+ secure: true, // HTTPS only (recommended in production)
307
+ sameSite: 'lax',
308
+ domain: 'yourdomain.com',
309
+ },
310
+
311
+ // bcrypt salt rounds (default: 12)
312
+ bcryptSaltRounds: 12,
313
+
314
+ // Email — see "Mailer Configuration" section below
315
+ email: {
316
+ siteUrl: 'https://yourapp.com',
317
+ mailer: {
318
+ endpoint: process.env.MAILER_ENDPOINT!, // HTTP POST endpoint
319
+ apiKey: process.env.MAILER_API_KEY!,
320
+ from: 'noreply@yourapp.com',
321
+ fromName: 'My App',
322
+ defaultLang: 'en', // 'en' or 'it'
323
+ },
324
+ },
325
+
326
+ // SMS (for OTP verification codes)
327
+ sms: {
328
+ endpoint: 'https://sms.example.com/sendsms',
329
+ apiKey: process.env.SMS_API_KEY!,
330
+ username: process.env.SMS_USERNAME!,
331
+ password: process.env.SMS_PASSWORD!,
332
+ codeExpiresInMinutes: 10,
333
+ },
334
+
335
+ // OAuth
336
+ oauth: {
337
+ google: {
338
+ clientId: process.env.GOOGLE_CLIENT_ID!,
339
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
340
+ callbackUrl: 'https://yourapp.com/auth/oauth/google/callback',
341
+ },
342
+ github: {
343
+ clientId: process.env.GITHUB_CLIENT_ID!,
344
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
345
+ callbackUrl: 'https://yourapp.com/auth/oauth/github/callback',
346
+ },
347
+ },
348
+
349
+ // 2FA app name shown in authenticator apps
350
+ twoFactor: {
351
+ appName: 'My App',
352
+ },
353
+ };
354
+ ```
355
+
356
+ ## Mailer Configuration
357
+
358
+ The library ships a built-in **HTTP mailer transport** (`MailerService`) that sends transactional
359
+ emails (password reset, magic links, welcome) via an HTTP POST to any configurable endpoint — no
360
+ SMTP required. Built-in templates are available in **English** (`en`) and **Italian** (`it`).
361
+
362
+ ### Option A — Built-in HTTP mailer transport (recommended)
363
+
364
+ Configure `email.mailer` in `AuthConfig`. The library will automatically send emails using the
365
+ built-in templates whenever a reset link or magic link needs to go out.
366
+
367
+ ```typescript
368
+ import { AuthConfig, MailerConfig } from 'node-auth';
369
+
370
+ const config: AuthConfig = {
371
+ // ...jwt secrets, cookies...
372
+ email: {
373
+ siteUrl: 'https://yourapp.com',
374
+ mailer: {
375
+ /** Full URL of your mailer API endpoint. Receives a JSON POST. */
376
+ endpoint: process.env.MAILER_ENDPOINT!, // e.g. 'https://api.mailgun.net/v3/...'
377
+ /** API key sent as the X-API-Key request header. */
378
+ apiKey: process.env.MAILER_API_KEY!,
379
+ /** Sender address. */
380
+ from: 'noreply@yourapp.com',
381
+ /** Sender display name (optional). */
382
+ fromName: 'My App',
383
+ /**
384
+ * Default language for built-in templates.
385
+ * Supported: 'en' (default) | 'it'
386
+ * Can be overridden per-request by passing emailLang in the request body.
387
+ */
388
+ defaultLang: 'en',
389
+ },
390
+ },
391
+ };
392
+ ```
393
+
394
+ The mailer sends a **POST** request to `endpoint` with the following JSON body:
395
+
396
+ ```json
397
+ {
398
+ "to": "user@example.com",
399
+ "from": "noreply@yourapp.com",
400
+ "fromName": "My App",
401
+ "subject": "Reset your password",
402
+ "html": "<p>Click the link...</p>",
403
+ "text": "Click the link..."
404
+ }
405
+ ```
406
+
407
+ and the header `X-API-Key: <apiKey>`.
408
+
409
+ Your mailer API (Mailgun, Resend, SendGrid, a custom proxy, etc.) only needs to accept this JSON
410
+ shape and forward it to the email provider. The content-type is `application/json`.
411
+
412
+ #### Per-request language override
413
+
414
+ For `POST /auth/forgot-password` and `POST /auth/magic-link/send`, pass `emailLang` in the request
415
+ body to override `defaultLang` for a single request:
416
+
417
+ ```json
418
+ { "email": "user@example.com", "emailLang": "it" }
419
+ ```
420
+
421
+ #### Using `MailerService` directly
422
+
423
+ ```typescript
424
+ import { MailerService } from 'node-auth';
425
+
426
+ const mailer = new MailerService({
427
+ endpoint: 'https://mailer.example.com/send',
428
+ apiKey: 'key-xxx',
429
+ from: 'noreply@example.com',
430
+ defaultLang: 'it',
431
+ });
432
+
433
+ await mailer.sendPasswordReset(to, token, resetLink, 'it');
434
+ await mailer.sendMagicLink(to, token, magicLink);
435
+ await mailer.sendWelcome(to, { loginUrl: 'https://yourapp.com/login', tempPassword: 'Temp@123' }, 'it');
436
+ ```
437
+
438
+ ### Option B — Custom callbacks
439
+
440
+ If you prefer full control, provide callback functions instead of (or in addition to) `mailer`.
441
+ **Callbacks always take precedence over the `mailer` transport.**
442
+
443
+ ```typescript
444
+ email: {
445
+ siteUrl: 'https://yourapp.com',
446
+ sendPasswordReset: async (to, token, link, lang) => {
447
+ await myEmailClient.send({ to, subject: 'Reset your password', html: `...${link}...` });
448
+ },
449
+ sendMagicLink: async (to, token, link, lang) => {
450
+ await myEmailClient.send({ to, subject: 'Your sign-in link', html: `...${link}...` });
451
+ },
452
+ sendWelcome: async (to, data, lang) => {
453
+ await myEmailClient.send({ to, subject: 'Welcome!', html: `...${data.loginUrl}...` });
454
+ },
455
+ },
456
+ ```
457
+
458
+
459
+ ## OAuth Strategies
460
+
461
+ OAuth strategies are abstract—extend them to implement your own user lookup logic:
462
+
463
+ ```typescript
464
+ import { GoogleStrategy, BaseUser, AuthConfig } from 'node-auth';
465
+
466
+ class MyGoogleStrategy extends GoogleStrategy<BaseUser> {
467
+ constructor(config: AuthConfig, private userStore: MyUserStore) {
468
+ super(config);
469
+ }
470
+
471
+ async findOrCreateUser(profile: { id: string; email: string; name?: string; picture?: string }) {
472
+ let user = await this.userStore.findByEmail(profile.email);
473
+ if (!user) {
474
+ user = await this.userStore.create({ email: profile.email, role: 'user' });
475
+ }
476
+ return user;
477
+ }
478
+ }
479
+
480
+ // Pass to router
481
+ app.use('/auth', auth.router({
482
+ googleStrategy: new MyGoogleStrategy(config, userStore),
483
+ githubStrategy: new MyGithubStrategy(config, userStore),
484
+ }));
485
+ ```
486
+
487
+ ## Using Services Directly
488
+
489
+ Access the underlying services for custom flows:
490
+
491
+ ```typescript
492
+ const auth = new AuthConfigurator(config, userStore);
493
+
494
+ // Hash passwords
495
+ const hash = await auth.passwordService.hash('mypassword');
496
+ const valid = await auth.passwordService.compare('mypassword', hash);
497
+
498
+ // Generate/verify tokens
499
+ const tokens = auth.tokenService.generateTokenPair({ sub: userId, email }, config);
500
+ const payload = auth.tokenService.verifyAccessToken(token, config);
501
+
502
+ // Get a local strategy instance
503
+ const localStrategy = auth.strategy('local');
504
+ const user = await localStrategy.authenticate({ email, password }, config);
505
+ ```
506
+
507
+ ## Using Strategies Independently
508
+
509
+ ```typescript
510
+ import {
511
+ MagicLinkStrategy,
512
+ SmsStrategy,
513
+ TotpStrategy,
514
+ LocalStrategy,
515
+ PasswordService,
516
+ } from 'node-auth';
517
+
518
+ // Magic Links
519
+ const magicLink = new MagicLinkStrategy();
520
+ await magicLink.sendMagicLink(email, userStore, config);
521
+ const user = await magicLink.verify(token, userStore);
522
+
523
+ // SMS OTP
524
+ const sms = new SmsStrategy();
525
+ await sms.sendCode(phone, userId, userStore, config);
526
+ const valid = await sms.verify(userId, code, userStore);
527
+
528
+ // TOTP 2FA
529
+ const totpStrategy = new TotpStrategy();
530
+ const { secret, otpauthUrl, qrCode } = totpStrategy.generateSecret(email, 'MyApp');
531
+ const qrDataUrl = await qrCode; // data:image/png;base64,...
532
+ const isValid = await totpStrategy.verify(token, secret);
533
+ ```
534
+
535
+ ## BaseUser Model
536
+
537
+ ```typescript
538
+ interface BaseUser {
539
+ id: string;
540
+ email: string;
541
+ password?: string;
542
+ role?: string;
543
+ refreshToken?: string | null;
544
+ refreshTokenExpiry?: Date | null;
545
+ resetToken?: string | null;
546
+ resetTokenExpiry?: Date | null;
547
+ totpSecret?: string | null;
548
+ isTotpEnabled?: boolean;
549
+ magicLinkToken?: string | null;
550
+ magicLinkTokenExpiry?: Date | null;
551
+ smsCode?: string | null;
552
+ smsCodeExpiry?: Date | null;
553
+ phoneNumber?: string | null;
554
+ }
555
+ ```
556
+
557
+ ## Error Handling
558
+
559
+ The library throws `AuthError` for authentication failures:
560
+
561
+ ```typescript
562
+ import { AuthError } from 'node-auth';
563
+
564
+ try {
565
+ await localStrategy.authenticate({ email, password }, config);
566
+ } catch (err) {
567
+ if (err instanceof AuthError) {
568
+ console.log(err.code); // e.g. 'INVALID_CREDENTIALS'
569
+ console.log(err.statusCode); // e.g. 401
570
+ console.log(err.message); // e.g. 'Invalid credentials'
571
+ }
572
+ }
573
+ ```
574
+
575
+ ## Custom Strategies
576
+
577
+ Extend `BaseAuthStrategy` to create custom authentication strategies:
578
+
579
+ ```typescript
580
+ import { BaseAuthStrategy, AuthConfig } from 'node-auth';
581
+
582
+ class ApiKeyStrategy extends BaseAuthStrategy<{ apiKey: string }, MyUser> {
583
+ name = 'api-key';
584
+
585
+ async authenticate(input: { apiKey: string }, config: AuthConfig): Promise<MyUser> {
586
+ const user = await myStore.findByApiKey(input.apiKey);
587
+ if (!user) throw new AuthError('Invalid API key', 'INVALID_API_KEY', 401);
588
+ return user;
589
+ }
590
+ }
591
+ ```
592
+
593
+ ## Rate Limiting
594
+
595
+ Auth routes should be rate-limited in production to prevent brute-force attacks. Pass an optional `rateLimiter` middleware to `createAuthRouter()`:
596
+
597
+ ```typescript
598
+ import rateLimit from 'express-rate-limit';
599
+
600
+ const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 20 });
601
+
602
+ app.use('/auth', auth.router({ rateLimiter: limiter }));
603
+ ```
604
+
605
+ All sensitive endpoints (login, refresh, password reset, 2FA, magic links, SMS) will be protected.
606
+
607
+ ## Building
608
+
609
+ ```bash
610
+ npm run build
611
+ ```
612
+
613
+ ## Testing
614
+
615
+ ```bash
616
+ npm test
617
+ npm run test:coverage
618
+ ```
619
+
620
+ ## Architecture
621
+
622
+ ```
623
+ src/
624
+ ├── interfaces/ # IUserStore, ITokenStore, IAuthStrategy
625
+ ├── models/ # BaseUser, TokenPair, AuthConfig, AuthError
626
+ ├── abstract/ # BaseAuthStrategy, BaseOAuthStrategy
627
+ ├── strategies/ # Local, Google, GitHub, MagicLink, SMS, TOTP
628
+ ├── services/ # TokenService, PasswordService, SmsService
629
+ ├── middleware/ # createAuthMiddleware()
630
+ ├── router/ # createAuthRouter() – full Express router
631
+ └── auth-configurator.ts # Main entry point
632
+ ```
633
+
634
+ ## License
635
+
636
+ MIT
@@ -0,0 +1,7 @@
1
+ import { AuthConfig } from '../models/auth-config.model';
2
+ import { BaseUser } from '../models/user.model';
3
+ export declare abstract class BaseAuthStrategy<TInput = unknown, TUser = BaseUser> {
4
+ abstract name: string;
5
+ abstract authenticate(input: TInput, config: AuthConfig): Promise<TUser>;
6
+ }
7
+ //# sourceMappingURL=base-auth-strategy.abstract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-auth-strategy.abstract.d.ts","sourceRoot":"","sources":["../../src/abstract/base-auth-strategy.abstract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,8BAAsB,gBAAgB,CAAC,MAAM,GAAG,OAAO,EAAE,KAAK,GAAG,QAAQ;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC;CACzE"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseAuthStrategy = void 0;
4
+ class BaseAuthStrategy {
5
+ }
6
+ exports.BaseAuthStrategy = BaseAuthStrategy;
7
+ //# sourceMappingURL=base-auth-strategy.abstract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-auth-strategy.abstract.js","sourceRoot":"","sources":["../../src/abstract/base-auth-strategy.abstract.ts"],"names":[],"mappings":";;;AAGA,MAAsB,gBAAgB;CAGrC;AAHD,4CAGC"}