@lenne.tech/nest-server 11.6.1 → 11.6.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.
- package/dist/config.env.js +141 -0
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/decorators/graphql-populate.decorator.d.ts +2 -2
- package/dist/core/common/decorators/restricted.decorator.d.ts +1 -0
- package/dist/core/common/decorators/restricted.decorator.js +1 -1
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +1 -0
- package/dist/core/common/helpers/input.helper.js +1 -1
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +50 -0
- package/dist/core/modules/auth/auth-guard-strategy.enum.d.ts +1 -0
- package/dist/core/modules/auth/auth-guard-strategy.enum.js +1 -0
- package/dist/core/modules/auth/auth-guard-strategy.enum.js.map +1 -1
- package/dist/core/modules/auth/guards/auth.guard.js +11 -5
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/core/modules/auth/tokens.decorator.d.ts +1 -1
- package/dist/core/modules/better-auth/better-auth-auth.model.d.ts +9 -0
- package/dist/core/modules/better-auth/better-auth-auth.model.js +63 -0
- package/dist/core/modules/better-auth/better-auth-auth.model.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth-models.d.ts +44 -0
- package/dist/core/modules/better-auth/better-auth-models.js +185 -0
- package/dist/core/modules/better-auth/better-auth-models.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.d.ts +12 -0
- package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js +70 -0
- package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth-rate-limiter.service.d.ts +32 -0
- package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js +173 -0
- package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.d.ts +43 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.js +159 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.config.d.ts +9 -0
- package/dist/core/modules/better-auth/better-auth.config.js +251 -0
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.middleware.d.ts +20 -0
- package/dist/core/modules/better-auth/better-auth.middleware.js +79 -0
- package/dist/core/modules/better-auth/better-auth.middleware.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.module.d.ts +30 -0
- package/dist/core/modules/better-auth/better-auth.module.js +265 -0
- package/dist/core/modules/better-auth/better-auth.module.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.resolver.d.ts +49 -0
- package/dist/core/modules/better-auth/better-auth.resolver.js +539 -0
- package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.service.d.ts +38 -0
- package/dist/core/modules/better-auth/better-auth.service.js +151 -0
- package/dist/core/modules/better-auth/better-auth.service.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.types.d.ts +38 -0
- package/dist/core/modules/better-auth/better-auth.types.js +15 -0
- package/dist/core/modules/better-auth/better-auth.types.js.map +1 -0
- package/dist/core/modules/better-auth/index.d.ts +11 -0
- package/dist/core/modules/better-auth/index.js +28 -0
- package/dist/core/modules/better-auth/index.js.map +1 -0
- package/dist/core/modules/user/core-user.model.d.ts +2 -0
- package/dist/core/modules/user/core-user.model.js +21 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core.module.js +7 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +9 -1
- package/src/config.env.ts +148 -1
- package/src/core/common/decorators/restricted.decorator.ts +2 -2
- package/src/core/common/helpers/input.helper.ts +2 -2
- package/src/core/common/interfaces/server-options.interface.ts +344 -20
- package/src/core/modules/auth/auth-guard-strategy.enum.ts +1 -0
- package/src/core/modules/auth/guards/auth.guard.ts +20 -6
- package/src/core/modules/better-auth/README.md +1096 -0
- package/src/core/modules/better-auth/better-auth-auth.model.ts +69 -0
- package/src/core/modules/better-auth/better-auth-models.ts +143 -0
- package/src/core/modules/better-auth/better-auth-rate-limit.middleware.ts +113 -0
- package/src/core/modules/better-auth/better-auth-rate-limiter.service.ts +326 -0
- package/src/core/modules/better-auth/better-auth-user.mapper.ts +269 -0
- package/src/core/modules/better-auth/better-auth.config.ts +483 -0
- package/src/core/modules/better-auth/better-auth.middleware.ts +111 -0
- package/src/core/modules/better-auth/better-auth.module.ts +433 -0
- package/src/core/modules/better-auth/better-auth.resolver.ts +678 -0
- package/src/core/modules/better-auth/better-auth.service.ts +323 -0
- package/src/core/modules/better-auth/better-auth.types.ts +75 -0
- package/src/core/modules/better-auth/index.ts +25 -0
- package/src/core/modules/user/core-user.model.ts +29 -0
- package/src/core.module.ts +12 -0
- package/src/index.ts +6 -0
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
# Better-Auth Module
|
|
2
|
+
|
|
3
|
+
Integration of the [better-auth](https://better-auth.com) authentication framework with @lenne.tech/nest-server.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Built-in Plugins (Explicit Configuration)
|
|
8
|
+
|
|
9
|
+
- **JWT Tokens** - For API clients and stateless authentication
|
|
10
|
+
- **Two-Factor Authentication (2FA)** - TOTP-based second factor
|
|
11
|
+
- **Passkey/WebAuthn** - Passwordless authentication
|
|
12
|
+
- **Legacy Password Handling** - Migration support for existing users
|
|
13
|
+
|
|
14
|
+
### Core Features
|
|
15
|
+
|
|
16
|
+
- **Email/Password Authentication** - Standard username/password login
|
|
17
|
+
- **Social Login** - Any OAuth provider (Google, GitHub, Apple, Discord, etc.)
|
|
18
|
+
- **Parallel Operation** - Runs alongside Legacy Auth without side effects
|
|
19
|
+
|
|
20
|
+
### Extensible via Plugins
|
|
21
|
+
|
|
22
|
+
- **Organization** - Multi-tenant, teams, member management
|
|
23
|
+
- **Admin** - User impersonation, banning
|
|
24
|
+
- **SSO** - Single Sign-On (OIDC, SAML)
|
|
25
|
+
- **And many more** - See [Plugins and Extensions](#plugins-and-extensions)
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
**True Zero-Config: Better-Auth is enabled by default!** No configuration block is required - it works out of the box.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// Works automatically - no betterAuth config needed!
|
|
33
|
+
// Better-Auth will use sensible defaults and fallback secrets
|
|
34
|
+
|
|
35
|
+
// To customize behavior (optional):
|
|
36
|
+
const config = {
|
|
37
|
+
betterAuth: {
|
|
38
|
+
// Only add this block if you need to override defaults
|
|
39
|
+
// baseUrl: 'https://your-domain.com',
|
|
40
|
+
// basePath: '/iam',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Default values (used when not configured):**
|
|
46
|
+
|
|
47
|
+
- **Secret**: Falls back to `jwt.secret` → `jwt.refresh.secret` → auto-generated
|
|
48
|
+
- **Base URL**: `http://localhost:3000`
|
|
49
|
+
- **Base Path**: `/iam`
|
|
50
|
+
- **Passkey Origin**: `http://localhost:3000`
|
|
51
|
+
- **Passkey rpId**: `localhost`
|
|
52
|
+
- **Passkey rpName**: `Nest Server`
|
|
53
|
+
|
|
54
|
+
To **explicitly disable** Better-Auth (the only way to turn it off):
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const config = {
|
|
58
|
+
betterAuth: {
|
|
59
|
+
enabled: false, // Only way to disable Better-Auth
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Read the security section below for production deployments.
|
|
65
|
+
|
|
66
|
+
## Understanding Core Settings
|
|
67
|
+
|
|
68
|
+
### Base URL (`baseUrl`)
|
|
69
|
+
|
|
70
|
+
**Purpose:** Absolute URL generation for links and redirects
|
|
71
|
+
|
|
72
|
+
**Technically used for:**
|
|
73
|
+
|
|
74
|
+
- **Email Links**: Password reset links, email verification links
|
|
75
|
+
```text
|
|
76
|
+
https://your-domain.com/iam/reset-password?token=xyz
|
|
77
|
+
```
|
|
78
|
+
- **OAuth Redirect URIs**: Callback URLs for social login providers
|
|
79
|
+
```text
|
|
80
|
+
https://your-domain.com/iam/callback/google
|
|
81
|
+
```
|
|
82
|
+
- **CORS Origin Validation**: If no `trustedOrigins` are set, `baseUrl` is used as the trusted origin
|
|
83
|
+
|
|
84
|
+
**Impact of incorrect value:** Email links point to wrong server, OAuth callbacks fail.
|
|
85
|
+
|
|
86
|
+
### Base Path (`basePath`)
|
|
87
|
+
|
|
88
|
+
**Purpose:** URL prefix for all Better-Auth REST endpoints
|
|
89
|
+
|
|
90
|
+
**Technically used for:**
|
|
91
|
+
|
|
92
|
+
- **Routing**: All endpoints are registered under this path
|
|
93
|
+
```text
|
|
94
|
+
/iam/sign-in
|
|
95
|
+
/iam/sign-up
|
|
96
|
+
/iam/session
|
|
97
|
+
/iam/callback/:provider
|
|
98
|
+
```
|
|
99
|
+
- **Middleware Matching**: `BetterAuthMiddleware` only forwards requests to paths starting with `basePath`
|
|
100
|
+
|
|
101
|
+
**Default `/iam`** avoids collisions with existing `/auth` routes from Legacy Auth.
|
|
102
|
+
|
|
103
|
+
### Passkey Origin (`passkey.origin`)
|
|
104
|
+
|
|
105
|
+
**Purpose:** WebAuthn/Passkey security validation
|
|
106
|
+
|
|
107
|
+
**Technically used for:**
|
|
108
|
+
|
|
109
|
+
- **Passkey Registration/Authentication**: Browser sends origin in WebAuthn request
|
|
110
|
+
- **Origin Verification**: Server validates that request comes from expected origin
|
|
111
|
+
- **Phishing Protection**: Prevents passkeys from being used on other domains
|
|
112
|
+
|
|
113
|
+
**Impact of incorrect value:** Passkey authentication fails with "origin mismatch" error.
|
|
114
|
+
|
|
115
|
+
### Configuration Summary
|
|
116
|
+
|
|
117
|
+
| Setting | Technical Purpose | Impact of Wrong Value |
|
|
118
|
+
| ----------------- | ---------------------- | ---------------------------- |
|
|
119
|
+
| `baseUrl` | Email links, OAuth | Links point to wrong server |
|
|
120
|
+
| `basePath` | Endpoint routing | 404 on API calls |
|
|
121
|
+
| `passkey.origin` | WebAuthn security | Passkey auth fails |
|
|
122
|
+
|
|
123
|
+
**For Development:** The defaults (`http://localhost:3000`, `/iam`) are correct.
|
|
124
|
+
|
|
125
|
+
**For Production:** You must set `baseUrl` and `passkey.origin` to your actual domain:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const config = {
|
|
129
|
+
betterAuth: {
|
|
130
|
+
baseUrl: 'https://api.your-domain.com',
|
|
131
|
+
passkey: {
|
|
132
|
+
// enabled by default when config block is present
|
|
133
|
+
origin: 'https://your-domain.com', // Frontend domain
|
|
134
|
+
rpId: 'your-domain.com', // Domain without protocol
|
|
135
|
+
rpName: 'Your Application',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## IMPORTANT: Secret Configuration
|
|
142
|
+
|
|
143
|
+
### Secret Resolution (Fallback Chain)
|
|
144
|
+
|
|
145
|
+
Better-Auth resolves the secret in the following order:
|
|
146
|
+
|
|
147
|
+
1. **`betterAuth.secret`** - If explicitly configured
|
|
148
|
+
2. **`jwt.secret`** - Fallback for backwards compatibility (if ≥32 chars)
|
|
149
|
+
3. **`jwt.refresh.secret`** - Second fallback (if ≥32 chars)
|
|
150
|
+
4. **Auto-generated** - Secure random secret (with warning)
|
|
151
|
+
|
|
152
|
+
### Backwards Compatibility
|
|
153
|
+
|
|
154
|
+
If you already have `jwt.secret` configured with ≥32 characters, **Better-Auth will automatically use it** as a fallback. This means:
|
|
155
|
+
|
|
156
|
+
- No configuration needed for existing projects with proper JWT secrets
|
|
157
|
+
- Sessions persist across server restarts (uses existing secret)
|
|
158
|
+
- A warning reminds you to set `betterAuth.secret` explicitly
|
|
159
|
+
|
|
160
|
+
### Development (Auto-Generated Secret)
|
|
161
|
+
|
|
162
|
+
When no valid secret is found (including fallbacks), Better-Auth **automatically generates a secure random secret** at server startup.
|
|
163
|
+
|
|
164
|
+
**Consequences:**
|
|
165
|
+
|
|
166
|
+
- **Secure** - The secret is cryptographically random (32 bytes)
|
|
167
|
+
- **Sessions lost on restart** - All user sessions become invalid when the server restarts
|
|
168
|
+
- **Acceptable for development** - Frequent restarts during development are normal
|
|
169
|
+
|
|
170
|
+
### Production (Recommended)
|
|
171
|
+
|
|
172
|
+
For production, explicitly set `betterAuth.secret` or ensure `jwt.secret` is ≥32 characters:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Generate a secure secret:
|
|
176
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
|
177
|
+
|
|
178
|
+
# Set in your environment:
|
|
179
|
+
export BETTER_AUTH_SECRET="your-generated-secret-here"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Consequences of using auto-generated secret in production:**
|
|
183
|
+
|
|
184
|
+
- Sessions lost on every deployment - All users must re-login after each deploy
|
|
185
|
+
- Sessions lost on server restart - Any restart invalidates all sessions
|
|
186
|
+
- No session sharing in clusters - Multiple server instances can't share sessions
|
|
187
|
+
|
|
188
|
+
### Secret Requirements
|
|
189
|
+
|
|
190
|
+
| Requirement | Value |
|
|
191
|
+
| ----------------- | --------------------------------------------------------- |
|
|
192
|
+
| Minimum length | 32 characters |
|
|
193
|
+
| Recommended | 44+ characters (32 bytes base64) |
|
|
194
|
+
| Character types | At least 2 of: lowercase, uppercase, numbers, special |
|
|
195
|
+
|
|
196
|
+
### Configuration Examples
|
|
197
|
+
|
|
198
|
+
**Via Environment Variable (Recommended):**
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
BETTER_AUTH_SECRET=K7xR2mN9pQ4wE6tY8uI0oP3aS5dF7gH9jK1lZ2xC4vB6nM8=
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Via config.env.ts:**
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const config = {
|
|
208
|
+
betterAuth: {
|
|
209
|
+
// enabled: true by default - no need to set explicitly
|
|
210
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Configuration
|
|
216
|
+
|
|
217
|
+
**Optional** - Better-Auth works without any configuration (true zero-config). Only add this block if you need to customize behavior:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// In config.env.ts
|
|
221
|
+
export default {
|
|
222
|
+
// ... other config
|
|
223
|
+
|
|
224
|
+
// OPTIONAL: Better-Auth configuration
|
|
225
|
+
// Omit entirely for default behavior, or customize as needed:
|
|
226
|
+
betterAuth: {
|
|
227
|
+
// enabled: true by default - only set to false to disable
|
|
228
|
+
// secret: auto-generated if not set (see Security section above)
|
|
229
|
+
// baseUrl: 'http://localhost:3000', // Default
|
|
230
|
+
// basePath: '/iam', // Default
|
|
231
|
+
|
|
232
|
+
// JWT Plugin (enabled by default when config block is present)
|
|
233
|
+
// Set enabled: false to explicitly disable
|
|
234
|
+
jwt: {
|
|
235
|
+
expiresIn: '15m',
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// Two-Factor Authentication (enabled by default when config block is present)
|
|
239
|
+
// Set enabled: false to explicitly disable
|
|
240
|
+
twoFactor: {
|
|
241
|
+
appName: 'My Application',
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// Passkey/WebAuthn (enabled by default when config block is present)
|
|
245
|
+
// Set enabled: false to explicitly disable
|
|
246
|
+
passkey: {
|
|
247
|
+
rpId: 'localhost',
|
|
248
|
+
rpName: 'My Application',
|
|
249
|
+
origin: 'http://localhost:3000',
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// Social Providers (enabled by default when credentials are configured)
|
|
253
|
+
// Set enabled: false to explicitly disable a provider
|
|
254
|
+
socialProviders: {
|
|
255
|
+
google: {
|
|
256
|
+
clientId: 'your-google-client-id',
|
|
257
|
+
clientSecret: 'your-google-client-secret',
|
|
258
|
+
},
|
|
259
|
+
github: {
|
|
260
|
+
clientId: 'your-github-client-id',
|
|
261
|
+
clientSecret: 'your-github-client-secret',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// Legacy Password Handling (enabled by default when config block is present)
|
|
266
|
+
// Set enabled: false to explicitly disable
|
|
267
|
+
legacyPassword: {},
|
|
268
|
+
|
|
269
|
+
// Trusted Origins for CORS
|
|
270
|
+
trustedOrigins: ['http://localhost:3000', 'https://your-app.com'],
|
|
271
|
+
|
|
272
|
+
// Rate Limiting (optional)
|
|
273
|
+
rateLimit: {
|
|
274
|
+
enabled: true,
|
|
275
|
+
max: 10,
|
|
276
|
+
windowSeconds: 60,
|
|
277
|
+
message: 'Too many requests, please try again later.',
|
|
278
|
+
strictEndpoints: ['/sign-in', '/sign-up', '/forgot-password', '/reset-password'],
|
|
279
|
+
skipEndpoints: ['/session', '/callback'],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Plugins and Extensions
|
|
286
|
+
|
|
287
|
+
Better-Auth provides a rich plugin ecosystem. This module uses a **hybrid approach**:
|
|
288
|
+
|
|
289
|
+
- **Built-in plugins** (JWT, 2FA, Passkey): Explicitly configured with typed options
|
|
290
|
+
- **Additional plugins**: Dynamically added via the `plugins` array
|
|
291
|
+
|
|
292
|
+
### Built-in Plugins (Explicit Configuration)
|
|
293
|
+
|
|
294
|
+
These plugins are enabled by default when their config block is present. **All properties have sensible defaults**, so an empty block `{}` is sufficient!
|
|
295
|
+
|
|
296
|
+
| Plugin | Minimal Config | Default Values |
|
|
297
|
+
| ------------------ | -------------------- | --------------------------------------------------------------------------------- |
|
|
298
|
+
| **JWT** | `jwt: {}` | `expiresIn: '15m'` |
|
|
299
|
+
| **Two-Factor** | `twoFactor: {}` | `appName: 'Nest Server'` |
|
|
300
|
+
| **Passkey** | `passkey: {}` | `origin: 'http://localhost:3000'`, `rpId: 'localhost'`, `rpName: 'Nest Server'` |
|
|
301
|
+
| **Legacy Password**| `legacyPassword: {}` | No config needed |
|
|
302
|
+
|
|
303
|
+
#### Minimal Syntax (Recommended for Development)
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
const config = {
|
|
307
|
+
betterAuth: {
|
|
308
|
+
// Just add empty blocks - all defaults are applied!
|
|
309
|
+
jwt: {},
|
|
310
|
+
twoFactor: {},
|
|
311
|
+
passkey: {},
|
|
312
|
+
legacyPassword: {},
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### Custom Configuration (Recommended for Production)
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const config = {
|
|
321
|
+
betterAuth: {
|
|
322
|
+
jwt: { expiresIn: '30m' },
|
|
323
|
+
twoFactor: { appName: 'My App' },
|
|
324
|
+
passkey: {
|
|
325
|
+
rpId: 'example.com',
|
|
326
|
+
rpName: 'My App',
|
|
327
|
+
origin: 'https://example.com',
|
|
328
|
+
},
|
|
329
|
+
legacyPassword: {},
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Disabling a Plugin
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
const config = {
|
|
338
|
+
betterAuth: {
|
|
339
|
+
jwt: { enabled: false }, // Explicitly disable JWT
|
|
340
|
+
twoFactor: {}, // 2FA still enabled with defaults
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Dynamic Plugins (plugins Array)
|
|
346
|
+
|
|
347
|
+
For all other Better-Auth plugins, use the `plugins` array. This provides maximum flexibility without requiring updates to this package.
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { organization } from 'better-auth/plugins';
|
|
351
|
+
import { admin } from 'better-auth/plugins';
|
|
352
|
+
import { multiSession } from 'better-auth/plugins';
|
|
353
|
+
import { apiKey } from 'better-auth/plugins';
|
|
354
|
+
|
|
355
|
+
const config = {
|
|
356
|
+
betterAuth: {
|
|
357
|
+
// Built-in plugins
|
|
358
|
+
jwt: { expiresIn: '30m' },
|
|
359
|
+
|
|
360
|
+
// Additional plugins via array
|
|
361
|
+
plugins: [
|
|
362
|
+
organization({
|
|
363
|
+
allowUserToCreateOrganization: true,
|
|
364
|
+
creatorRole: 'owner',
|
|
365
|
+
}),
|
|
366
|
+
admin({
|
|
367
|
+
impersonationSessionDuration: 60 * 60,
|
|
368
|
+
}),
|
|
369
|
+
multiSession({
|
|
370
|
+
maximumSessions: 5,
|
|
371
|
+
}),
|
|
372
|
+
apiKey(),
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Available Better-Auth Plugins
|
|
379
|
+
|
|
380
|
+
| Plugin | Use Case | Recommendation |
|
|
381
|
+
| ------------------ | --------------------------------------------- | --------------------------- |
|
|
382
|
+
| **organization** | Multi-tenant apps, teams, member management | Common for SaaS/B2B |
|
|
383
|
+
| **admin** | User impersonation, banning, user management | Common for admin panels |
|
|
384
|
+
| **multiSession** | Multiple active sessions per user | Account switching apps |
|
|
385
|
+
| **apiKey** | API key based authentication | Public APIs |
|
|
386
|
+
| **sso** | Single Sign-On (OIDC, SAML 2.0) | Enterprise apps |
|
|
387
|
+
| **oidcProvider** | Build your own identity provider | Identity platforms |
|
|
388
|
+
| **genericOAuth** | Custom OAuth providers | Special OAuth integrations |
|
|
389
|
+
| **polar** | Usage-based billing with Polar | SaaS billing |
|
|
390
|
+
|
|
391
|
+
For the complete list of plugins, see:
|
|
392
|
+
|
|
393
|
+
- [Official Plugins](https://www.better-auth.com/docs/concepts/plugins)
|
|
394
|
+
- [Community Plugins](https://www.better-auth.com/docs/plugins/community-plugins)
|
|
395
|
+
|
|
396
|
+
### Example: Organization Plugin
|
|
397
|
+
|
|
398
|
+
Multi-tenant support with teams and roles:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { organization } from 'better-auth/plugins';
|
|
402
|
+
|
|
403
|
+
const config = {
|
|
404
|
+
betterAuth: {
|
|
405
|
+
plugins: [
|
|
406
|
+
organization({
|
|
407
|
+
allowUserToCreateOrganization: true,
|
|
408
|
+
creatorRole: 'owner',
|
|
409
|
+
membershipLimit: 100,
|
|
410
|
+
organizationLimit: 5,
|
|
411
|
+
teams: {
|
|
412
|
+
enabled: true,
|
|
413
|
+
maximumTeams: 10,
|
|
414
|
+
},
|
|
415
|
+
}),
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Example: Admin Plugin
|
|
422
|
+
|
|
423
|
+
User management and impersonation:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { admin } from 'better-auth/plugins';
|
|
427
|
+
|
|
428
|
+
const config = {
|
|
429
|
+
betterAuth: {
|
|
430
|
+
plugins: [
|
|
431
|
+
admin({
|
|
432
|
+
impersonationSessionDuration: 60 * 60,
|
|
433
|
+
defaultRole: 'user',
|
|
434
|
+
adminRole: 'admin',
|
|
435
|
+
}),
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Example: SSO Plugin
|
|
442
|
+
|
|
443
|
+
Enterprise Single Sign-On:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import { sso } from 'better-auth/plugins';
|
|
447
|
+
|
|
448
|
+
const config = {
|
|
449
|
+
betterAuth: {
|
|
450
|
+
plugins: [
|
|
451
|
+
sso({
|
|
452
|
+
issuer: 'https://your-identity-provider.com',
|
|
453
|
+
// ... OIDC/SAML configuration
|
|
454
|
+
}),
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Why This Hybrid Approach?
|
|
461
|
+
|
|
462
|
+
| Approach | Pros | Cons |
|
|
463
|
+
| --------------------------------- | -------------------------------------------------- | --------------------------------- |
|
|
464
|
+
| **Built-in** (jwt, 2fa, passkey) | TypeScript types, IDE autocomplete, documentation | Package update needed for changes |
|
|
465
|
+
| **Dynamic** (plugins array) | Any plugin works immediately, future-proof | No typed config in IBetterAuth |
|
|
466
|
+
|
|
467
|
+
**Best of both worlds:**
|
|
468
|
+
|
|
469
|
+
- Core auth plugins with great developer experience
|
|
470
|
+
- Full flexibility for specialized plugins
|
|
471
|
+
- No package updates needed for new Better-Auth plugins
|
|
472
|
+
|
|
473
|
+
## Module Setup
|
|
474
|
+
|
|
475
|
+
The Better-Auth module is **automatically enabled by default** - no configuration required (true zero-config). It will only be disabled if you explicitly set `enabled: false`.
|
|
476
|
+
|
|
477
|
+
### Using with CoreModule
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// In your ServerModule
|
|
481
|
+
@Module({
|
|
482
|
+
imports: [
|
|
483
|
+
CoreModule.forRoot(environment),
|
|
484
|
+
// BetterAuthModule is automatically included and configured
|
|
485
|
+
// No betterAuth config block needed - works out of the box!
|
|
486
|
+
],
|
|
487
|
+
})
|
|
488
|
+
export class ServerModule {}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Standalone Usage
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
import { BetterAuthModule } from '@lenne.tech/nest-server';
|
|
495
|
+
|
|
496
|
+
@Module({
|
|
497
|
+
imports: [
|
|
498
|
+
BetterAuthModule.forRoot({
|
|
499
|
+
config: environment.betterAuth,
|
|
500
|
+
}),
|
|
501
|
+
],
|
|
502
|
+
})
|
|
503
|
+
export class AppModule {}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## REST API Endpoints
|
|
507
|
+
|
|
508
|
+
When enabled, Better-Auth exposes the following endpoints at the configured `basePath` (default: `/iam`):
|
|
509
|
+
|
|
510
|
+
| Endpoint | Method | Description |
|
|
511
|
+
| --------------------- | ------ | ---------------------------- |
|
|
512
|
+
| `/iam/sign-up` | POST | Register new user |
|
|
513
|
+
| `/iam/sign-in` | POST | Sign in with email/password |
|
|
514
|
+
| `/iam/sign-out` | POST | Sign out (invalidate session)|
|
|
515
|
+
| `/iam/session` | GET | Get current session |
|
|
516
|
+
| `/iam/forgot-password`| POST | Request password reset |
|
|
517
|
+
| `/iam/reset-password` | POST | Reset password with token |
|
|
518
|
+
| `/iam/verify-email` | POST | Verify email address |
|
|
519
|
+
|
|
520
|
+
### Social Login Endpoints
|
|
521
|
+
|
|
522
|
+
| Endpoint | Method | Description |
|
|
523
|
+
| -------------------------- | ------ | --------------------- |
|
|
524
|
+
| `/iam/sign-in/social` | POST | Initiate social login |
|
|
525
|
+
| `/iam/callback/:provider` | GET | OAuth callback |
|
|
526
|
+
|
|
527
|
+
### Two-Factor Authentication Endpoints
|
|
528
|
+
|
|
529
|
+
| Endpoint | Method | Description |
|
|
530
|
+
| ------------------------- | ------ | ---------------- |
|
|
531
|
+
| `/iam/two-factor/enable` | POST | Enable 2FA |
|
|
532
|
+
| `/iam/two-factor/disable` | POST | Disable 2FA |
|
|
533
|
+
| `/iam/two-factor/verify` | POST | Verify 2FA code |
|
|
534
|
+
|
|
535
|
+
### Passkey Endpoints
|
|
536
|
+
|
|
537
|
+
| Endpoint | Method | Description |
|
|
538
|
+
| ---------------------------- | ------ | ------------------------- |
|
|
539
|
+
| `/iam/passkey/register` | POST | Register new passkey |
|
|
540
|
+
| `/iam/passkey/authenticate` | POST | Authenticate with passkey |
|
|
541
|
+
|
|
542
|
+
## GraphQL API
|
|
543
|
+
|
|
544
|
+
In addition to REST endpoints, Better-Auth provides GraphQL queries and mutations:
|
|
545
|
+
|
|
546
|
+
### Queries
|
|
547
|
+
|
|
548
|
+
| Query | Arguments | Return Type | Description |
|
|
549
|
+
| ------------------------ | --------- | ---------------------------- | ------------------------------- |
|
|
550
|
+
| `betterAuthEnabled` | - | `Boolean` | Check if Better-Auth is enabled |
|
|
551
|
+
| `betterAuthFeatures` | - | `BetterAuthFeaturesModel` | Get enabled features status |
|
|
552
|
+
| `betterAuthSession` | - | `BetterAuthSessionModel` | Get current session (auth req.) |
|
|
553
|
+
| `betterAuthListPasskeys` | - | `[BetterAuthPasskeyModel]` | List user's passkeys (auth req.)|
|
|
554
|
+
|
|
555
|
+
### Mutations
|
|
556
|
+
|
|
557
|
+
#### Authentication
|
|
558
|
+
|
|
559
|
+
| Mutation | Arguments | Return Type | Description |
|
|
560
|
+
| --------------------- | ---------------------------- | -------------------- | ------------------------- |
|
|
561
|
+
| `betterAuthSignIn` | `email`, `password` | `BetterAuthAuthModel`| Sign in with email/pass |
|
|
562
|
+
| `betterAuthSignUp` | `email`, `password`, `name?` | `BetterAuthAuthModel`| Register new account |
|
|
563
|
+
| `betterAuthSignOut` | - | `Boolean` | Sign out (requires auth) |
|
|
564
|
+
| `betterAuthVerify2FA` | `code` | `BetterAuthAuthModel`| Verify 2FA code |
|
|
565
|
+
|
|
566
|
+
#### 2FA Management (requires authentication)
|
|
567
|
+
|
|
568
|
+
| Mutation | Arguments | Return Type | Description |
|
|
569
|
+
| ------------------------------ | ---------- | ------------------------ | ------------------------------ |
|
|
570
|
+
| `betterAuthEnable2FA` | `password` | `BetterAuth2FASetupModel`| Enable 2FA, get TOTP URI |
|
|
571
|
+
| `betterAuthDisable2FA` | `password` | `Boolean` | Disable 2FA for user |
|
|
572
|
+
| `betterAuthGenerateBackupCodes`| - | `[String]` | Generate new backup codes |
|
|
573
|
+
|
|
574
|
+
#### Passkey Management (requires authentication)
|
|
575
|
+
|
|
576
|
+
| Mutation | Arguments | Return Type | Description |
|
|
577
|
+
| ------------------------------ | ----------- | -------------------------------- | --------------------------- |
|
|
578
|
+
| `betterAuthGetPasskeyChallenge`| - | `BetterAuthPasskeyChallengeModel`| Get WebAuthn challenge |
|
|
579
|
+
| `betterAuthDeletePasskey` | `passkeyId` | `Boolean` | Delete a passkey |
|
|
580
|
+
|
|
581
|
+
### Response Types
|
|
582
|
+
|
|
583
|
+
#### BetterAuthAuthModel
|
|
584
|
+
|
|
585
|
+
```graphql
|
|
586
|
+
type BetterAuthAuthModel {
|
|
587
|
+
success: Boolean!
|
|
588
|
+
requiresTwoFactor: Boolean
|
|
589
|
+
token: String
|
|
590
|
+
user: BetterAuthUserModel
|
|
591
|
+
session: BetterAuthSessionInfoModel
|
|
592
|
+
error: String
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### BetterAuthFeaturesModel
|
|
597
|
+
|
|
598
|
+
```graphql
|
|
599
|
+
type BetterAuthFeaturesModel {
|
|
600
|
+
enabled: Boolean!
|
|
601
|
+
jwt: Boolean!
|
|
602
|
+
twoFactor: Boolean!
|
|
603
|
+
passkey: Boolean!
|
|
604
|
+
legacyPassword: Boolean!
|
|
605
|
+
socialProviders: [String!]!
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
#### BetterAuth2FASetupModel
|
|
610
|
+
|
|
611
|
+
```graphql
|
|
612
|
+
type BetterAuth2FASetupModel {
|
|
613
|
+
success: Boolean!
|
|
614
|
+
totpUri: String
|
|
615
|
+
backupCodes: [String!]
|
|
616
|
+
error: String
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
#### BetterAuthPasskeyModel
|
|
621
|
+
|
|
622
|
+
```graphql
|
|
623
|
+
type BetterAuthPasskeyModel {
|
|
624
|
+
id: String!
|
|
625
|
+
name: String
|
|
626
|
+
credentialId: String!
|
|
627
|
+
createdAt: DateTime!
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### BetterAuthPasskeyChallengeModel
|
|
632
|
+
|
|
633
|
+
```graphql
|
|
634
|
+
type BetterAuthPasskeyChallengeModel {
|
|
635
|
+
success: Boolean!
|
|
636
|
+
challenge: String
|
|
637
|
+
error: String
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Example Usage
|
|
642
|
+
|
|
643
|
+
```graphql
|
|
644
|
+
# Check if Better-Auth is enabled
|
|
645
|
+
query {
|
|
646
|
+
betterAuthEnabled
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
# Get available features
|
|
650
|
+
query {
|
|
651
|
+
betterAuthFeatures {
|
|
652
|
+
enabled
|
|
653
|
+
jwt
|
|
654
|
+
twoFactor
|
|
655
|
+
passkey
|
|
656
|
+
socialProviders
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
# Sign in
|
|
661
|
+
mutation {
|
|
662
|
+
betterAuthSignIn(email: "user@example.com", password: "password123") {
|
|
663
|
+
success
|
|
664
|
+
requiresTwoFactor
|
|
665
|
+
token
|
|
666
|
+
user {
|
|
667
|
+
id
|
|
668
|
+
email
|
|
669
|
+
name
|
|
670
|
+
roles
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
# Sign up
|
|
676
|
+
mutation {
|
|
677
|
+
betterAuthSignUp(
|
|
678
|
+
email: "newuser@example.com"
|
|
679
|
+
password: "securePassword123"
|
|
680
|
+
name: "New User"
|
|
681
|
+
) {
|
|
682
|
+
success
|
|
683
|
+
user {
|
|
684
|
+
id
|
|
685
|
+
email
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
# Verify 2FA (after sign-in with requiresTwoFactor: true)
|
|
691
|
+
mutation {
|
|
692
|
+
betterAuthVerify2FA(code: "123456") {
|
|
693
|
+
success
|
|
694
|
+
token
|
|
695
|
+
user {
|
|
696
|
+
id
|
|
697
|
+
email
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
# Enable 2FA (requires authentication)
|
|
703
|
+
mutation {
|
|
704
|
+
betterAuthEnable2FA(password: "yourPassword") {
|
|
705
|
+
success
|
|
706
|
+
totpUri
|
|
707
|
+
backupCodes
|
|
708
|
+
error
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
# Disable 2FA
|
|
713
|
+
mutation {
|
|
714
|
+
betterAuthDisable2FA(password: "yourPassword")
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
# Generate new backup codes
|
|
718
|
+
mutation {
|
|
719
|
+
betterAuthGenerateBackupCodes
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
# List passkeys
|
|
723
|
+
query {
|
|
724
|
+
betterAuthListPasskeys {
|
|
725
|
+
id
|
|
726
|
+
name
|
|
727
|
+
credentialId
|
|
728
|
+
createdAt
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
# Get passkey registration challenge (for WebAuthn)
|
|
733
|
+
mutation {
|
|
734
|
+
betterAuthGetPasskeyChallenge {
|
|
735
|
+
success
|
|
736
|
+
challenge
|
|
737
|
+
error
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
# Delete a passkey
|
|
742
|
+
mutation {
|
|
743
|
+
betterAuthDeletePasskey(passkeyId: "passkey-id-here")
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
## Using BetterAuthService
|
|
748
|
+
|
|
749
|
+
Inject `BetterAuthService` to access Better-Auth functionality:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
import { BetterAuthService } from '@lenne.tech/nest-server';
|
|
753
|
+
|
|
754
|
+
@Injectable()
|
|
755
|
+
export class MyService {
|
|
756
|
+
constructor(private readonly betterAuthService: BetterAuthService) {}
|
|
757
|
+
|
|
758
|
+
async checkUser(req: Request) {
|
|
759
|
+
// Check if Better-Auth is enabled
|
|
760
|
+
if (!this.betterAuthService.isEnabled()) {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Get current session
|
|
765
|
+
const { session, user } = await this.betterAuthService.getSession(req);
|
|
766
|
+
|
|
767
|
+
if (session) {
|
|
768
|
+
console.log('User:', user.email);
|
|
769
|
+
console.log('Session expires:', session.expiresAt);
|
|
770
|
+
|
|
771
|
+
// Check if session is expiring soon
|
|
772
|
+
if (this.betterAuthService.isSessionExpiringSoon(session)) {
|
|
773
|
+
console.log('Session expiring soon!');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Get remaining time
|
|
777
|
+
const remaining = this.betterAuthService.getSessionTimeRemaining(session);
|
|
778
|
+
console.log(`Session valid for ${remaining} seconds`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return user;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async logout(sessionToken: string) {
|
|
785
|
+
const success = await this.betterAuthService.revokeSession(sessionToken);
|
|
786
|
+
return success;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Available Methods
|
|
792
|
+
|
|
793
|
+
| Method | Description |
|
|
794
|
+
| ----------------------------------- | -------------------------------------------- |
|
|
795
|
+
| `isEnabled()` | Check if Better-Auth is enabled |
|
|
796
|
+
| `getInstance()` | Get the Better-Auth instance |
|
|
797
|
+
| `getApi()` | Get the Better-Auth API |
|
|
798
|
+
| `getConfig()` | Get the current configuration |
|
|
799
|
+
| `isJwtEnabled()` | Check if JWT plugin is enabled |
|
|
800
|
+
| `isTwoFactorEnabled()` | Check if 2FA is enabled |
|
|
801
|
+
| `isPasskeyEnabled()` | Check if Passkey is enabled |
|
|
802
|
+
| `isLegacyPasswordEnabled()` | Check if legacy password handling is enabled |
|
|
803
|
+
| `getEnabledSocialProviders()` | Get list of enabled social providers |
|
|
804
|
+
| `getBasePath()` | Get the base path for endpoints |
|
|
805
|
+
| `getBaseUrl()` | Get the base URL |
|
|
806
|
+
| `getSession(req)` | Get current session from request |
|
|
807
|
+
| `revokeSession(token)` | Revoke a session (logout) |
|
|
808
|
+
| `isSessionExpiringSoon(session, t?)`| Check if session is expiring soon |
|
|
809
|
+
| `getSessionTimeRemaining(session)` | Get remaining session time in seconds |
|
|
810
|
+
|
|
811
|
+
## Security Integration
|
|
812
|
+
|
|
813
|
+
Better-Auth users are automatically integrated with the existing security system:
|
|
814
|
+
|
|
815
|
+
### Role-Based Access Control
|
|
816
|
+
|
|
817
|
+
Better-Auth users work seamlessly with `@Roles()` decorators:
|
|
818
|
+
|
|
819
|
+
```typescript
|
|
820
|
+
@Roles(RoleEnum.ADMIN)
|
|
821
|
+
@Query(() => [User])
|
|
822
|
+
async findAllUsers() {
|
|
823
|
+
// Only accessible by users with ADMIN role
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Special Roles
|
|
828
|
+
|
|
829
|
+
| Role | Description |
|
|
830
|
+
| ------------- | ------------------------------------- |
|
|
831
|
+
| `S_EVERYONE` | Accessible by everyone (no auth req.) |
|
|
832
|
+
| `S_USER` | Any authenticated user |
|
|
833
|
+
| `S_VERIFIED` | Users with verified email |
|
|
834
|
+
| `S_NO_ONE` | Never accessible |
|
|
835
|
+
|
|
836
|
+
### How It Works
|
|
837
|
+
|
|
838
|
+
1. `BetterAuthMiddleware` validates the session on each request
|
|
839
|
+
2. `BetterAuthUserMapper` maps the session user to a user with `hasRole()` capability
|
|
840
|
+
3. The mapped user is set to `req.user` for use with guards and decorators
|
|
841
|
+
4. `RolesGuard` and `@Restricted()` work as expected
|
|
842
|
+
|
|
843
|
+
## User Mapping
|
|
844
|
+
|
|
845
|
+
The `BetterAuthUserMapper` handles the conversion between Better-Auth sessions and application users:
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
import { BetterAuthUserMapper } from '@lenne.tech/nest-server';
|
|
849
|
+
|
|
850
|
+
@Injectable()
|
|
851
|
+
export class MyService {
|
|
852
|
+
constructor(private readonly userMapper: BetterAuthUserMapper) {}
|
|
853
|
+
|
|
854
|
+
async mapUser(sessionUser: BetterAuthSessionUser) {
|
|
855
|
+
// Maps session user to application user with roles
|
|
856
|
+
const user = await this.userMapper.mapSessionUser(sessionUser);
|
|
857
|
+
|
|
858
|
+
if (user) {
|
|
859
|
+
// Check roles
|
|
860
|
+
user.hasRole(RoleEnum.ADMIN); // true/false
|
|
861
|
+
user.hasRole([RoleEnum.ADMIN, 'editor']); // true if any match
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async linkUser(sessionUser: BetterAuthSessionUser) {
|
|
866
|
+
// Links Better-Auth user with application database
|
|
867
|
+
const dbUser = await this.userMapper.linkOrCreateUser(sessionUser);
|
|
868
|
+
// Creates new user or links existing one via iamId
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Mapped User Properties
|
|
874
|
+
|
|
875
|
+
| Property | Type | Description |
|
|
876
|
+
| ----------------------------- | ---------- | ----------------------------------------------------- |
|
|
877
|
+
| `id` | string | User ID (from database or Better-Auth ID as fallback) |
|
|
878
|
+
| `iamId` | string | IAM provider user ID (e.g., Better-Auth) |
|
|
879
|
+
| `email` | string | User email |
|
|
880
|
+
| `name` | string | User display name |
|
|
881
|
+
| `roles` | string[] | User roles from database |
|
|
882
|
+
| `verified` | boolean | Whether user is verified |
|
|
883
|
+
| `emailVerified` | boolean | Better-Auth email verification status |
|
|
884
|
+
| `hasRole(roles)` | function | Check if user has any of the specified roles |
|
|
885
|
+
| `_authenticatedViaBetterAuth` | true | Marker for Better-Auth authenticated users |
|
|
886
|
+
|
|
887
|
+
## Parallel Operation with Legacy Auth
|
|
888
|
+
|
|
889
|
+
Better-Auth runs **parallel to Legacy JWT authentication** without conflicts. Both systems are fully compatible because they use the same password hashing (bcrypt) and share the same users collection.
|
|
890
|
+
|
|
891
|
+
### How It Works
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
// Legacy Auth - continues to work as before
|
|
895
|
+
const legacyToken = await authService.signIn({ email, password });
|
|
896
|
+
|
|
897
|
+
// Better-Auth - works with the same users
|
|
898
|
+
// POST /iam/sign-in { email, password }
|
|
899
|
+
|
|
900
|
+
// Both share the same users collection and bcrypt passwords
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### Key Points
|
|
904
|
+
|
|
905
|
+
1. **Shared Users Collection**: Both systems use the same `users` MongoDB collection
|
|
906
|
+
2. **bcrypt Compatibility**: Both systems use bcrypt - passwords work with either system
|
|
907
|
+
3. **User Linking**: When a user logs in via Better-Auth, `iamId` is set to link them
|
|
908
|
+
4. **Role Preservation**: User roles work with both systems
|
|
909
|
+
|
|
910
|
+
### Compatibility Matrix
|
|
911
|
+
|
|
912
|
+
| Scenario | Result |
|
|
913
|
+
| ---------------------------------- | ---------------------------------- |
|
|
914
|
+
| Legacy user → Legacy login | Works |
|
|
915
|
+
| Legacy user → Better-Auth login | Works (bcrypt compatible) |
|
|
916
|
+
| Better-Auth user → Better-Auth | Works |
|
|
917
|
+
| Better-Auth user → Legacy login | Works (password field preserved) |
|
|
918
|
+
| Social-only user → Legacy login | Fails (no password field) |
|
|
919
|
+
|
|
920
|
+
### User Database Fields
|
|
921
|
+
|
|
922
|
+
| Field | Purpose |
|
|
923
|
+
| ---------- | ------------------------------------------------- |
|
|
924
|
+
| `password` | Password hash (bcrypt) - used by both systems |
|
|
925
|
+
| `iamId` | IAM provider user ID (set on first Better-Auth login) |
|
|
926
|
+
|
|
927
|
+
**No migration is required** - users can authenticate with either system immediately.
|
|
928
|
+
|
|
929
|
+
## Testing
|
|
930
|
+
|
|
931
|
+
The module provides a `reset()` method for testing:
|
|
932
|
+
|
|
933
|
+
```typescript
|
|
934
|
+
import { BetterAuthModule } from '@lenne.tech/nest-server';
|
|
935
|
+
|
|
936
|
+
describe('My Tests', () => {
|
|
937
|
+
beforeEach(() => {
|
|
938
|
+
// Reset static state between tests
|
|
939
|
+
BetterAuthModule.reset();
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
afterAll(() => {
|
|
943
|
+
BetterAuthModule.reset();
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
## Troubleshooting
|
|
949
|
+
|
|
950
|
+
### Better-Auth endpoints return 404
|
|
951
|
+
|
|
952
|
+
- Ensure `betterAuth.enabled` is NOT explicitly set to `false`
|
|
953
|
+
- Check that a valid secret is available
|
|
954
|
+
- Verify MongoDB connection is established
|
|
955
|
+
- Check logs for initialization errors during startup
|
|
956
|
+
|
|
957
|
+
### Session not being set on requests
|
|
958
|
+
|
|
959
|
+
- Check that `BetterAuthMiddleware` is being applied (automatic with module)
|
|
960
|
+
- Verify cookies are being sent with requests
|
|
961
|
+
- Check browser developer tools for session cookies
|
|
962
|
+
|
|
963
|
+
### Social login not working
|
|
964
|
+
|
|
965
|
+
- Verify `clientId` and `clientSecret` are correct
|
|
966
|
+
- Check that redirect URLs are configured in provider settings
|
|
967
|
+
- Ensure `trustedOrigins` includes your application URL
|
|
968
|
+
|
|
969
|
+
### 2FA/Passkey not working
|
|
970
|
+
|
|
971
|
+
- Ensure the respective plugin is enabled in configuration
|
|
972
|
+
- For Passkey, verify `rpId` matches your domain
|
|
973
|
+
- Check browser console for WebAuthn errors
|
|
974
|
+
|
|
975
|
+
## Rate Limiting
|
|
976
|
+
|
|
977
|
+
The Better-Auth module includes built-in rate limiting to protect against brute-force attacks.
|
|
978
|
+
|
|
979
|
+
### Configuration
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
const config = {
|
|
983
|
+
betterAuth: {
|
|
984
|
+
rateLimit: {
|
|
985
|
+
enabled: true,
|
|
986
|
+
max: 10,
|
|
987
|
+
windowSeconds: 60,
|
|
988
|
+
message: 'Too many requests, please try again later.',
|
|
989
|
+
strictEndpoints: ['/sign-in', '/sign-up', '/forgot-password', '/reset-password'],
|
|
990
|
+
skipEndpoints: ['/session', '/callback'],
|
|
991
|
+
},
|
|
992
|
+
},
|
|
993
|
+
};
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
### Configuration Options
|
|
997
|
+
|
|
998
|
+
| Option | Type | Default | Description |
|
|
999
|
+
| ----------------- | -------- | -------------------------- | ------------------------------------- |
|
|
1000
|
+
| `enabled` | boolean | `false` | Enable/disable rate limiting |
|
|
1001
|
+
| `max` | number | `10` | Maximum requests per time window |
|
|
1002
|
+
| `windowSeconds` | number | `60` | Time window in seconds |
|
|
1003
|
+
| `message` | string | `'Too many requests...'` | Error message when limit exceeded |
|
|
1004
|
+
| `strictEndpoints` | string[] | See below | Endpoints with half the normal limit |
|
|
1005
|
+
| `skipEndpoints` | string[] | See below | Endpoints that skip rate limiting |
|
|
1006
|
+
|
|
1007
|
+
### Default Strict Endpoints
|
|
1008
|
+
|
|
1009
|
+
Strict endpoints receive half the configured `max` limit to provide extra protection:
|
|
1010
|
+
|
|
1011
|
+
- `/sign-in` - Login attempts
|
|
1012
|
+
- `/sign-up` - Registration attempts
|
|
1013
|
+
- `/forgot-password` - Password reset requests
|
|
1014
|
+
- `/reset-password` - Password reset submissions
|
|
1015
|
+
|
|
1016
|
+
### Default Skip Endpoints
|
|
1017
|
+
|
|
1018
|
+
These endpoints bypass rate limiting entirely:
|
|
1019
|
+
|
|
1020
|
+
- `/session` - Session checks (frequent client-side calls)
|
|
1021
|
+
- `/callback` - OAuth callbacks
|
|
1022
|
+
|
|
1023
|
+
### Response Headers
|
|
1024
|
+
|
|
1025
|
+
When rate limiting is enabled, the following headers are added to responses:
|
|
1026
|
+
|
|
1027
|
+
| Header | Description |
|
|
1028
|
+
| ---------------------- | ---------------------------------------- |
|
|
1029
|
+
| `X-RateLimit-Limit` | Maximum requests allowed in the window |
|
|
1030
|
+
| `X-RateLimit-Remaining`| Remaining requests in current window |
|
|
1031
|
+
| `X-RateLimit-Reset` | Seconds until the rate limit resets |
|
|
1032
|
+
| `Retry-After` | (429 only) Seconds to wait before retry |
|
|
1033
|
+
|
|
1034
|
+
### Rate Limit Exceeded Response
|
|
1035
|
+
|
|
1036
|
+
When the rate limit is exceeded, a `429 Too Many Requests` response is returned:
|
|
1037
|
+
|
|
1038
|
+
```json
|
|
1039
|
+
{
|
|
1040
|
+
"statusCode": 429,
|
|
1041
|
+
"error": "Too Many Requests",
|
|
1042
|
+
"message": "Too many requests, please try again later.",
|
|
1043
|
+
"retryAfter": 45
|
|
1044
|
+
}
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Using BetterAuthRateLimiter Programmatically
|
|
1048
|
+
|
|
1049
|
+
```typescript
|
|
1050
|
+
import { BetterAuthRateLimiter } from '@lenne.tech/nest-server';
|
|
1051
|
+
|
|
1052
|
+
@Injectable()
|
|
1053
|
+
export class MyService {
|
|
1054
|
+
constructor(private readonly rateLimiter: BetterAuthRateLimiter) {}
|
|
1055
|
+
|
|
1056
|
+
checkCustomLimit(ip: string) {
|
|
1057
|
+
// Check rate limit for custom endpoint
|
|
1058
|
+
const result = this.rateLimiter.check(ip, '/custom-endpoint');
|
|
1059
|
+
|
|
1060
|
+
if (!result.allowed) {
|
|
1061
|
+
console.log(`Rate limit exceeded. Retry in ${result.resetIn} seconds`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
resetUserLimit(ip: string) {
|
|
1066
|
+
// Reset rate limit for specific IP (e.g., after successful captcha)
|
|
1067
|
+
this.rateLimiter.reset(ip);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
getStats() {
|
|
1071
|
+
// Get rate limiter statistics
|
|
1072
|
+
return this.rateLimiter.getStats();
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
### Production Recommendations
|
|
1078
|
+
|
|
1079
|
+
For production environments, consider:
|
|
1080
|
+
|
|
1081
|
+
1. **Enable rate limiting** - Always enable in production
|
|
1082
|
+
2. **Lower limits** - Use stricter limits (e.g., `max: 5`) for production
|
|
1083
|
+
3. **Environment variables** - Configure via environment:
|
|
1084
|
+
|
|
1085
|
+
```typescript
|
|
1086
|
+
const config = {
|
|
1087
|
+
rateLimit: {
|
|
1088
|
+
enabled: process.env.RATE_LIMIT_ENABLED !== 'false',
|
|
1089
|
+
max: parseInt(process.env.RATE_LIMIT_MAX || '10', 10),
|
|
1090
|
+
windowSeconds: parseInt(process.env.RATE_LIMIT_WINDOW_SECONDS || '60', 10),
|
|
1091
|
+
},
|
|
1092
|
+
};
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
4. **Monitor rate limit events** - The service logs warnings when limits are exceeded
|
|
1096
|
+
5. **Consider Redis** - For multi-instance deployments, implement Redis-based rate limiting
|