@flink-app/generic-auth-plugin 0.12.1-alpha.9 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.flink/generatedHandlers.ts +1 -1
- package/.flink/generatedJobs.ts +1 -1
- package/.flink/generatedRepos.ts +1 -1
- package/.flink/schemas/schemas.json +1 -643
- package/.flink/schemas/schemas.ts +1 -105
- package/.flink/start.ts +2 -1
- package/CHANGELOG.md +15 -0
- package/dist/.flink/generatedHandlers.js +1 -1
- package/dist/.flink/generatedJobs.js +1 -1
- package/dist/.flink/generatedRepos.js +1 -1
- package/dist/.flink/schemas/schemas.d.ts +0 -104
- package/dist/.flink/schemas/schemas.js +1 -1
- package/dist/.flink/schemas/schemas.json +1 -643
- package/dist/.flink/start.d.ts +2 -0
- package/dist/.flink/start.js +2 -1
- package/dist/src/coreFunctions.d.ts +5 -5
- package/dist/src/coreFunctions.js +31 -14
- package/dist/src/genericAuthContext.d.ts +8 -5
- package/dist/src/genericAuthPluginOptions.d.ts +6 -1
- package/dist/src/handlers/Management/DeleteUserByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/DeleteUserByUserid.js +3 -4
- package/dist/src/handlers/Management/GetSchema.d.ts +0 -1
- package/dist/src/handlers/Management/GetSchema.js +3 -4
- package/dist/src/handlers/Management/GetUser.d.ts +0 -1
- package/dist/src/handlers/Management/GetUser.js +3 -4
- package/dist/src/handlers/Management/GetUserByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/GetUserByUserid.js +3 -4
- package/dist/src/handlers/Management/GetUserViewByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/GetUserViewByUserid.js +3 -4
- package/dist/src/handlers/Management/PutUserPasswordByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/PutUserPasswordByUserid.js +3 -4
- package/dist/src/handlers/Management/PutUserProfileByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/PutUserProfileByUserid.js +16 -9
- package/dist/src/handlers/Management/PutUserProfileByUseridAppend.d.ts +0 -1
- package/dist/src/handlers/Management/PutUserProfileByUseridAppend.js +3 -4
- package/dist/src/handlers/Management/PutUserRolesByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/PutUserRolesByUserid.js +3 -4
- package/dist/src/handlers/Management/PutUserUsernameByUserid.d.ts +0 -1
- package/dist/src/handlers/Management/PutUserUsernameByUserid.js +3 -4
- package/dist/src/handlers/UserCreate.d.ts +0 -1
- package/dist/src/handlers/UserCreate.js +6 -7
- package/dist/src/handlers/UserLogin.d.ts +0 -1
- package/dist/src/handlers/UserLogin.js +42 -11
- package/dist/src/handlers/UserLoginByToken.d.ts +0 -1
- package/dist/src/handlers/UserLoginByToken.js +3 -4
- package/dist/src/handlers/UserPasswordPut.d.ts +0 -1
- package/dist/src/handlers/UserPasswordPut.js +3 -4
- package/dist/src/handlers/UserPasswordResetComplete.d.ts +0 -1
- package/dist/src/handlers/UserPasswordResetComplete.js +3 -4
- package/dist/src/handlers/UserPasswordResetForm.js +6 -6
- package/dist/src/handlers/UserPasswordResetStart.d.ts +0 -1
- package/dist/src/handlers/UserPasswordResetStart.js +3 -4
- package/dist/src/handlers/UserProfileGet.d.ts +0 -1
- package/dist/src/handlers/UserProfileGet.js +3 -4
- package/dist/src/handlers/UserProfilePut.d.ts +0 -1
- package/dist/src/handlers/UserProfilePut.js +11 -7
- package/dist/src/handlers/UserPushRegisterToken.d.ts +0 -1
- package/dist/src/handlers/UserPushRegisterToken.js +4 -5
- package/dist/src/handlers/UserPushRemoveToken.d.ts +0 -1
- package/dist/src/handlers/UserPushRemoveToken.js +3 -4
- package/dist/src/handlers/UserToken.d.ts +0 -1
- package/dist/src/handlers/UserToken.js +3 -4
- package/dist/src/index.js +2 -2
- package/dist/src/init.js +2 -3
- package/dist/src/schemas/User.d.ts +2 -1
- package/dist/src/schemas/UserCreateReq.d.ts +2 -1
- package/dist/src/schemas/UserPasswordResetCompleteRes.d.ts +4 -0
- package/package.json +32 -33
- package/readme.md +584 -570
- package/src/coreFunctions.ts +29 -7
- package/src/genericAuthContext.ts +8 -5
- package/src/genericAuthPluginOptions.ts +6 -1
- package/src/handlers/Management/PutUserProfileByUserid.ts +6 -0
- package/src/handlers/UserCreate.ts +3 -2
- package/src/handlers/UserLogin.ts +56 -31
- package/src/handlers/UserProfilePut.ts +20 -22
- package/src/handlers/UserPushRegisterToken.ts +1 -1
- package/src/index.ts +2 -1
- package/src/init.ts +108 -120
- package/src/schemas/User.ts +2 -1
- package/src/schemas/UserCreateReq.ts +5 -4
- package/src/schemas/UserPasswordResetCompleteRes.ts +8 -3
- package/tsconfig.json +21 -21
package/readme.md
CHANGED
|
@@ -1,338 +1,422 @@
|
|
|
1
|
-
# Generic Auth Plugin
|
|
1
|
+
# Generic Auth Plugin
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
-
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
18
|
+
## Dependencies
|
|
20
19
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
|
|
31
|
+
For SMS authentication:
|
|
32
|
+
```bash
|
|
33
|
+
npm install @flink-app/sms-plugin
|
|
27
34
|
```
|
|
28
35
|
|
|
29
36
|
## Setup
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
### Step 1: Create User Repository
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
Create a user repository in your project:
|
|
34
41
|
|
|
35
|
-
|
|
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
|
-
|
|
47
|
+
|
|
43
48
|
class UserRepo extends FlinkRepo<Ctx, User> {}
|
|
44
49
|
|
|
45
50
|
export default UserRepo;
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
|
|
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
|
|
60
|
+
userRepo: UserRepo;
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
```
|
|
60
64
|
|
|
61
|
-
### Step 2
|
|
65
|
+
### Step 2: Configure Authentication
|
|
62
66
|
|
|
63
|
-
|
|
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
|
-
|
|
74
|
-
name: "My
|
|
76
|
+
const app = new FlinkApp<Ctx>({
|
|
77
|
+
name: "My Flink App",
|
|
75
78
|
debug: true,
|
|
76
|
-
auth
|
|
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:
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
###
|
|
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
|
-
|
|
177
|
+
**Handlebars Context:**
|
|
178
|
+
- `{{username}}` - User's username
|
|
179
|
+
- `{{code}}` - Password reset code
|
|
180
|
+
- `{{profile}}` - User profile object
|
|
99
181
|
|
|
100
|
-
|
|
182
|
+
### SMS Authentication Options
|
|
101
183
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
+
The plugin exposes the following functions via `ctx.plugins.genericAuthPlugin`:
|
|
143
198
|
|
|
144
|
-
|
|
199
|
+
### `loginUser()`
|
|
145
200
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
216
|
+
**Returns:** `UserLoginRes`
|
|
155
217
|
|
|
156
|
-
|
|
218
|
+
### `loginByToken()`
|
|
157
219
|
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
+
### `createUser()`
|
|
169
235
|
|
|
170
|
-
|
|
236
|
+
Create a new user with password, SMS, or BankID authentication.
|
|
171
237
|
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
253
|
+
**Returns:** `UserCreateRes`
|
|
180
254
|
|
|
181
|
-
|
|
255
|
+
### `changePassword()`
|
|
182
256
|
|
|
183
|
-
|
|
257
|
+
Change a user's password.
|
|
184
258
|
|
|
185
|
-
```
|
|
186
|
-
|
|
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
|
-
|
|
269
|
+
**Returns:** `UserPasswordChangeRes`
|
|
190
270
|
|
|
191
|
-
|
|
271
|
+
### `passwordResetStart()`
|
|
192
272
|
|
|
193
|
-
|
|
273
|
+
Initiate password reset process and send email with code.
|
|
194
274
|
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
287
|
+
**Returns:** `UserPasswordResetStartRes`
|
|
203
288
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
306
|
+
**Returns:** `UserPasswordResetCompleteRes`
|
|
212
307
|
|
|
213
|
-
|
|
308
|
+
## Built-in API Endpoints
|
|
214
309
|
|
|
215
|
-
|
|
310
|
+
When `enableRoutes: true` (default), the following endpoints are automatically registered:
|
|
216
311
|
|
|
217
|
-
|
|
312
|
+
### POST /user/create
|
|
218
313
|
|
|
219
|
-
|
|
314
|
+
Create a new user account.
|
|
315
|
+
|
|
316
|
+
**Request:**
|
|
317
|
+
```json
|
|
220
318
|
{
|
|
221
|
-
"username"
|
|
222
|
-
"password"
|
|
223
|
-
"
|
|
224
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
```
|
|
329
|
+
**Response:**
|
|
330
|
+
```json
|
|
232
331
|
{
|
|
233
332
|
"data": {
|
|
234
|
-
"status": "success"
|
|
333
|
+
"status": "success",
|
|
235
334
|
"user": {
|
|
236
|
-
"_id": "
|
|
237
|
-
"
|
|
238
|
-
"
|
|
335
|
+
"_id": "507f1f77bcf86cd799439011",
|
|
336
|
+
"username": "user@example.com",
|
|
337
|
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
239
338
|
}
|
|
240
339
|
}
|
|
241
340
|
}
|
|
242
341
|
```
|
|
243
342
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
#### Request data:
|
|
350
|
+
Login with username and password.
|
|
258
351
|
|
|
259
|
-
|
|
352
|
+
**Request:**
|
|
353
|
+
```json
|
|
260
354
|
{
|
|
261
|
-
"username"
|
|
262
|
-
"password"
|
|
355
|
+
"username": "user@example.com",
|
|
356
|
+
"password": "mypassword123"
|
|
263
357
|
}
|
|
264
358
|
```
|
|
265
359
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
```
|
|
360
|
+
**Response:**
|
|
361
|
+
```json
|
|
269
362
|
{
|
|
270
363
|
"data": {
|
|
271
|
-
"status": "success"
|
|
364
|
+
"status": "success",
|
|
272
365
|
"user": {
|
|
273
|
-
"_id": "
|
|
274
|
-
"username": "
|
|
275
|
-
"token": "
|
|
366
|
+
"_id": "507f1f77bcf86cd799439011",
|
|
367
|
+
"username": "user@example.com",
|
|
368
|
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
276
369
|
"profile": {
|
|
277
|
-
"
|
|
370
|
+
"name": "John Doe",
|
|
371
|
+
"age": 30
|
|
278
372
|
}
|
|
279
373
|
}
|
|
280
374
|
}
|
|
281
375
|
}
|
|
282
376
|
```
|
|
283
377
|
|
|
284
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
#### Request data:
|
|
383
|
+
Initiate password reset (sends email with code).
|
|
295
384
|
|
|
296
|
-
|
|
385
|
+
**Request:**
|
|
386
|
+
```json
|
|
297
387
|
{
|
|
298
|
-
"username"
|
|
388
|
+
"username": "user@example.com"
|
|
299
389
|
}
|
|
300
390
|
```
|
|
301
391
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
```
|
|
392
|
+
**Response:**
|
|
393
|
+
```json
|
|
305
394
|
{
|
|
306
395
|
"data": {
|
|
307
|
-
"status": "success"
|
|
308
|
-
"passwordResetToken": "
|
|
396
|
+
"status": "success",
|
|
397
|
+
"passwordResetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
309
398
|
}
|
|
310
399
|
}
|
|
311
400
|
```
|
|
312
401
|
|
|
313
|
-
|
|
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
|
-
|
|
407
|
+
Complete password reset with code from email.
|
|
322
408
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
```
|
|
409
|
+
**Request:**
|
|
410
|
+
```json
|
|
326
411
|
{
|
|
327
|
-
"passwordResetToken"
|
|
328
|
-
"code"
|
|
329
|
-
"password"
|
|
412
|
+
"passwordResetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
413
|
+
"code": "123456",
|
|
414
|
+
"password": "mynewpassword123"
|
|
330
415
|
}
|
|
331
416
|
```
|
|
332
417
|
|
|
333
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
434
|
+
Change password for authenticated user.
|
|
354
435
|
|
|
355
|
-
|
|
436
|
+
**Authentication:** Required
|
|
356
437
|
|
|
357
|
-
|
|
438
|
+
**Request:**
|
|
439
|
+
```json
|
|
358
440
|
{
|
|
359
|
-
"password"
|
|
441
|
+
"password": "mynewpassword123"
|
|
360
442
|
}
|
|
361
443
|
```
|
|
362
444
|
|
|
363
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
460
|
+
Get current user's profile.
|
|
383
461
|
|
|
384
|
-
|
|
462
|
+
**Authentication:** Required
|
|
385
463
|
|
|
386
|
-
|
|
464
|
+
**Response:**
|
|
465
|
+
```json
|
|
387
466
|
{
|
|
388
467
|
"data": {
|
|
389
|
-
"
|
|
468
|
+
"name": "John Doe",
|
|
469
|
+
"age": 30
|
|
390
470
|
}
|
|
391
471
|
}
|
|
392
472
|
```
|
|
393
473
|
|
|
394
474
|
### PUT /user/profile
|
|
395
475
|
|
|
396
|
-
|
|
476
|
+
Update current user's profile.
|
|
397
477
|
|
|
398
|
-
|
|
478
|
+
**Authentication:** Required
|
|
399
479
|
|
|
400
|
-
|
|
480
|
+
**Request:**
|
|
481
|
+
```json
|
|
401
482
|
{
|
|
402
|
-
"
|
|
403
|
-
"
|
|
483
|
+
"name": "Jane Doe",
|
|
484
|
+
"age": 31,
|
|
485
|
+
"city": "Stockholm"
|
|
404
486
|
}
|
|
405
487
|
```
|
|
406
488
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
```
|
|
489
|
+
**Response:**
|
|
490
|
+
```json
|
|
410
491
|
{
|
|
411
492
|
"data": {
|
|
412
|
-
"
|
|
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
|
-
|
|
502
|
+
Register push notification token.
|
|
421
503
|
|
|
422
|
-
|
|
504
|
+
**Authentication:** Required
|
|
423
505
|
|
|
424
|
-
|
|
506
|
+
**Request:**
|
|
507
|
+
```json
|
|
425
508
|
{
|
|
426
|
-
"deviceId"
|
|
427
|
-
"token"
|
|
509
|
+
"deviceId": "device-123",
|
|
510
|
+
"token": "firebase-token-xyz"
|
|
428
511
|
}
|
|
429
512
|
```
|
|
430
513
|
|
|
431
|
-
|
|
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
|
-
|
|
525
|
+
Remove push notification token.
|
|
444
526
|
|
|
445
|
-
|
|
527
|
+
**Authentication:** Required
|
|
446
528
|
|
|
447
|
-
|
|
529
|
+
**Request:**
|
|
530
|
+
```json
|
|
448
531
|
{
|
|
449
|
-
"deviceId"
|
|
450
|
-
"token"
|
|
532
|
+
"deviceId": "device-123",
|
|
533
|
+
"token": "firebase-token-xyz"
|
|
451
534
|
}
|
|
452
535
|
```
|
|
453
536
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
```
|
|
537
|
+
**Response:**
|
|
538
|
+
```json
|
|
457
539
|
{
|
|
458
540
|
"data": {
|
|
459
541
|
"status": "success"
|
|
@@ -463,429 +545,361 @@ Removes a push notification token from the user. Request needs authentication.
|
|
|
463
545
|
|
|
464
546
|
### GET /user/token
|
|
465
547
|
|
|
466
|
-
|
|
548
|
+
Refresh JWT token for current user (useful after role changes).
|
|
467
549
|
|
|
468
|
-
|
|
550
|
+
**Authentication:** Required
|
|
469
551
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
```
|
|
552
|
+
**Response:**
|
|
553
|
+
```json
|
|
473
554
|
{
|
|
474
555
|
"data": {
|
|
475
|
-
"token": "
|
|
556
|
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
476
557
|
}
|
|
477
558
|
}
|
|
478
559
|
```
|
|
479
560
|
|
|
480
|
-
##
|
|
561
|
+
## SMS Authentication
|
|
481
562
|
|
|
482
|
-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
592
|
+
**POST /user/create:**
|
|
593
|
+
```json
|
|
594
|
+
{
|
|
595
|
+
"username": "+46701234567",
|
|
596
|
+
"authentificationMethod": "sms"
|
|
597
|
+
}
|
|
546
598
|
```
|
|
547
599
|
|
|
548
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
609
|
+
Response:
|
|
610
|
+
```json
|
|
611
|
+
{
|
|
612
|
+
"data": {
|
|
613
|
+
"status": "success",
|
|
614
|
+
"validationToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
615
|
+
}
|
|
616
|
+
}
|
|
560
617
|
```
|
|
561
618
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
642
|
+
## Custom Password Hashing
|
|
617
643
|
|
|
618
|
-
|
|
644
|
+
You can provide custom password hashing functions to support legacy systems:
|
|
619
645
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
```
|
|
625
|
-
import { GetManagementModule } from "@flink-app/generic-auth-plugin"
|
|
646
|
+
```typescript
|
|
647
|
+
import passwordHash from "password-hash";
|
|
626
648
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
683
|
+
interface UserProfile {
|
|
684
|
+
[key: string]: any; // Custom profile fields
|
|
685
|
+
}
|
|
689
686
|
|
|
687
|
+
interface PushNotificationToken {
|
|
688
|
+
deviceId: string;
|
|
689
|
+
token: string;
|
|
690
|
+
registeredAt: Date;
|
|
691
|
+
}
|
|
690
692
|
```
|
|
691
|
-
"flink:generate": "flink generate && flink generate-schema"
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
#### Step 3:
|
|
695
693
|
|
|
696
|
-
Restart your flink app (to generate the JSON-schemeas)
|
|
697
|
-
|
|
698
|
-
#### Step 4:
|
|
699
|
-
|
|
700
|
-
Import the JSON-schema and pass it to the `GetManagementModule` function.
|
|
701
|
-
|
|
702
|
-
```
|
|
703
|
-
import { GetManagementModule } from "@flink-app/generic-auth-plugin"
|
|
704
|
-
import schemas from "../.flink/schemas.json";
|
|
705
694
|
|
|
706
|
-
|
|
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
|
-
```
|
|
695
|
+
## Lifecycle Hooks
|
|
719
696
|
|
|
720
|
-
|
|
697
|
+
### onSuccessfulLogin
|
|
721
698
|
|
|
722
|
-
|
|
699
|
+
Called after successful authentication:
|
|
723
700
|
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
701
|
+
```typescript
|
|
702
|
+
genericAuthPlugin({
|
|
703
|
+
repoName: "userRepo",
|
|
704
|
+
onSuccessfulLogin: async (user, req) => {
|
|
705
|
+
// Track login
|
|
706
|
+
await ctx.repos.auditLogRepo.create({
|
|
707
|
+
userId: user._id,
|
|
708
|
+
action: "login",
|
|
709
|
+
ip: req?.ip,
|
|
710
|
+
timestamp: new Date(),
|
|
711
|
+
});
|
|
712
|
+
},
|
|
713
|
+
})
|
|
730
714
|
```
|
|
731
715
|
|
|
732
|
-
|
|
716
|
+
### onUserCreated
|
|
733
717
|
|
|
734
|
-
|
|
735
|
-
type myType = {
|
|
736
|
-
hello : string,
|
|
737
|
-
world : string
|
|
738
|
-
}
|
|
718
|
+
Called after user creation:
|
|
739
719
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
720
|
+
```typescript
|
|
721
|
+
genericAuthPlugin({
|
|
722
|
+
repoName: "userRepo",
|
|
723
|
+
onUserCreated: async (user) => {
|
|
724
|
+
// Send welcome email
|
|
725
|
+
await ctx.plugins.email.send({
|
|
726
|
+
to: user.username,
|
|
727
|
+
subject: "Welcome!",
|
|
728
|
+
html: `<p>Welcome to our app, ${user.profile.name}!</p>`,
|
|
729
|
+
});
|
|
730
|
+
},
|
|
731
|
+
})
|
|
744
732
|
```
|
|
745
733
|
|
|
734
|
+
## Protecting Your Routes
|
|
746
735
|
|
|
736
|
+
Use permissions from jwt-auth-plugin to protect routes:
|
|
747
737
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
738
|
+
```typescript
|
|
739
|
+
// Only authenticated users
|
|
740
|
+
export const Route: RouteProps = {
|
|
741
|
+
path: "/api/data",
|
|
742
|
+
permission: "read",
|
|
743
|
+
};
|
|
754
744
|
|
|
755
|
-
|
|
756
|
-
|
|
745
|
+
// Only admins
|
|
746
|
+
export const Route: RouteProps = {
|
|
747
|
+
path: "/api/admin/users",
|
|
748
|
+
permission: "manage_users",
|
|
749
|
+
};
|
|
750
|
+
```
|
|
757
751
|
|
|
752
|
+
## Complete Example
|
|
758
753
|
|
|
759
|
-
```
|
|
754
|
+
```typescript
|
|
755
|
+
// index.ts
|
|
760
756
|
import { FlinkApp } from "@flink-app/flink";
|
|
757
|
+
import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
|
|
758
|
+
import { genericAuthPlugin } from "@flink-app/generic-auth-plugin";
|
|
759
|
+
import { emailPlugin } from "@flink-app/email-plugin";
|
|
761
760
|
import { Ctx } from "./Ctx";
|
|
762
761
|
|
|
763
|
-
import { getJtwTokenPlugin, genericAuthPlugin } from "@flink-app/generic-auth-plugin"
|
|
764
|
-
|
|
765
|
-
const authPlugin = getJtwTokenPlugin("secret");
|
|
766
|
-
|
|
767
762
|
function start() {
|
|
768
|
-
|
|
769
|
-
name: "My
|
|
770
|
-
|
|
771
|
-
|
|
763
|
+
const app = new FlinkApp<Ctx>({
|
|
764
|
+
name: "My App",
|
|
765
|
+
auth: jwtAuthPlugin({
|
|
766
|
+
secret: process.env.JWT_SECRET!,
|
|
767
|
+
getUser: async (tokenData) => {
|
|
768
|
+
const user = await app.ctx.repos.userRepo.findById(tokenData.userId);
|
|
769
|
+
if (!user) throw new Error("User not found");
|
|
770
|
+
return {
|
|
771
|
+
id: user._id,
|
|
772
|
+
username: user.username,
|
|
773
|
+
roles: user.roles,
|
|
774
|
+
};
|
|
775
|
+
},
|
|
776
|
+
rolePermissions: {
|
|
777
|
+
admin: ["read", "write", "delete", "manage_users"],
|
|
778
|
+
user: ["read", "write"],
|
|
779
|
+
},
|
|
780
|
+
passwordPolicy: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*?&]{10,}$/,
|
|
781
|
+
tokenTTL: 1000 * 60 * 60 * 24 * 30, // 30 days
|
|
782
|
+
}),
|
|
772
783
|
db: {
|
|
773
|
-
uri:
|
|
784
|
+
uri: process.env.MONGODB_URI!,
|
|
774
785
|
},
|
|
775
786
|
plugins: [
|
|
787
|
+
emailPlugin({
|
|
788
|
+
provider: "sendgrid",
|
|
789
|
+
apiKey: process.env.SENDGRID_API_KEY!,
|
|
790
|
+
}),
|
|
776
791
|
genericAuthPlugin({
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
792
|
+
repoName: "userRepo",
|
|
793
|
+
enableRoutes: true,
|
|
794
|
+
enablePasswordReset: true,
|
|
795
|
+
enablePushNotificationTokens: true,
|
|
796
|
+
usernameFormat: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
797
|
+
passwordResetSettings: {
|
|
798
|
+
email: {
|
|
799
|
+
from_address: "noreply@example.com",
|
|
800
|
+
subject: "Password Reset - {{username}}",
|
|
801
|
+
html: `
|
|
802
|
+
<h2>Password Reset Request</h2>
|
|
803
|
+
<p>Your password reset code is: <strong>{{code}}</strong></p>
|
|
804
|
+
<p>This code expires in 1 hour.</p>
|
|
805
|
+
`,
|
|
806
|
+
},
|
|
807
|
+
code: {
|
|
808
|
+
numberOfDigits: 6,
|
|
809
|
+
lifeTime: "1h",
|
|
810
|
+
jwtSecret: process.env.PASSWORD_RESET_SECRET!,
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
onSuccessfulLogin: async (user) => {
|
|
814
|
+
console.log(`User ${user.username} logged in`);
|
|
815
|
+
},
|
|
816
|
+
onUserCreated: async (user) => {
|
|
817
|
+
console.log(`New user created: ${user.username}`);
|
|
818
|
+
},
|
|
796
819
|
}),
|
|
797
820
|
],
|
|
798
|
-
})
|
|
821
|
+
});
|
|
822
|
+
|
|
799
823
|
app.start();
|
|
800
824
|
}
|
|
825
|
+
|
|
801
826
|
start();
|
|
802
827
|
```
|
|
803
828
|
|
|
829
|
+
## Security Best Practices
|
|
804
830
|
|
|
831
|
+
### 1. Username Validation
|
|
805
832
|
|
|
806
|
-
|
|
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.
|
|
833
|
+
Validate username format to prevent injection attacks:
|
|
809
834
|
|
|
810
|
-
|
|
835
|
+
```typescript
|
|
836
|
+
usernameFormat: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
837
|
+
```
|
|
811
838
|
|
|
812
|
-
|
|
839
|
+
### 2. Password Reset Security
|
|
813
840
|
|
|
814
|
-
|
|
841
|
+
- Use short-lived tokens (1 hour or less)
|
|
842
|
+
- Use single-use tokens (set `passwordResetReusableTokens: false`)
|
|
843
|
+
- Use separate JWT secret for password resets
|
|
844
|
+
- Include rate limiting on reset endpoints
|
|
815
845
|
|
|
816
|
-
|
|
817
|
-
{
|
|
818
|
-
"username" : "+4671234567",
|
|
819
|
-
"authentificationMethod" : "sms"
|
|
820
|
-
}
|
|
821
|
-
```
|
|
846
|
+
### 3. Push Token Management
|
|
822
847
|
|
|
823
|
-
|
|
848
|
+
Enable `deregisterOtherDevices` to prevent token duplication:
|
|
824
849
|
|
|
825
|
-
```
|
|
826
|
-
|
|
827
|
-
"data": {
|
|
828
|
-
"status": "success",
|
|
829
|
-
},
|
|
850
|
+
```typescript
|
|
851
|
+
deregisterOtherDevices: true
|
|
830
852
|
```
|
|
831
853
|
|
|
854
|
+
### 4. Environment Variables
|
|
832
855
|
|
|
856
|
+
Never commit secrets to version control:
|
|
833
857
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
858
|
+
```bash
|
|
859
|
+
JWT_SECRET=xxx
|
|
860
|
+
PASSWORD_RESET_SECRET=yyy
|
|
861
|
+
SENDGRID_API_KEY=zzz
|
|
862
|
+
```
|
|
837
863
|
|
|
838
|
-
###
|
|
864
|
+
### 5. HTTPS Only
|
|
839
865
|
|
|
866
|
+
Always use HTTPS in production to prevent credential interception.
|
|
840
867
|
|
|
841
|
-
|
|
868
|
+
## TypeScript Types
|
|
842
869
|
|
|
843
|
-
```
|
|
844
|
-
{
|
|
845
|
-
|
|
846
|
-
|
|
870
|
+
```typescript
|
|
871
|
+
import {
|
|
872
|
+
User,
|
|
873
|
+
UserProfile,
|
|
874
|
+
UserLoginRes,
|
|
875
|
+
UserCreateRes,
|
|
876
|
+
UserPasswordResetStartRes,
|
|
877
|
+
UserPasswordResetCompleteRes,
|
|
878
|
+
GenericAuthPluginOptions,
|
|
879
|
+
} from "@flink-app/generic-auth-plugin";
|
|
847
880
|
```
|
|
848
881
|
|
|
849
|
-
|
|
882
|
+
## Troubleshooting
|
|
850
883
|
|
|
851
|
-
|
|
852
|
-
{
|
|
853
|
-
"data": {
|
|
854
|
-
"status": "success",
|
|
855
|
-
"validationToken": "TOKEN"
|
|
856
|
-
},
|
|
857
|
-
}
|
|
884
|
+
### Password Reset Emails Not Sending
|
|
858
885
|
|
|
886
|
+
**Solution:** Verify email plugin is configured and test email settings:
|
|
887
|
+
```typescript
|
|
888
|
+
await ctx.plugins.email.send({
|
|
889
|
+
to: "test@example.com",
|
|
890
|
+
subject: "Test",
|
|
891
|
+
html: "<p>Test email</p>",
|
|
892
|
+
});
|
|
859
893
|
```
|
|
860
894
|
|
|
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
|
|
895
|
+
### Users Cannot Login After Creation
|
|
865
896
|
|
|
897
|
+
**Solution:** Check password policy matches between jwt-auth-plugin and user creation.
|
|
866
898
|
|
|
867
|
-
|
|
899
|
+
### SMS Code Not Received
|
|
868
900
|
|
|
869
|
-
|
|
870
|
-
{
|
|
871
|
-
"token" : "TOKEN",
|
|
872
|
-
"code" : "code"
|
|
873
|
-
}
|
|
874
|
-
```
|
|
901
|
+
**Solution:** Verify SMS plugin configuration and check SMS provider logs.
|
|
875
902
|
|
|
876
|
-
|
|
903
|
+
## License
|
|
877
904
|
|
|
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
|
-
```
|
|
905
|
+
MIT
|