@flink-app/generic-auth-plugin 0.12.1-alpha.30 → 0.12.1-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -1,338 +1,422 @@
1
- # Generic Auth Plugin Docs
1
+ # Generic Auth Plugin
2
2
 
3
- A FLINK plugin that provides a generic and easy to use user system.
3
+ A comprehensive Flink plugin that provides a complete user authentication system with user management, password reset, SMS authentication, and push notification token management. This plugin builds on top of the JWT Auth Plugin to provide ready-to-use authentication endpoints and functions.
4
4
 
5
- This plugin is dependent on other flink plugins:
5
+ ## Features
6
6
 
7
- - [jwt-auth-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/jwt-auth-plugin)
8
- - [email-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/email-plugin)
9
-
10
- This plugin enables the following functionalities:
11
-
12
- - User creation
13
- - User login
14
- - Change user password
15
- - Password reset routine (with email)
16
- - User profile
7
+ - User registration and login
8
+ - Password-based and SMS-based authentication
9
+ - BankID authentication support
10
+ - Password reset flow with email verification
11
+ - User profile management
17
12
  - Push notification token management
13
+ - Customizable password hashing
14
+ - Pre-built API endpoints (optional)
15
+ - Management API integration for admin interfaces
16
+ - Lifecycle hooks (onSuccessfulLogin, onUserCreated)
18
17
 
19
- Plugin can both be used by accessing core functions by calling them directly from your code, or by using the embedded API endpoints.
18
+ ## Dependencies
20
19
 
21
- ## Installation
20
+ This plugin requires:
21
+ - [@flink-app/jwt-auth-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/jwt-auth-plugin) - For JWT token management
22
+ - [@flink-app/email-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/email-plugin) - For password reset emails
23
+ - [@flink-app/sms-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/sms-plugin) - For SMS authentication (optional)
22
24
 
23
- Install plugin to your flink app project:
25
+ ## Installation
24
26
 
27
+ ```bash
28
+ npm install @flink-app/generic-auth-plugin @flink-app/jwt-auth-plugin @flink-app/email-plugin
25
29
  ```
26
- npm i -S @flink-app/generic-auth-plugin
30
+
31
+ For SMS authentication:
32
+ ```bash
33
+ npm install @flink-app/sms-plugin
27
34
  ```
28
35
 
29
36
  ## Setup
30
37
 
31
- The setup of this plugin contains a few different steps. Please follow each steps before trying to use the plugin.
38
+ ### Step 1: Create User Repository
32
39
 
33
- ### Step 1 - Adding a user repo
40
+ Create a user repository in your project:
34
41
 
35
- The plugin needs a repo to store the users in.
36
-
37
- Add this repo to your project by first adding `src/repos/UserRepo.ts`:
38
-
39
- ```
42
+ **`src/repos/UserRepo.ts`:**
43
+ ```typescript
40
44
  import { FlinkRepo } from "@flink-app/flink";
45
+ import { User } from "@flink-app/generic-auth-plugin";
41
46
  import { Ctx } from "../Ctx";
42
- import { User } from "@flink-app/generic-auth-plugin"
47
+
43
48
  class UserRepo extends FlinkRepo<Ctx, User> {}
44
49
 
45
50
  export default UserRepo;
46
51
  ```
47
52
 
48
- And add the repo to your ApplicationContext `src/Ctx.ts`:
49
-
50
- ```
53
+ **`src/Ctx.ts`:**
54
+ ```typescript
51
55
  import { FlinkContext } from "@flink-app/flink";
52
56
  import UserRepo from "./repos/UserRepo";
53
57
 
54
58
  export interface Ctx extends FlinkContext {
55
59
  repos: {
56
- userRepo : UserRepo
60
+ userRepo: UserRepo;
57
61
  };
58
62
  }
59
63
  ```
60
64
 
61
- ### Step 2 - Configure auth to your app
65
+ ### Step 2: Configure Authentication
62
66
 
63
- Create a configured instance of JwtAuthPlugin by using the helper function `getJtwTokenPlugin()` and configure auth property of the FlinkApp in `index.ts`
64
-
65
- ```
67
+ **`index.ts`:**
68
+ ```typescript
66
69
  import { FlinkApp } from "@flink-app/flink";
70
+ import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
71
+ import { genericAuthPlugin } from "@flink-app/generic-auth-plugin";
72
+ import { emailPlugin } from "@flink-app/email-plugin";
67
73
  import { Ctx } from "./Ctx";
68
74
 
69
- import { getJtwTokenPlugin } from "@flink-app/generic-auth-plugin"
70
- const authPlugin = getJtwTokenPlugin("secret");
71
-
72
75
  function start() {
73
- var app = new FlinkApp<Ctx>({
74
- name: "My flink app",
76
+ const app = new FlinkApp<Ctx>({
77
+ name: "My Flink App",
75
78
  debug: true,
76
- auth : authPlugin,
79
+ auth: jwtAuthPlugin({
80
+ secret: process.env.JWT_SECRET!,
81
+ getUser: async (tokenData) => {
82
+ const user = await app.ctx.repos.userRepo.findById(tokenData.userId);
83
+ if (!user) throw new Error("User not found");
84
+ return {
85
+ id: user._id,
86
+ username: user.username,
87
+ roles: user.roles,
88
+ };
89
+ },
90
+ rolePermissions: {
91
+ admin: ["read", "write", "delete", "manage_users"],
92
+ user: ["read", "write"],
93
+ },
94
+ passwordPolicy: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/,
95
+ }),
77
96
  db: {
78
- uri: "mongodb://localhost:27017/my-flink-app",
97
+ uri: process.env.MONGODB_URI!,
79
98
  },
80
99
  plugins: [
81
-
100
+ emailPlugin({
101
+ // Email configuration for password resets
102
+ provider: "sendgrid",
103
+ apiKey: process.env.SENDGRID_API_KEY!,
104
+ }),
105
+ genericAuthPlugin({
106
+ repoName: "userRepo",
107
+ enableRoutes: true, // Enable built-in API endpoints
108
+ enablePasswordReset: true,
109
+ enablePushNotificationTokens: true,
110
+ usernameFormat: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, // Email format
111
+ passwordResetSettings: {
112
+ email: {
113
+ from_address: "noreply@example.com",
114
+ subject: "Password Reset Code",
115
+ html: "Your password reset code is: {{code}}",
116
+ },
117
+ code: {
118
+ numberOfDigits: 6,
119
+ lifeTime: "1h", // Uses ms package format
120
+ jwtSecret: process.env.PASSWORD_RESET_SECRET!,
121
+ },
122
+ },
123
+ }),
82
124
  ],
83
- })
125
+ });
126
+
84
127
  app.start();
85
128
  }
86
129
 
87
130
  start();
88
131
  ```
89
132
 
90
- Se details and configuration for getJtwTokenPlugin() below.
91
-
92
- ### Step 3 - Initiate the plugin
93
-
133
+ ## Configuration Options
134
+
135
+ ### `GenericAuthPluginOptions`
136
+
137
+ | Option | Type | Required | Default | Description |
138
+ |--------|------|----------|---------|-------------|
139
+ | `repoName` | `string` | Yes | - | Name of the user repository in your context |
140
+ | `enableRoutes` | `boolean` | No | `true` | Enable built-in HTTP endpoints |
141
+ | `enablePasswordReset` | `boolean` | No | `false` | Enable password reset functionality |
142
+ | `passwordResetReusableTokens` | `boolean` | No | `false` | Allow password reset tokens to be reused |
143
+ | `enablePushNotificationTokens` | `boolean` | No | `false` | Enable push notification token management |
144
+ | `enableUserCreation` | `boolean` | No | `true` | Enable user creation endpoint |
145
+ | `enableProfileUpdate` | `boolean` | No | `true` | Enable profile update endpoint |
146
+ | `enablePasswordUpdate` | `boolean` | No | `true` | Enable password update endpoint |
147
+ | `enableUserLogin` | `boolean` | No | `true` | Enable user login endpoint |
148
+ | `passwordResetSettings` | `UserPasswordResetSettings` | No | - | Password reset configuration |
149
+ | `baseUrl` | `string` | No | - | Base URL for email links |
150
+ | `pluginId` | `string` | No | `"genericAuthPlugin"` | Plugin identifier |
151
+ | `usernameFormat` | `RegExp` | No | `/.{1,}$/` | Regex to validate username format |
152
+ | `sms` | `GenericAuthsmsOptions` | No | - | SMS authentication configuration |
153
+ | `createPasswordHashAndSaltMethod` | `Function` | No | - | Custom password hashing function |
154
+ | `validatePasswordMethod` | `Function` | No | - | Custom password validation function |
155
+ | `onSuccessfulLogin` | `Function` | No | - | Callback after successful login |
156
+ | `onUserCreated` | `Function` | No | - | Callback after user creation |
157
+ | `deregisterOtherDevices` | `boolean` | No | `false` | Deregister other devices when new device is registered |
158
+ | `allowMultipleDevices` | `boolean` | No | `true` | Allow multiple devices with same deviceId |
159
+
160
+ ### Password Reset Settings
161
+
162
+ ```typescript
163
+ interface UserPasswordResetSettings {
164
+ email: {
165
+ from_address: string;
166
+ subject: string; // Handlebars template
167
+ html: string; // Handlebars template
168
+ };
169
+ code: {
170
+ numberOfDigits: number; // Length of reset code
171
+ lifeTime: string; // e.g., "1h", "30m", "1d" (ms package format)
172
+ jwtSecret: string; // Secret for reset token JWT
173
+ };
174
+ }
94
175
  ```
95
- import { FlinkApp } from "@flink-app/flink";
96
- import { Ctx } from "./Ctx";
97
176
 
98
- import { getJtwTokenPlugin, genericAuthPlugin } from "@flink-app/generic-auth-plugin"
177
+ **Handlebars Context:**
178
+ - `{{username}}` - User's username
179
+ - `{{code}}` - Password reset code
180
+ - `{{profile}}` - User profile object
99
181
 
100
- const authPlugin = getJtwTokenPlugin("secret");
182
+ ### SMS Authentication Options
101
183
 
102
- function start() {
103
- var app = new FlinkApp<Ctx>({
104
- name: "My flink app",
105
- debug: true,
106
- auth : authPlugin,
107
- db: {
108
- uri: "mongodb://localhost:27017/my-flink-app",
109
- },
110
- plugins: [
111
- genericAuthPlugin({
112
- repoName : "userRepo",
113
- usernameFormat : /.{1,}$/, //Regex to validate username
114
- enableRoutes : true, //Set true to enable API-endpoints
115
- enablePasswordReset : true,
116
- enablePushNotificationTokens : true,
117
- passwordResetSettings : {
118
- email : {
119
- from_address : "from@host.xxx",
120
- subject : "Your password reset code",
121
- html : "To reset your password use the code {{code}}",
122
- },
123
- code : {
124
- numberOfDigits : 8,
125
- lifeTime : "1h", //npm package
126
- jwtSecret : "Secret used by password reset"
127
- }
128
- }
129
- }),
130
- ],
131
- })
132
- app.start();
184
+ ```typescript
185
+ interface GenericAuthsmsOptions {
186
+ smsClient: smsClient; // SMS client instance
187
+ smsFrom: string; // Sender name/number
188
+ smsMessage: string; // Message template with {{code}}
189
+ jwtToken: string; // Secret for SMS JWT tokens
190
+ codeType: "numeric" | "alphanumeric";
191
+ codeLength: number; // Length of SMS code
133
192
  }
134
- start();
135
193
  ```
136
194
 
137
- | Parameter | Description |
138
- | -------------- | ------------------------------------------------------------------------------------------------ |
139
- | lifeTime | expressed in seconds or a string describing a time span [zeit/ms](https://github.com/vercel/ms). |
140
- | subject / html | expressed in [handlebars](https://handlebarsjs.com/) |
195
+ ## Context API
141
196
 
142
- #### Handlebars context
197
+ The plugin exposes the following functions via `ctx.plugins.genericAuthPlugin`:
143
198
 
144
- Context used when processing the handlebars template for subject and html is:
199
+ ### `loginUser()`
145
200
 
146
- ```
147
- {
148
- username, //Username of user
149
- code, //The code for the password reset
150
- profile, //Profile of the user
151
- }
201
+ Authenticate a user with username and password or initiate SMS authentication.
202
+
203
+ ```typescript
204
+ const result = await ctx.plugins.genericAuthPlugin.loginUser(
205
+ repo,
206
+ auth,
207
+ username,
208
+ password,
209
+ validatePasswordMethod?,
210
+ smsOptions?,
211
+ onSuccessfulLogin?,
212
+ req?
213
+ );
152
214
  ```
153
215
 
154
- ### getJtwTokenPlugin()
216
+ **Returns:** `UserLoginRes`
155
217
 
156
- The function have the following definition:
218
+ ### `loginByToken()`
157
219
 
158
- ```
159
- getJtwTokenPlugin(secret: string, rolePermissions?: { [role: string]: string[]; } | undefined, passwordPolicy?: RegExp | undefined): JwtAuthPlugin
220
+ Complete SMS authentication using the validation token and code.
221
+
222
+ ```typescript
223
+ const result = await ctx.plugins.genericAuthPlugin.loginByToken(
224
+ repo,
225
+ auth,
226
+ token,
227
+ code,
228
+ jwtSecret
229
+ );
160
230
  ```
161
231
 
162
- | Parameter | Description |
163
- | --------------- | --------------------------------------------------------------- |
164
- | secret | A secret string that will be used to encrypt the jwt-token |
165
- | rolePermissions | An object with roles as key and arrays of permissions as values |
166
- | passwordPolicy | Regex used to validate password |
232
+ **Returns:** `UserLoginRes`
167
233
 
168
- #### rolePermissions
234
+ ### `createUser()`
169
235
 
170
- The rolePermissions parameter specifies which roles have which permissions. Example:
236
+ Create a new user with password, SMS, or BankID authentication.
171
237
 
172
- ```
173
- {
174
- "normal_user" : ["read", "list"],
175
- "admin" : ["read", "list", "write"]
176
- }
238
+ ```typescript
239
+ const result = await ctx.plugins.genericAuthPlugin.createUser(
240
+ repo,
241
+ auth,
242
+ username,
243
+ password,
244
+ authentificationMethod, // "password" | "sms" | "bankid"
245
+ roles,
246
+ profile,
247
+ createPasswordHashAndSaltMethod?,
248
+ onUserCreated?,
249
+ personalNumber?
250
+ );
177
251
  ```
178
252
 
179
- In this plugin the role `user` with the permission `authenticated` is added automatically. This permission is used to require authenticated user to access a route.
253
+ **Returns:** `UserCreateRes`
180
254
 
181
- ## Making authenticated requests
255
+ ### `changePassword()`
182
256
 
183
- After logging in by calling `user/login` any subsequent calls should contain the Bearer Authentification token header like this:
257
+ Change a user's password.
184
258
 
185
- ```
186
- Authorization: Bearer <token>
259
+ ```typescript
260
+ const result = await ctx.plugins.genericAuthPlugin.changePassword(
261
+ repo,
262
+ auth,
263
+ userId,
264
+ newPassword,
265
+ createPasswordHashAndSaltMethod?
266
+ );
187
267
  ```
188
268
 
189
- ## Limit access to your own handlers
269
+ **Returns:** `UserPasswordChangeRes`
190
270
 
191
- To limit the access to your own handler, specify the "permission" property of the RouteProps of your handler.
271
+ ### `passwordResetStart()`
192
272
 
193
- To limit access to your handler to any user, modify the Route like this:
273
+ Initiate password reset process and send email with code.
194
274
 
195
- ```
196
- export const Route: RouteProps = {
197
- path: "/sample/url",
198
- permission : "authenticated"
199
- };
275
+ ```typescript
276
+ const result = await ctx.plugins.genericAuthPlugin.passwordResetStart(
277
+ repo,
278
+ auth,
279
+ jwtSecret,
280
+ username,
281
+ numberOfDigits?,
282
+ lifeTime?,
283
+ passwordResetReusableTokens?
284
+ );
200
285
  ```
201
286
 
202
- To limit access to your handler to a specific permission, specify that permission to the Route like this:
287
+ **Returns:** `UserPasswordResetStartRes`
203
288
 
204
- ```
205
- export const Route: RouteProps = {
206
- path: "/sample/url",
207
- permission : "my_permission"
208
- };
289
+ ### `passwordResetComplete()`
290
+
291
+ Complete password reset with token, code, and new password.
292
+
293
+ ```typescript
294
+ const result = await ctx.plugins.genericAuthPlugin.passwordResetComplete(
295
+ repo,
296
+ auth,
297
+ jwtSecret,
298
+ passwordResetToken,
299
+ code,
300
+ newPassword,
301
+ createPasswordHashAndSaltMethod?,
302
+ passwordResetReusableTokens?
303
+ );
209
304
  ```
210
305
 
211
- ## Using API-endpoints
306
+ **Returns:** `UserPasswordResetCompleteRes`
212
307
 
213
- ### POST /user/create
308
+ ## Built-in API Endpoints
214
309
 
215
- Creates a new user.
310
+ When `enableRoutes: true` (default), the following endpoints are automatically registered:
216
311
 
217
- #### Request data:
312
+ ### POST /user/create
218
313
 
219
- ```
314
+ Create a new user account.
315
+
316
+ **Request:**
317
+ ```json
220
318
  {
221
- "username" : "demo@test.com",
222
- "password" : "12345678",
223
- "profile" : {
224
- "age" : 20
319
+ "username": "user@example.com",
320
+ "password": "mypassword123",
321
+ "authentificationMethod": "password",
322
+ "profile": {
323
+ "name": "John Doe",
324
+ "age": 30
225
325
  }
226
326
  }
227
327
  ```
228
328
 
229
- #### Response example
230
-
231
- ```
329
+ **Response:**
330
+ ```json
232
331
  {
233
332
  "data": {
234
- "status": "success"
333
+ "status": "success",
235
334
  "user": {
236
- "_id": "id...",
237
- "token": "token...",
238
- "username": "demo@test.com"
335
+ "_id": "507f1f77bcf86cd799439011",
336
+ "username": "user@example.com",
337
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
239
338
  }
240
339
  }
241
340
  }
242
341
  ```
243
342
 
244
- #### Errors
245
-
246
- | Code | Description |
247
- | ------------- | ------------------------------------------------- |
248
- | error | Internal unknown error |
249
- | userExists | User already exists |
250
- | passwordError | Password not accepted / not meeting requirements |
251
- | usernameError |  Username not accepted / not meeting requirements |
343
+ **Error Codes:**
344
+ - `userExists` - Username already taken
345
+ - `passwordError` - Password doesn't meet requirements
346
+ - `usernameError` - Username doesn't meet format requirements
252
347
 
253
348
  ### POST /user/login
254
349
 
255
- Logs a user in.
256
-
257
- #### Request data:
350
+ Login with username and password.
258
351
 
259
- ```
352
+ **Request:**
353
+ ```json
260
354
  {
261
- "username" : "demo@test.com",
262
- "password" : "12345678"
355
+ "username": "user@example.com",
356
+ "password": "mypassword123"
263
357
  }
264
358
  ```
265
359
 
266
- #### Response example
267
-
268
- ```
360
+ **Response:**
361
+ ```json
269
362
  {
270
363
  "data": {
271
- "status": "success"
364
+ "status": "success",
272
365
  "user": {
273
- "_id": "id...",
274
- "username": "demo@test.com",
275
- "token": "token...",
366
+ "_id": "507f1f77bcf86cd799439011",
367
+ "username": "user@example.com",
368
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
276
369
  "profile": {
277
- "age": 20
370
+ "name": "John Doe",
371
+ "age": 30
278
372
  }
279
373
  }
280
374
  }
281
375
  }
282
376
  ```
283
377
 
284
- #### Errors
285
-
286
- | Code | Description |
287
- | ------ | ---------------------------- |
288
- | failed | Invalid username or password |
378
+ **Error Codes:**
379
+ - `failed` - Invalid username or password
289
380
 
290
381
  ### POST /user/password/reset
291
382
 
292
- Initiates a password reset. Username must be in form of an e-mail for the password reset to work.
293
-
294
- #### Request data:
383
+ Initiate password reset (sends email with code).
295
384
 
296
- ```
385
+ **Request:**
386
+ ```json
297
387
  {
298
- "username" : "demo@test.com"
388
+ "username": "user@example.com"
299
389
  }
300
390
  ```
301
391
 
302
- #### Response example
303
-
304
- ```
392
+ **Response:**
393
+ ```json
305
394
  {
306
395
  "data": {
307
- "status": "success"
308
- "passwordResetToken": "token to use later"
396
+ "status": "success",
397
+ "passwordResetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
309
398
  }
310
399
  }
311
400
  ```
312
401
 
313
- #### Errors
314
-
315
- | Code | Description |
316
- | ------------ | -------------- |
317
- | userNotFound | User not found |
402
+ **Error Codes:**
403
+ - `userNotFound` - User doesn't exist
318
404
 
319
405
  ### POST /user/password/reset/complete
320
406
 
321
- Completes a password reset by supplying the passwordRestToken received from step 1, the code from the email sent to the user and the new password.
407
+ Complete password reset with code from email.
322
408
 
323
- #### Request data:
324
-
325
- ```
409
+ **Request:**
410
+ ```json
326
411
  {
327
- "passwordResetToken" : "token to use later",
328
- "code" : "12345678",
329
- "password" : "new password"
412
+ "passwordResetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
413
+ "code": "123456",
414
+ "password": "mynewpassword123"
330
415
  }
331
416
  ```
332
417
 
333
- #### Response example
334
-
335
- ```
418
+ **Response:**
419
+ ```json
336
420
  {
337
421
  "data": {
338
422
  "status": "success"
@@ -340,29 +424,26 @@ Completes a password reset by supplying the passwordRestToken received from step
340
424
  }
341
425
  ```
342
426
 
343
- #### Errors
344
-
345
- | Code | Description |
346
- | ------------- | -------------------------------------------------- |
347
- | invalidCode | Invalid validation code |
348
- | passwordError | Password not accepted / does not meet requirements |
349
- | userNotFound | User not found |
427
+ **Error Codes:**
428
+ - `invalidCode` - Code is wrong or expired
429
+ - `passwordError` - Password doesn't meet requirements
430
+ - `userNotFound` - User not found
350
431
 
351
432
  ### PUT /user/password
352
433
 
353
- Updates password of the current user. Request needs authentication.
434
+ Change password for authenticated user.
354
435
 
355
- #### Request data:
436
+ **Authentication:** Required
356
437
 
357
- ```
438
+ **Request:**
439
+ ```json
358
440
  {
359
- "password" : "12345678"
441
+ "password": "mynewpassword123"
360
442
  }
361
443
  ```
362
444
 
363
- #### Response example
364
-
365
- ```
445
+ **Response:**
446
+ ```json
366
447
  {
367
448
  "data": {
368
449
  "status": "success"
@@ -370,46 +451,47 @@ Updates password of the current user. Request needs authentication.
370
451
  }
371
452
  ```
372
453
 
373
- #### Errors
374
-
375
- | Code | Description |
376
- | ------------- | -------------------------------------------------- |
377
- | failed | Internal unknown error |
378
- | passwordError | Password not accepted / does not meet requirements |
454
+ **Error Codes:**
455
+ - `passwordError` - Password doesn't meet requirements
456
+ - `failed` - Internal error
379
457
 
380
458
  ### GET /user/profile
381
459
 
382
- Gets the user profile of the current user. Request needs authentication.
460
+ Get current user's profile.
383
461
 
384
- #### Response example
462
+ **Authentication:** Required
385
463
 
386
- ```
464
+ **Response:**
465
+ ```json
387
466
  {
388
467
  "data": {
389
- "age": 20
468
+ "name": "John Doe",
469
+ "age": 30
390
470
  }
391
471
  }
392
472
  ```
393
473
 
394
474
  ### PUT /user/profile
395
475
 
396
- Updates profile of the current user. Request needs authentication.
476
+ Update current user's profile.
397
477
 
398
- #### Request data:
478
+ **Authentication:** Required
399
479
 
400
- ```
480
+ **Request:**
481
+ ```json
401
482
  {
402
- "age" : "21",
403
- "city" : "Stockholm"
483
+ "name": "Jane Doe",
484
+ "age": 31,
485
+ "city": "Stockholm"
404
486
  }
405
487
  ```
406
488
 
407
- #### Response example
408
-
409
- ```
489
+ **Response:**
490
+ ```json
410
491
  {
411
492
  "data": {
412
- "age": "21",
493
+ "name": "Jane Doe",
494
+ "age": 31,
413
495
  "city": "Stockholm"
414
496
  }
415
497
  }
@@ -417,20 +499,20 @@ Updates profile of the current user. Request needs authentication.
417
499
 
418
500
  ### POST /user/push
419
501
 
420
- Adds a push notification token to the user. Request needs authentication.
502
+ Register push notification token.
421
503
 
422
- #### Request data:
504
+ **Authentication:** Required
423
505
 
424
- ```
506
+ **Request:**
507
+ ```json
425
508
  {
426
- "deviceId" : "xxx",
427
- "token" : "token..."
509
+ "deviceId": "device-123",
510
+ "token": "firebase-token-xyz"
428
511
  }
429
512
  ```
430
513
 
431
- #### Response example
432
-
433
- ```
514
+ **Response:**
515
+ ```json
434
516
  {
435
517
  "data": {
436
518
  "status": "success"
@@ -440,20 +522,20 @@ Adds a push notification token to the user. Request needs authentication.
440
522
 
441
523
  ### DELETE /user/push
442
524
 
443
- Removes a push notification token from the user. Request needs authentication.
525
+ Remove push notification token.
444
526
 
445
- #### Request data:
527
+ **Authentication:** Required
446
528
 
447
- ```
529
+ **Request:**
530
+ ```json
448
531
  {
449
- "deviceId" : "xxx",
450
- "token" : "token..."
532
+ "deviceId": "device-123",
533
+ "token": "firebase-token-xyz"
451
534
  }
452
535
  ```
453
536
 
454
- #### Response example
455
-
456
- ```
537
+ **Response:**
538
+ ```json
457
539
  {
458
540
  "data": {
459
541
  "status": "success"
@@ -463,429 +545,409 @@ Removes a push notification token from the user. Request needs authentication.
463
545
 
464
546
  ### GET /user/token
465
547
 
466
- Get a refreshed token for the current user. Request needs authentication.
548
+ Refresh JWT token for current user (useful after role changes).
467
549
 
468
- User need to refresh his token if roles or username have been changed.
550
+ **Authentication:** Required
469
551
 
470
- #### Response example
471
-
472
- ```
552
+ **Response:**
553
+ ```json
473
554
  {
474
555
  "data": {
475
- "token": "token..."
556
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
476
557
  }
477
558
  }
478
559
  ```
479
560
 
480
- ## Using plugin functions
561
+ ## SMS Authentication
481
562
 
482
- As an alternative to use the included API-endpoints you might use the supplied core functions from this plugin to manage some basic tasks.
483
-
484
- After initilizing this plugin, these functions are exposed in the app context:
485
-
486
- ```
487
- ctx.plugins.genericAuthPlugin.loginUser( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, username : string, password? : string) : Promise<UserLoginRes>
488
- ```
489
-
490
- ```
491
- ctx.plugins.genericAuthPlugin.createUser( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, username : string, password : string, authentificationMethod : "password" | "sms", roles : string[], profile : UserProfile ) : Promise<UserCreateRes>
492
- ```
493
-
494
- ```
495
- ctx.plugins.genericAuthPlugin.changePassword( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, userId : string, newPassword : string) : Promise<UserPasswordChangeRes>
496
- ```
563
+ ### Setup
497
564
 
498
- ```
499
- ctx.plugins.genericAuthPlugin.passwordResetStart( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, jwtSecret : string, username : string, numberOfDigits? : number, lifeTime? : string) : Promise<UserPasswordResetStartRes>
500
- ```
565
+ Install and configure SMS plugin:
501
566
 
567
+ ```bash
568
+ npm install @flink-app/sms-plugin
502
569
  ```
503
- ctx.plugins.genericAuthPlugin.passwordResetComplete( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, jwtSecret : string, passwordResetToken : string, code : string, newPassword : string) : Promise<UserPasswordResetCompleteRes>
504
- ```
505
-
506
- ## Deleting users, changing roles and more..
507
-
508
- You can always interact directly with the userRepo to modify you users.
509
-
510
- Please remember to refresh the users token after changing vital parts of a user.
511
570
 
512
- ## Using alternativ password and hash functions
513
-
514
- You can specify your own methods to generate hash and salt or to verify a password.
515
-
516
- To do this, simply specify the `createPasswordHashAndSaltMethod` and/or `validatePasswordMethod` options on plugin initilizing. Like this:
517
-
518
- ```
519
- //This is just a dummy on storing password in plain text. STRONGLY NOT RECOMMENDED.
571
+ ```typescript
572
+ import { sms46elksClient } from "@flink-app/sms-plugin";
520
573
 
521
574
  genericAuthPlugin({
522
- createPasswordHashAndSaltMethod : (password) => {
523
- return new Promise((resolve) => {
524
- resolve({ hash : password, salt : "" });
525
- })
575
+ repoName: "userRepo",
576
+ sms: {
577
+ smsClient: new sms46elksClient({
578
+ username: process.env.SMS_USERNAME!,
579
+ password: process.env.SMS_PASSWORD!,
580
+ }),
581
+ smsFrom: "MyApp",
582
+ smsMessage: "Your verification code is {{code}}",
583
+ jwtToken: process.env.SMS_JWT_SECRET!,
584
+ codeType: "numeric",
585
+ codeLength: 6,
526
586
  },
527
- validatePasswordMethod : (password, hash, salt) => {
528
- return new Promise((resolve) => {
529
- resolve(password == hash);
530
- })
531
- }
532
587
  })
533
588
  ```
534
589
 
535
- When `validatePasswordMethod` is specified, that method will be used to validate the password. If that method returns false, the default validation will try as well. This will make it possible to use both default password hashes and alternative ones.
536
-
537
- ### How to validate old Aquro / Aplexa passwords?
538
-
539
- If you are using this plugin to verify old Aquro / Aplexa passwords, you can simply do this by specifying `validatePasswordMethod` and use the same hash-function as Aquro Platform.
540
-
541
- #### Install required plugin
590
+ ### Create SMS User
542
591
 
543
- ```
544
- npm install --save password-hash
545
- npm install --save-dev @types/password-hash
592
+ **POST /user/create:**
593
+ ```json
594
+ {
595
+ "username": "+46701234567",
596
+ "authentificationMethod": "sms"
597
+ }
546
598
  ```
547
599
 
548
- #### Update plugin initialization
600
+ ### Login with SMS (Two-Step Process)
549
601
 
602
+ **Step 1: Initiate** - POST /user/login
603
+ ```json
604
+ {
605
+ "username": "+46701234567"
606
+ }
550
607
  ```
551
- import passwordHash from "password-hash";
552
608
 
553
- genericAuthPlugin({
554
- validatePasswordMethod : (password, hash, salt) => {
555
- return new Promise((resolve) => {
556
- resolve(passwordHash.verify(password, hash));
557
- })
558
- }
559
- })
609
+ Response:
610
+ ```json
611
+ {
612
+ "data": {
613
+ "status": "success",
614
+ "validationToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
615
+ }
616
+ }
560
617
  ```
561
618
 
562
- ## Enabling management-api functions
563
-
564
- This plugin supports the [management-api-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/management-api-plugin) structure to expose management apis.
565
-
566
- The management API is used by [flink-admin](https://github.com/FrostDigital/flink-admin). So to enable managing of app users in flink-admin, you need to exponse this plugins management api functions.
567
-
568
- To do this, first install the managemnet-api-plugin and then get the management module from this plugin.
569
-
619
+ **Step 2: Complete** - POST /user/login-by-token
620
+ ```json
621
+ {
622
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
623
+ "code": "123456"
624
+ }
570
625
  ```
571
- import { GetManagementModule } from "@flink-app/generic-auth-plugin"
572
626
 
573
- const genericAuthManagementModule = GetManagementModule(
574
- {
575
- ui : true, //Enable UI for this module in flink-admin-portal
576
- uiSettings : {
577
- title : "App users", //Title of this module
578
- enableUserEdit, : true //Make it possible to edit the user
579
- enableUserCreate, : true //Make it possible to create new users
580
- enableUserDelete, : true //Make it possible to delete the user
581
- enableUserView, : true //Make it possible to view a user
627
+ Response:
628
+ ```json
629
+ {
630
+ "data": {
631
+ "status": "success",
632
+ "user": {
633
+ "_id": "507f1f77bcf86cd799439011",
634
+ "username": "+46701234567",
635
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
636
+ "profile": {}
582
637
  }
583
638
  }
584
- )
585
- ```
586
-
587
- Finally add the management module to the list of modules in the managementApiPlugin config:
588
-
589
- ```
590
-
591
- function start() {
592
- new FlinkApp<Ctx>({
593
- name: "My flink app",
594
- debug: true,
595
- auth : authPlugin,
596
- loader: (file: any) => import(file),
597
- db: {
598
- uri: "mongodb://localhost:27017/my-flink-app",
599
- },
600
- plugins: [
601
- genericAuthPlugin({
602
- repoName : "userRepo",
603
- }),
604
- managementApiPlugin({
605
- token : "TOKEN",
606
- jwtSecret : "SECRET",
607
- modules : [
608
- genericAuthManagementModule
609
- ]
610
- })
611
- ],
612
- }).start();
613
639
  }
614
640
  ```
615
641
 
616
- ### Enable user viewing
642
+ ## Custom Password Hashing
617
643
 
618
- To make it possible to view data for a user, you will need to first enable the `enableUserView` flag.
644
+ You can provide custom password hashing functions to support legacy systems:
619
645
 
620
- You can also provide a function that returns the data that should be shown of the user.
621
- This function can also be extended to return a list of buttons that will be added to the toolbar.
622
-
623
-
624
- ```
625
- import { GetManagementModule } from "@flink-app/generic-auth-plugin"
646
+ ```typescript
647
+ import passwordHash from "password-hash";
626
648
 
627
- const genericAuthManagementModule = GetManagementModule(
628
- {
629
- ui : true, //Enable UI for this module in flink-admin-portal
630
- uiSettings : {
631
- title : "App users", //Title of this module
632
- enableUserEdit, : true //Make it possible to edit the user
633
- enableUserCreate, : true //Make it possible to create new users
634
- enableUserDelete, : true //Make it possible to delete the user
635
- enableUserView, : true //Make it possible to view a user
636
- },
637
- userView: {
638
- getData(user: User) {
639
-
640
- let data: {
641
- [key: string]: string
642
- } = {
643
- 'E-mail': user.username,
644
- 'Profile property' : user.profile.Property.toString()
645
- }
646
-
647
- let buttons: {
648
- text: string
649
- url: string
650
- }[] = []
651
-
652
- buttons.push({
653
- text: 'Visit google',
654
- url: 'https://www.google.com',
655
- })
656
-
657
-
658
- return {
659
- buttons,
660
- data,
661
- }
662
- },
663
- },
664
- }
665
- )
649
+ genericAuthPlugin({
650
+ repoName: "userRepo",
651
+ createPasswordHashAndSaltMethod: async (password) => {
652
+ // Custom hash creation
653
+ return {
654
+ hash: passwordHash.generate(password),
655
+ salt: "",
656
+ };
657
+ },
658
+ validatePasswordMethod: async (password, hash, salt) => {
659
+ // Custom validation
660
+ return passwordHash.verify(password, hash);
661
+ },
662
+ })
666
663
  ```
667
664
 
665
+ This allows validating both new and legacy password formats.
668
666
 
667
+ ## User Model
669
668
 
670
- ### Make it possible to edit profile properites
671
-
672
- To make it possible to edit profile properties when editing users, you must expose the JSON-schema of the profile.
673
-
674
- To do this, follow these steps:
675
-
676
- #### Step 1:
677
-
678
- Create a Schema file for your profile properties, eg. schemas/ProfileProperties.ts
679
-
680
- #### Step 2:
681
-
682
- Configure flink to generate schema files by editing package.json file and replace
683
-
684
- ```
685
- "flink:generate": "flink generate"
686
- ```
669
+ ```typescript
670
+ interface User {
671
+ _id: string;
672
+ username: string;
673
+ personalNumber?: string;
674
+ password?: string;
675
+ salt?: string;
676
+ pwdResetStartedAt?: string | null;
677
+ roles: string[];
678
+ authentificationMethod: "password" | "sms" | "bankid";
679
+ profile: UserProfile;
680
+ pushNotificationTokens: PushNotificationToken[];
681
+ }
687
682
 
688
- to:
683
+ interface UserProfile {
684
+ [key: string]: any; // Custom profile fields
685
+ }
689
686
 
690
- ```
691
- "flink:generate": "flink generate && flink generate-schema"
687
+ interface PushNotificationToken {
688
+ deviceId: string;
689
+ token: string;
690
+ registeredAt: Date;
691
+ }
692
692
  ```
693
693
 
694
- #### Step 3:
694
+ ## Management API Integration
695
695
 
696
- Restart your flink app (to generate the JSON-schemeas)
696
+ Integrate with [@flink-app/management-api-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/management-api-plugin) for admin interfaces:
697
697
 
698
- #### Step 4:
698
+ ```typescript
699
+ import { managementApiPlugin } from "@flink-app/management-api-plugin";
700
+ import { GetManagementModule } from "@flink-app/generic-auth-plugin";
701
+ import schemas from "./.flink/schemas.json"; // Generated schemas
699
702
 
700
- Import the JSON-schema and pass it to the `GetManagementModule` function.
703
+ const genericAuthManagementModule = GetManagementModule({
704
+ ui: true,
705
+ profileSchema: schemas.UserProfile, // Enable profile editing
706
+ uiSettings: {
707
+ title: "App Users",
708
+ enableUserEdit: true,
709
+ enableUserCreate: true,
710
+ enableUserDelete: true,
711
+ enableUserView: true,
712
+ },
713
+ userView: {
714
+ getData(user) {
715
+ return {
716
+ data: {
717
+ "Email": user.username,
718
+ "Name": user.profile.name,
719
+ "Status": user.roles.join(", "),
720
+ },
721
+ buttons: [
722
+ {
723
+ text: "Send Email",
724
+ url: `mailto:${user.username}`,
725
+ },
726
+ ],
727
+ };
728
+ },
729
+ },
730
+ });
701
731
 
732
+ // Add to plugins
733
+ plugins: [
734
+ genericAuthPlugin({ /* ... */ }),
735
+ managementApiPlugin({
736
+ token: process.env.ADMIN_TOKEN!,
737
+ jwtSecret: process.env.ADMIN_JWT_SECRET!,
738
+ modules: [genericAuthManagementModule],
739
+ }),
740
+ ]
702
741
  ```
703
- import { GetManagementModule } from "@flink-app/generic-auth-plugin"
704
- import schemas from "../.flink/schemas.json";
705
742
 
706
- const genericAuthManagementModule = GetManagementModule(
707
- {
708
- ui : true, //Enable UI for this module in flink-admin-portal
709
- profileSchema : schemas.ProfileProperties,
710
- uiSettings : {
711
- title : "App users", //Title of this module
712
- enableUserEdit, : true //Make it possible to edit the user
713
- enableUserCreate, : true //Make it possible to create new users
714
- enableUserDelete, : true //Make it possible to delete the user
715
- }
716
- }
717
- )
718
- ```
743
+ ## Lifecycle Hooks
719
744
 
720
- Please note that only string and enum property types can be edited from the flink-admin interface.
745
+ ### onSuccessfulLogin
721
746
 
722
- Do:
747
+ Called after successful authentication:
723
748
 
724
- ```
725
- export interface ProfileProperties{
726
- name : string;
727
- city : string;
728
- gender : "male" | "female" | "any";
729
- }
749
+ ```typescript
750
+ genericAuthPlugin({
751
+ repoName: "userRepo",
752
+ onSuccessfulLogin: async (user, req) => {
753
+ // Track login
754
+ await ctx.repos.auditLogRepo.create({
755
+ userId: user._id,
756
+ action: "login",
757
+ ip: req?.ip,
758
+ timestamp: new Date(),
759
+ });
760
+ },
761
+ })
730
762
  ```
731
763
 
732
- Dont:
764
+ ### onUserCreated
733
765
 
734
- ```
735
- type myType = {
736
- hello : string,
737
- world : string
738
- }
766
+ Called after user creation:
739
767
 
740
- export interface Profile{
741
- name : string;
742
- type : myType
743
- }
768
+ ```typescript
769
+ genericAuthPlugin({
770
+ repoName: "userRepo",
771
+ onUserCreated: async (user) => {
772
+ // Send welcome email
773
+ await ctx.plugins.email.send({
774
+ to: user.username,
775
+ subject: "Welcome!",
776
+ html: `<p>Welcome to our app, ${user.profile.name}!</p>`,
777
+ });
778
+ },
779
+ })
744
780
  ```
745
781
 
782
+ ## Protecting Your Routes
746
783
 
784
+ Use permissions from jwt-auth-plugin to protect routes:
747
785
 
748
- ## Using SMS login
749
-
750
-
751
- ### prerequisites
752
- - A SMS client must be setup using the [sms-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/sms-plugin)
753
-
786
+ ```typescript
787
+ // Only authenticated users
788
+ export const Route: RouteProps = {
789
+ path: "/api/data",
790
+ permission: "read",
791
+ };
754
792
 
755
- ### Setup
756
- - Configure this plugin by setting the sms option:
793
+ // Only admins
794
+ export const Route: RouteProps = {
795
+ path: "/api/admin/users",
796
+ permission: "manage_users",
797
+ };
798
+ ```
757
799
 
800
+ ## Complete Example
758
801
 
759
- ```
802
+ ```typescript
803
+ // index.ts
760
804
  import { FlinkApp } from "@flink-app/flink";
805
+ import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
806
+ import { genericAuthPlugin } from "@flink-app/generic-auth-plugin";
807
+ import { emailPlugin } from "@flink-app/email-plugin";
761
808
  import { Ctx } from "./Ctx";
762
809
 
763
- import { getJtwTokenPlugin, genericAuthPlugin } from "@flink-app/generic-auth-plugin"
764
-
765
- const authPlugin = getJtwTokenPlugin("secret");
766
-
767
810
  function start() {
768
- var app = new FlinkApp<Ctx>({
769
- name: "My flink app",
770
- debug: true,
771
- auth : authPlugin,
811
+ const app = new FlinkApp<Ctx>({
812
+ name: "My App",
813
+ auth: jwtAuthPlugin({
814
+ secret: process.env.JWT_SECRET!,
815
+ getUser: async (tokenData) => {
816
+ const user = await app.ctx.repos.userRepo.findById(tokenData.userId);
817
+ if (!user) throw new Error("User not found");
818
+ return {
819
+ id: user._id,
820
+ username: user.username,
821
+ roles: user.roles,
822
+ };
823
+ },
824
+ rolePermissions: {
825
+ admin: ["read", "write", "delete", "manage_users"],
826
+ user: ["read", "write"],
827
+ },
828
+ passwordPolicy: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*?&]{10,}$/,
829
+ tokenTTL: 1000 * 60 * 60 * 24 * 30, // 30 days
830
+ }),
772
831
  db: {
773
- uri: "mongodb://localhost:27017/my-flink-app",
832
+ uri: process.env.MONGODB_URI!,
774
833
  },
775
834
  plugins: [
835
+ emailPlugin({
836
+ provider: "sendgrid",
837
+ apiKey: process.env.SENDGRID_API_KEY!,
838
+ }),
776
839
  genericAuthPlugin({
777
- ...
778
-
779
-
780
- sms : {
781
- smsClient: new sms46elksClient({
782
- username: "XXX",
783
- password: "YYY",
784
- }),
785
- smsFrom: "AUTHMSG",
786
- smsMessage: "Your code is {{code}}",
787
- jwtToken: "secret-to-sign-jwt-tokens",
788
- codeType: "numeric",
789
- codeLength: 6
790
- }
791
-
792
-
793
- ...
794
-
795
- }
840
+ repoName: "userRepo",
841
+ enableRoutes: true,
842
+ enablePasswordReset: true,
843
+ enablePushNotificationTokens: true,
844
+ usernameFormat: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
845
+ passwordResetSettings: {
846
+ email: {
847
+ from_address: "noreply@example.com",
848
+ subject: "Password Reset - {{username}}",
849
+ html: `
850
+ <h2>Password Reset Request</h2>
851
+ <p>Your password reset code is: <strong>{{code}}</strong></p>
852
+ <p>This code expires in 1 hour.</p>
853
+ `,
854
+ },
855
+ code: {
856
+ numberOfDigits: 6,
857
+ lifeTime: "1h",
858
+ jwtSecret: process.env.PASSWORD_RESET_SECRET!,
859
+ },
860
+ },
861
+ onSuccessfulLogin: async (user) => {
862
+ console.log(`User ${user.username} logged in`);
863
+ },
864
+ onUserCreated: async (user) => {
865
+ console.log(`New user created: ${user.username}`);
866
+ },
796
867
  }),
797
868
  ],
798
- })
869
+ });
870
+
799
871
  app.start();
800
872
  }
873
+
801
874
  start();
802
875
  ```
803
876
 
877
+ ## Security Best Practices
804
878
 
879
+ ### 1. Username Validation
805
880
 
806
- ### Register users with SMS-login
807
- To use SMS-login on a user, the user must be created with the `authentificationMethod` option set to sms.
808
- Username also have to be the users phone number in the "+4671234567" format.
881
+ Validate username format to prevent injection attacks:
809
882
 
810
- ### POST /user/create
883
+ ```typescript
884
+ usernameFormat: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
885
+ ```
811
886
 
812
- Create a user that can login via SMS
887
+ ### 2. Password Reset Security
813
888
 
814
- #### Request data:
889
+ - Use short-lived tokens (1 hour or less)
890
+ - Use single-use tokens (set `passwordResetReusableTokens: false`)
891
+ - Use separate JWT secret for password resets
892
+ - Include rate limiting on reset endpoints
815
893
 
816
- ```
817
- {
818
- "username" : "+4671234567",
819
- "authentificationMethod" : "sms"
820
- }
821
- ```
894
+ ### 3. Push Token Management
822
895
 
823
- #### Response example
896
+ Enable `deregisterOtherDevices` to prevent token duplication:
824
897
 
825
- ```
826
- {
827
- "data": {
828
- "status": "success",
829
- },
898
+ ```typescript
899
+ deregisterOtherDevices: true
830
900
  ```
831
901
 
902
+ ### 4. Environment Variables
832
903
 
904
+ Never commit secrets to version control:
833
905
 
834
- ### Initiate login
835
- Initiate a user login by sending a SMS with the code to the user.
836
- Please note that the user HAVE to be created with the `authentificationMethod` option set to sms.
906
+ ```bash
907
+ JWT_SECRET=xxx
908
+ PASSWORD_RESET_SECRET=yyy
909
+ SENDGRID_API_KEY=zzz
910
+ ```
837
911
 
838
- ### POST /user/login
912
+ ### 5. HTTPS Only
839
913
 
914
+ Always use HTTPS in production to prevent credential interception.
840
915
 
841
- #### Request data:
916
+ ## TypeScript Types
842
917
 
843
- ```
844
- {
845
- "username" : "+4671234567",
846
- }
918
+ ```typescript
919
+ import {
920
+ User,
921
+ UserProfile,
922
+ UserLoginRes,
923
+ UserCreateRes,
924
+ UserPasswordResetStartRes,
925
+ UserPasswordResetCompleteRes,
926
+ GenericAuthPluginOptions,
927
+ } from "@flink-app/generic-auth-plugin";
847
928
  ```
848
929
 
849
- #### Response example
930
+ ## Troubleshooting
850
931
 
851
- ```
852
- {
853
- "data": {
854
- "status": "success",
855
- "validationToken": "TOKEN"
856
- },
857
- }
932
+ ### Password Reset Emails Not Sending
858
933
 
934
+ **Solution:** Verify email plugin is configured and test email settings:
935
+ ```typescript
936
+ await ctx.plugins.email.send({
937
+ to: "test@example.com",
938
+ subject: "Test",
939
+ html: "<p>Test email</p>",
940
+ });
859
941
  ```
860
942
 
861
- ### Login
862
- Finalize the login by sending the token received above, and the code received via SMS.
863
-
864
- ### POST /user/login-by-token
943
+ ### Users Cannot Login After Creation
865
944
 
945
+ **Solution:** Check password policy matches between jwt-auth-plugin and user creation.
866
946
 
867
- #### Request data:
947
+ ### SMS Code Not Received
868
948
 
869
- ```
870
- {
871
- "token" : "TOKEN",
872
- "code" : "code"
873
- }
874
- ```
949
+ **Solution:** Verify SMS plugin configuration and check SMS provider logs.
875
950
 
876
- #### Response example
951
+ ## License
877
952
 
878
- ```
879
- {
880
- "data": {
881
- "status": "success",
882
- "user": {
883
- "_id": "1234...",
884
- "username": "+4671234567",
885
- "token": "Token",
886
- "profile": {}
887
- }
888
- },
889
- "status": 200
890
- }
891
- ```
953
+ MIT