@flink-app/generic-auth-plugin 0.11.16 → 0.11.18

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.
@@ -1,4 +1,4 @@
1
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
1
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
2
2
  import { autoRegisteredHandlers, HttpMethod } from "@flink-app/flink";
3
3
  import * as UserCreate_0 from "../src/handlers/UserCreate";
4
4
  import * as UserLogin_0 from "../src/handlers/UserLogin";
@@ -1,4 +1,4 @@
1
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
1
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
2
2
  import { autoRegisteredJobs } from "@flink-app/flink";
3
3
  export const jobs = [];
4
4
  autoRegisteredJobs.push(...jobs);
@@ -1,4 +1,4 @@
1
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
1
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
2
2
  import { autoRegisteredRepos } from "@flink-app/flink";
3
3
  export const repos = [];
4
4
  autoRegisteredRepos.push(...repos);
@@ -29,7 +29,7 @@ import { PutManagementUserRolesByUseridRes } from "../../src/schemas/Management/
29
29
  import { PutManagementUserUsernameByUseridReq } from "../../src/schemas/Management/PutUserUsernameByUseridReq";
30
30
  import { PutManagementUserUsernameByUseridRes } from "../../src/schemas/Management/PutUserUsernameByUseridRes";
31
31
 
32
- // Generated Tue Feb 25 2025 12:57:56 GMT+0100 (Central European Standard Time)
32
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
33
33
  export interface UserCreate_7_ReqSchema extends UserCreateReq {}
34
34
 
35
35
  export interface UserCreate_7_ResSchema extends UserCreateRes {}
package/.flink/start.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
1
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
2
2
  import "./generatedHandlers";
3
3
  import "./generatedRepos";
4
4
  import "./generatedJobs";
package/CLAUDE.md ADDED
@@ -0,0 +1,32 @@
1
+ # CLAUDE.md - Guidelines for Generic Auth Plugin
2
+
3
+ ## Build Commands
4
+ - Build: `npm run build` (runs `flink build`)
5
+ - Watch mode: `npm run watch` (uses nodemon to watch files and trigger builds)
6
+ - Publish: `npm run prepublish` (runs build before publishing)
7
+
8
+ ## Code Style
9
+
10
+ ### TypeScript
11
+ - Target: ES5, CommonJS modules, strict typing enabled
12
+ - Keep type definitions in separate schema files in the `/schemas` directory
13
+ - Use explicit return types on all functions (eg: `Promise<UserLoginRes>`)
14
+ - Always handle optionality with `?` and `undefined`/`null` checks
15
+
16
+ ### Naming Conventions
17
+ - camelCase for variables, functions, and methods
18
+ - PascalCase for interfaces and types
19
+ - Request types use `Req` suffix (UserLoginReq)
20
+ - Response types use `Res` suffix (UserLoginRes)
21
+ - Handler files match their function name (UserLogin.ts)
22
+
23
+ ### Error Handling
24
+ - Use structured error responses with status fields and messages
25
+ - Return typed response objects rather than throwing exceptions
26
+ - Use try/catch blocks for operations that might fail (JWT verification)
27
+
28
+ ### Code Structure
29
+ - Group imports: framework first, then local, then third-party
30
+ - Separate authentication methods and utility functions
31
+ - Use standard 4-space indentation with semicolons
32
+ - Maintain consistent function parameter ordering
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handlers = void 0;
4
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
4
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
5
5
  var flink_1 = require("@flink-app/flink");
6
6
  exports.handlers = [];
7
7
  flink_1.autoRegisteredHandlers.push.apply(flink_1.autoRegisteredHandlers, exports.handlers);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jobs = void 0;
4
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
4
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
5
5
  var flink_1 = require("@flink-app/flink");
6
6
  exports.jobs = [];
7
7
  flink_1.autoRegisteredJobs.push.apply(flink_1.autoRegisteredJobs, exports.jobs);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.repos = void 0;
4
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
4
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
5
5
  var flink_1 = require("@flink-app/flink");
6
6
  exports.repos = [];
7
7
  flink_1.autoRegisteredRepos.push.apply(flink_1.autoRegisteredRepos, exports.repos);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // Generated Tue Feb 25 2025 12:57:55 GMT+0100 (Central European Standard Time)
3
+ // Generated Wed Mar 12 2025 11:44:41 GMT+0100 (Central European Standard Time)
4
4
  require("./generatedHandlers");
5
5
  require("./generatedRepos");
6
6
  require("./generatedJobs");
@@ -16,6 +16,8 @@ export declare function createUser(repo: FlinkRepo<any, User>, auth: JwtAuthPlug
16
16
  hash: string;
17
17
  salt: string;
18
18
  } | null>;
19
+ }, onUserCreated?: {
20
+ (user: User): Promise<void>;
19
21
  }): Promise<UserCreateRes>;
20
22
  export declare function loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token: string, code: string, jwtSecret: string): Promise<UserLoginRes>;
21
23
  export declare function loginUser(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, username: string, password: string | undefined, validatePasswordMethod?: {
@@ -29,10 +31,10 @@ export declare function changePassword(repo: FlinkRepo<any, User>, auth: JwtAuth
29
31
  salt: string;
30
32
  } | null>;
31
33
  }): Promise<UserPasswordChangeRes>;
32
- export declare function passwordResetStart(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, jwtSecret: string, username: string, numberOfDigits?: number, lifeTime?: string): Promise<UserPasswordResetStartRes>;
34
+ export declare function passwordResetStart(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, jwtSecret: string, username: string, numberOfDigits?: number, lifeTime?: string, passwordResetReusableTokens?: boolean): Promise<UserPasswordResetStartRes>;
33
35
  export declare function passwordResetComplete(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, jwtSecret: string, passwordResetToken: string, code: string, newPassword: string, createPasswordHashAndSaltMethod?: {
34
36
  (password: string): Promise<{
35
37
  hash: string;
36
38
  salt: string;
37
39
  } | null>;
38
- }): Promise<UserPasswordResetCompleteRes>;
40
+ }, passwordResetReusableTokens?: boolean): Promise<UserPasswordResetCompleteRes>;
@@ -70,7 +70,7 @@ function getJtwTokenPlugin(secret, rolePermissions, passwordPolicy, tokenTTL) {
70
70
  });
71
71
  }
72
72
  exports.getJtwTokenPlugin = getJtwTokenPlugin;
73
- function createUser(repo, auth, username, password, authentificationMethod, roles, profile, createPasswordHashAndSaltMethod) {
73
+ function createUser(repo, auth, username, password, authentificationMethod, roles, profile, createPasswordHashAndSaltMethod, onUserCreated) {
74
74
  return __awaiter(this, void 0, void 0, function () {
75
75
  var existingUser, userData, passwordAndSalt, user, token;
76
76
  return __generator(this, function (_a) {
@@ -116,8 +116,13 @@ function createUser(repo, auth, username, password, authentificationMethod, role
116
116
  case 6: return [4 /*yield*/, repo.create(userData)];
117
117
  case 7:
118
118
  user = _a.sent();
119
- return [4 /*yield*/, auth.createToken({ username: username.toLowerCase(), _id: user._id }, roles)];
119
+ if (!onUserCreated) return [3 /*break*/, 9];
120
+ return [4 /*yield*/, onUserCreated(user)];
120
121
  case 8:
122
+ _a.sent();
123
+ _a.label = 9;
124
+ case 9: return [4 /*yield*/, auth.createToken({ username: username.toLowerCase(), _id: user._id }, roles)];
125
+ case 10:
121
126
  token = _a.sent();
122
127
  if (user.authentificationMethod == "sms") {
123
128
  return [2 /*return*/, {
@@ -300,9 +305,10 @@ function changePassword(repo, auth, userId, newPassword, createPasswordHashAndSa
300
305
  });
301
306
  }
302
307
  exports.changePassword = changePassword;
303
- function passwordResetStart(repo, auth, jwtSecret, username, numberOfDigits, lifeTime) {
308
+ function passwordResetStart(repo, auth, jwtSecret, username, numberOfDigits, lifeTime, passwordResetReusableTokens) {
309
+ if (passwordResetReusableTokens === void 0) { passwordResetReusableTokens = true; }
304
310
  return __awaiter(this, void 0, void 0, function () {
305
- var user, fakepayload, fakeToken, payload, code, secret, options, token;
311
+ var user, fakepayload, fakeToken, payload, code, pwdResetStartedAt, secret, options, token;
306
312
  return __generator(this, function (_a) {
307
313
  switch (_a.label) {
308
314
  case 0: return [4 /*yield*/, repo.getOne({ username: username.toLowerCase() })];
@@ -328,7 +334,17 @@ function passwordResetStart(repo, auth, jwtSecret, username, numberOfDigits, lif
328
334
  username: username.toLocaleLowerCase(),
329
335
  };
330
336
  code = generate(numberOfDigits);
337
+ pwdResetStartedAt = new Date().toISOString();
338
+ if (!passwordResetReusableTokens) return [3 /*break*/, 2];
331
339
  secret = jwtSecret + ":" + code;
340
+ return [3 /*break*/, 4];
341
+ case 2:
342
+ secret = jwtSecret + ":" + code + ":" + pwdResetStartedAt;
343
+ return [4 /*yield*/, repo.updateOne(user._id, { pwdResetStartedAt: pwdResetStartedAt })];
344
+ case 3:
345
+ _a.sent();
346
+ _a.label = 4;
347
+ case 4:
332
348
  options = {
333
349
  expiresIn: lifeTime,
334
350
  };
@@ -344,28 +360,36 @@ function passwordResetStart(repo, auth, jwtSecret, username, numberOfDigits, lif
344
360
  });
345
361
  }
346
362
  exports.passwordResetStart = passwordResetStart;
347
- function passwordResetComplete(repo, auth, jwtSecret, passwordResetToken, code, newPassword, createPasswordHashAndSaltMethod) {
363
+ function passwordResetComplete(repo, auth, jwtSecret, passwordResetToken, code, newPassword, createPasswordHashAndSaltMethod, passwordResetReusableTokens) {
364
+ if (passwordResetReusableTokens === void 0) { passwordResetReusableTokens = true; }
348
365
  return __awaiter(this, void 0, void 0, function () {
349
- var payload, secret, user, passwordAndSalt;
366
+ var payload, user, secret, passwordAndSalt;
350
367
  return __generator(this, function (_a) {
351
368
  switch (_a.label) {
352
369
  case 0:
353
- payload = { type: "", username: "" };
354
- try {
355
- secret = jwtSecret + ":" + code;
356
- payload = jsonwebtoken_1.default.verify(passwordResetToken, secret);
357
- }
358
- catch (ex) {
370
+ payload = jsonwebtoken_1.default.decode(passwordResetToken);
371
+ if (!payload || !payload.username)
359
372
  return [2 /*return*/, { status: "invalidCode" }];
360
- }
361
373
  return [4 /*yield*/, repo.getOne({ username: payload.username })];
362
374
  case 1:
363
375
  user = _a.sent();
364
- if (user == null) {
376
+ if (!user || user == null || user.authentificationMethod != "password") {
365
377
  return [2 /*return*/, { status: "userNotFound" }];
366
378
  }
367
- if (user.authentificationMethod != "password") {
368
- return [2 /*return*/, { status: "userNotFound" }];
379
+ if (passwordResetReusableTokens === true) {
380
+ secret = jwtSecret + ":" + code;
381
+ }
382
+ else {
383
+ if (!user.pwdResetStartedAt || user.pwdResetStartedAt === null) {
384
+ return [2 /*return*/, { status: "userNotFound" }];
385
+ }
386
+ secret = jwtSecret + ":" + code + ":" + user.pwdResetStartedAt;
387
+ }
388
+ try {
389
+ jsonwebtoken_1.default.verify(passwordResetToken, secret);
390
+ }
391
+ catch (ex) {
392
+ return [2 /*return*/, { status: "invalidCode" }];
369
393
  }
370
394
  passwordAndSalt = null;
371
395
  if (!(createPasswordHashAndSaltMethod == null)) return [3 /*break*/, 3];
@@ -386,6 +410,7 @@ function passwordResetComplete(repo, auth, jwtSecret, passwordResetToken, code,
386
410
  return [4 /*yield*/, repo.updateOne(user._id, {
387
411
  password: passwordAndSalt.hash,
388
412
  salt: passwordAndSalt.salt,
413
+ pwdResetStartedAt: null
389
414
  })];
390
415
  case 6:
391
416
  _a.sent();
@@ -20,20 +20,20 @@ export interface genericAuthContext {
20
20
  hash: string;
21
21
  salt: string;
22
22
  } | null>;
23
- }): Promise<UserCreateRes>;
23
+ }, onUserCreated?: (user: User) => Promise<void>): Promise<UserCreateRes>;
24
24
  changePassword(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, userId: string, newPassword: string, createPasswordHashAndSaltMethod?: {
25
25
  (password: string): Promise<{
26
26
  hash: string;
27
27
  salt: string;
28
28
  } | null>;
29
29
  }): Promise<UserPasswordChangeRes>;
30
- passwordResetStart(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, jwtSecret: string, username: string, numberOfDigits?: number, lifeTime?: string): Promise<UserPasswordResetStartRes>;
30
+ passwordResetStart(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, jwtSecret: string, username: string, numberOfDigits?: number, lifeTime?: string, passwordResetReusableTokens?: boolean): Promise<UserPasswordResetStartRes>;
31
31
  passwordResetComplete(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, jwtSecret: string, passwordResetToken: string, code: string, newPassword: string, createPasswordHashAndSaltMethod?: {
32
32
  (password: string): Promise<{
33
33
  hash: string;
34
34
  salt: string;
35
35
  } | null>;
36
- }): Promise<UserPasswordResetCompleteRes>;
36
+ }, passwordResetReusableTokens?: boolean): Promise<UserPasswordResetCompleteRes>;
37
37
  repoName: string;
38
38
  passwordResetSettings?: UserPasswordResetSettings;
39
39
  createPasswordHashAndSaltMethod?: {
@@ -50,5 +50,8 @@ export interface genericAuthContext {
50
50
  onSuccessfulLogin?: {
51
51
  (user: User): Promise<void>;
52
52
  };
53
+ onUserCreated?: {
54
+ (user: User): Promise<void>;
55
+ };
53
56
  };
54
57
  }
@@ -5,6 +5,7 @@ export interface GenericAuthPluginOptions {
5
5
  repoName: string;
6
6
  enableRoutes?: boolean;
7
7
  enablePasswordReset?: boolean;
8
+ passwordResetReusableTokens?: boolean;
8
9
  enablePushNotificationTokens?: boolean;
9
10
  passwordResetSettings?: UserPasswordResetSettings;
10
11
  enableUserCreation?: boolean;
@@ -27,6 +28,9 @@ export interface GenericAuthPluginOptions {
27
28
  onSuccessfulLogin?: {
28
29
  (user: User): Promise<void>;
29
30
  };
31
+ onUserCreated?: {
32
+ (user: User): Promise<void>;
33
+ };
30
34
  /**
31
35
  * If true, when a new device is registered, all other devices identified by `deviceId`
32
36
  * will be deregistered to avoid duplicate notifications.
@@ -64,7 +64,7 @@ var userCreateHandler = function (_a) {
64
64
  if (!re.test(username)) {
65
65
  return [2 /*return*/, flink_1.badRequest("Username does not meet requirements", "usernameError")];
66
66
  }
67
- return [4 /*yield*/, ctx.plugins.genericAuthPlugin.createUser(repo, ctx.auth, username.toLocaleLowerCase(), password, authentificationMethod, roles, profile, ctx.plugins.genericAuthPlugin.createPasswordHashAndSaltMethod)];
67
+ return [4 /*yield*/, ctx.plugins.genericAuthPlugin.createUser(repo, ctx.auth, username.toLocaleLowerCase(), password, authentificationMethod, roles, profile, ctx.plugins[pluginName].createPasswordHashAndSaltMethod, ctx.plugins[pluginName].onSuccessfulLogin)];
68
68
  case 1:
69
69
  createUserResponse = _c.sent();
70
70
  if (createUserResponse.status != "success") {
@@ -51,7 +51,7 @@ var postPasswordResetCompleteHandler = function (_a) {
51
51
  return [2 /*return*/, flink_1.internalServerError("Password reset settings is needed to use password-reset")];
52
52
  }
53
53
  jwtSecret = ctx.plugins[pluginName].passwordResetSettings.code.jwtSecret;
54
- return [4 /*yield*/, ctx.plugins.genericAuthPlugin.passwordResetComplete(repo, ctx.auth, jwtSecret, req.body.passwordResetToken, req.body.code, req.body.password, ctx.plugins.genericAuthPlugin.createPasswordHashAndSaltMethod)];
54
+ return [4 /*yield*/, ctx.plugins.genericAuthPlugin.passwordResetComplete(repo, ctx.auth, jwtSecret, req.body.passwordResetToken, req.body.code, req.body.password, ctx.plugins.genericAuthPlugin.createPasswordHashAndSaltMethod, ctx.plugins.genericAuthPlugin.passwordResetSettings.passwordResetReusableTokens)];
55
55
  case 1:
56
56
  resp = _b.sent();
57
57
  switch (resp.status) {
@@ -60,7 +60,7 @@ var postPasswordResetStartHandler = function (_a) {
60
60
  return [2 /*return*/, flink_1.internalServerError("Password reset settings is needed to use /password/reset")];
61
61
  }
62
62
  _b = genericAuthPlugin.passwordResetSettings.code, jwtSecret = _b.jwtSecret, numberOfDigits = _b.numberOfDigits, lifeTime = _b.lifeTime;
63
- return [4 /*yield*/, genericAuthPlugin.passwordResetStart(repo, ctx.auth, jwtSecret, req.body.username, numberOfDigits, lifeTime)];
63
+ return [4 /*yield*/, genericAuthPlugin.passwordResetStart(repo, ctx.auth, jwtSecret, req.body.username, numberOfDigits, lifeTime, genericAuthPlugin.passwordResetSettings.passwordResetReusableTokens)];
64
64
  case 1:
65
65
  resp = _d.sent();
66
66
  if (resp.status != "success") {
@@ -5,6 +5,7 @@ export interface User {
5
5
  username: string;
6
6
  password?: string;
7
7
  salt?: string;
8
+ pwdResetStartedAt?: string;
8
9
  roles: string[];
9
10
  authentificationMethod: "password" | "sms";
10
11
  profile: UserProfile;
@@ -20,5 +20,6 @@ export interface UserPasswordResetSettings {
20
20
  enablePasswordResetForm?: boolean;
21
21
  passwordResetForm?: string;
22
22
  resetPasswordFormBaseUrl?: string;
23
+ passwordResetReusableTokens?: boolean;
23
24
  }
24
25
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/generic-auth-plugin",
3
- "version": "0.11.16",
3
+ "version": "0.11.18",
4
4
  "description": "Flink plugin that provides a generic user authentification solution.",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\"",
@@ -30,5 +30,5 @@
30
30
  "ts-node": "^9.1.1",
31
31
  "typescript": "^4.2.4"
32
32
  },
33
- "gitHead": "639288292e47f6d1d6329e2929d0cefba3b3e603"
33
+ "gitHead": "de631fa7613a2ae9830d22d954d36265d1ebf9ba"
34
34
  }
@@ -50,6 +50,9 @@ export async function createUser(
50
50
  profile: UserProfile,
51
51
  createPasswordHashAndSaltMethod?: {
52
52
  (password: string): Promise<{ hash: string; salt: string } | null>;
53
+ },
54
+ onUserCreated?: {
55
+ (user: User): Promise<void>;
53
56
  }
54
57
  ): Promise<UserCreateRes> {
55
58
  if (!roles.includes("user")) roles.push("user");
@@ -87,6 +90,10 @@ export async function createUser(
87
90
 
88
91
  const user = await repo.create(userData);
89
92
 
93
+ if (onUserCreated) {
94
+ await onUserCreated(user);
95
+ }
96
+
90
97
  const token = await auth.createToken({ username: username.toLowerCase(), _id: user._id }, roles);
91
98
 
92
99
  if (user.authentificationMethod == "sms") {
@@ -285,7 +292,8 @@ export async function passwordResetStart(
285
292
  jwtSecret: string,
286
293
  username: string,
287
294
  numberOfDigits?: number,
288
- lifeTime?: string
295
+ lifeTime?: string,
296
+ passwordResetReusableTokens: boolean = true
289
297
  ): Promise<UserPasswordResetStartRes> {
290
298
  const user = await repo.getOne({ username: username.toLowerCase() });
291
299
 
@@ -295,8 +303,6 @@ export async function passwordResetStart(
295
303
  };
296
304
  const fakeToken = jsonwebtoken.sign(fakepayload, "fake_payload", { expiresIn: lifeTime });
297
305
 
298
-
299
-
300
306
  if (user == null) {
301
307
  return { status: "userNotFound", passwordResetToken : fakeToken };
302
308
  }
@@ -314,7 +320,14 @@ export async function passwordResetStart(
314
320
  };
315
321
  const code = generate(numberOfDigits);
316
322
 
317
- const secret = jwtSecret + ":" + code;
323
+ const pwdResetStartedAt = new Date().toISOString();
324
+ let secret;
325
+ if(passwordResetReusableTokens) {
326
+ secret = jwtSecret + ":" + code;
327
+ } else {
328
+ secret = jwtSecret + ":" + code + ":" + pwdResetStartedAt;
329
+ await repo.updateOne(user._id, { pwdResetStartedAt });
330
+ }
318
331
 
319
332
  const options: jsonwebtoken.SignOptions = {
320
333
  expiresIn: lifeTime,
@@ -339,25 +352,38 @@ export async function passwordResetComplete(
339
352
  newPassword: string,
340
353
  createPasswordHashAndSaltMethod?: {
341
354
  (password: string): Promise<{ hash: string; salt: string } | null>;
342
- }
355
+ },
356
+ passwordResetReusableTokens: boolean = true
343
357
  ): Promise<UserPasswordResetCompleteRes> {
344
- let payload: { type: string; username: string } = { type: "", username: "" };
345
- try {
346
- const secret = jwtSecret + ":" + code;
347
- payload = <{ type: string; username: string }>jsonwebtoken.verify(passwordResetToken, secret);
348
- } catch (ex) {
358
+
359
+ const payload = <{ username:string }>jsonwebtoken.decode(passwordResetToken);
360
+
361
+ if(!payload || !payload.username)
349
362
  return { status: "invalidCode" };
350
- }
351
363
 
352
364
  const user = await repo.getOne({ username: payload.username });
353
- if (user == null) {
365
+
366
+ if (!user || user == null || user.authentificationMethod != "password") {
354
367
  return { status: "userNotFound" };
355
368
  }
356
369
 
357
- if (user.authentificationMethod != "password") {
358
- return { status: "userNotFound" };
370
+ let secret;
371
+ if (passwordResetReusableTokens === true) {
372
+ secret = jwtSecret + ":" + code;
373
+ } else {
374
+ if (!user.pwdResetStartedAt || user.pwdResetStartedAt === null) {
375
+ return { status: "userNotFound" };
376
+ }
377
+ secret = jwtSecret + ":" + code + ":" + user.pwdResetStartedAt;
359
378
  }
360
379
 
380
+ try {
381
+ jsonwebtoken.verify(passwordResetToken, secret);
382
+ } catch (ex) {
383
+ return { status: "invalidCode" };
384
+ }
385
+
386
+
361
387
  let passwordAndSalt = null;
362
388
 
363
389
  if (createPasswordHashAndSaltMethod == null) {
@@ -375,6 +401,7 @@ export async function passwordResetComplete(
375
401
  await repo.updateOne(user._id, {
376
402
  password: passwordAndSalt.hash,
377
403
  salt: passwordAndSalt.salt,
404
+ pwdResetStartedAt: null
378
405
  });
379
406
 
380
407
  return { status: "success" };
@@ -1,32 +1,73 @@
1
- import { FlinkRepo } from "@flink-app/flink";
1
+ import { FlinkRepo } from "@flink-app/flink";
2
2
  import { JwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
3
3
  import { User } from "./schemas/User";
4
4
  import { UserCreateRes } from "./schemas/UserCreateRes";
5
5
  import { UserLoginRes } from "./schemas/UserLoginRes";
6
6
  import { UserProfile } from "./schemas/UserProfile";
7
7
  import { UserPasswordChangeRes } from "./schemas/UserPasswordChangeRes";
8
- import { UserPasswordResetSettings} from "./schemas/UserPasswordResetSettings"
9
- import { UserPasswordResetStartRes } from "./schemas/UserPasswordResetStartRes"
10
- import { UserPasswordResetCompleteRes } from "./schemas/UserPasswordResetCompleteRes"
8
+ import { UserPasswordResetSettings } from "./schemas/UserPasswordResetSettings";
9
+ import { UserPasswordResetStartRes } from "./schemas/UserPasswordResetStartRes";
10
+ import { UserPasswordResetCompleteRes } from "./schemas/UserPasswordResetCompleteRes";
11
11
  import { GenericAuthsmsOptions } from "./genericAuthPluginOptions";
12
12
 
13
- export interface genericAuthContext{
14
- genericAuthPlugin : {
15
-
16
-
17
- loginUser( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, username : string, password? : string, validatePasswordMethod? : { (password : string, hash : string, salt : string) : Promise<boolean> }, smsOptions? : GenericAuthsmsOptions, onSuccessfulLogin?: (user:User) => Promise<void> ) : Promise<UserLoginRes>,
18
- loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token : string, code : string, jwtSecret : string) : Promise<UserLoginRes>,
19
- createUser( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, username : string, password : string, authentificationMethod : "password" | "sms", roles : string[], profile : UserProfile, createPasswordHashAndSaltMethod? : { (password : string) : Promise<{ hash: string; salt: string;} | null> } ) : Promise<UserCreateRes>,
20
- changePassword( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, userId : string, newPassword : string, createPasswordHashAndSaltMethod? : { (password : string) : Promise<{ hash: string; salt: string;} | null> } ) : Promise<UserPasswordChangeRes>,
21
- passwordResetStart( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, jwtSecret : string, username : string, numberOfDigits? : number, lifeTime? : string) : Promise<UserPasswordResetStartRes>,
22
- passwordResetComplete( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, jwtSecret : string, passwordResetToken : string, code : string, newPassword : string, createPasswordHashAndSaltMethod? : { (password : string) : Promise<{ hash: string; salt: string;} | null> } ) : Promise<UserPasswordResetCompleteRes>
23
- repoName : string,
24
- passwordResetSettings? : UserPasswordResetSettings,
25
- createPasswordHashAndSaltMethod? : { (password : string) : Promise<{ hash: string; salt: string;} | null> },
26
- validatePasswordMethod? : { (password : string, hash : string, salt : string) : Promise<boolean> },
27
- usernameFormat : RegExp
28
- smsOptions? : GenericAuthsmsOptions,
29
- onSuccessfulLogin?: { (user:User) : Promise<void> }
30
- }
31
-
13
+ export interface genericAuthContext {
14
+ genericAuthPlugin: {
15
+ loginUser(
16
+ repo: FlinkRepo<any, User>,
17
+ auth: JwtAuthPlugin,
18
+ username: string,
19
+ password?: string,
20
+ validatePasswordMethod?: { (password: string, hash: string, salt: string): Promise<boolean> },
21
+ smsOptions?: GenericAuthsmsOptions,
22
+ onSuccessfulLogin?: (user: User) => Promise<void>
23
+ ): Promise<UserLoginRes>;
24
+ loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token: string, code: string, jwtSecret: string): Promise<UserLoginRes>;
25
+ createUser(
26
+ repo: FlinkRepo<any, User>,
27
+ auth: JwtAuthPlugin,
28
+ username: string,
29
+ password: string,
30
+ authentificationMethod: "password" | "sms",
31
+ roles: string[],
32
+ profile: UserProfile,
33
+ createPasswordHashAndSaltMethod?: {
34
+ (password: string): Promise<{ hash: string; salt: string } | null>;
35
+ },
36
+ onUserCreated?: (user: User) => Promise<void>
37
+ ): Promise<UserCreateRes>;
38
+ changePassword(
39
+ repo: FlinkRepo<any, User>,
40
+ auth: JwtAuthPlugin,
41
+ userId: string,
42
+ newPassword: string,
43
+ createPasswordHashAndSaltMethod?: { (password: string): Promise<{ hash: string; salt: string } | null> }
44
+ ): Promise<UserPasswordChangeRes>;
45
+ passwordResetStart(
46
+ repo: FlinkRepo<any, User>,
47
+ auth: JwtAuthPlugin,
48
+ jwtSecret: string,
49
+ username: string,
50
+ numberOfDigits?: number,
51
+ lifeTime?: string,
52
+ passwordResetReusableTokens?: boolean
53
+ ): Promise<UserPasswordResetStartRes>;
54
+ passwordResetComplete(
55
+ repo: FlinkRepo<any, User>,
56
+ auth: JwtAuthPlugin,
57
+ jwtSecret: string,
58
+ passwordResetToken: string,
59
+ code: string,
60
+ newPassword: string,
61
+ createPasswordHashAndSaltMethod?: { (password: string): Promise<{ hash: string; salt: string } | null> },
62
+ passwordResetReusableTokens?: boolean
63
+ ): Promise<UserPasswordResetCompleteRes>;
64
+ repoName: string;
65
+ passwordResetSettings?: UserPasswordResetSettings;
66
+ createPasswordHashAndSaltMethod?: { (password: string): Promise<{ hash: string; salt: string } | null> };
67
+ validatePasswordMethod?: { (password: string, hash: string, salt: string): Promise<boolean> };
68
+ usernameFormat: RegExp;
69
+ smsOptions?: GenericAuthsmsOptions;
70
+ onSuccessfulLogin?: { (user: User): Promise<void> };
71
+ onUserCreated?: { (user: User): Promise<void> };
72
+ };
32
73
  }
@@ -5,6 +5,7 @@ export interface GenericAuthPluginOptions {
5
5
  repoName: string;
6
6
  enableRoutes?: boolean;
7
7
  enablePasswordReset?: boolean;
8
+ passwordResetReusableTokens?: boolean;
8
9
  enablePushNotificationTokens?: boolean;
9
10
  passwordResetSettings?: UserPasswordResetSettings;
10
11
  enableUserCreation?: boolean;
@@ -24,6 +25,9 @@ export interface GenericAuthPluginOptions {
24
25
  onSuccessfulLogin?: {
25
26
  (user: User): Promise<void>;
26
27
  };
28
+ onUserCreated?: {
29
+ (user: User): Promise<void>;
30
+ };
27
31
  /**
28
32
  * If true, when a new device is registered, all other devices identified by `deviceId`
29
33
  * will be deregistered to avoid duplicate notifications.
@@ -35,7 +35,8 @@ const userCreateHandler: Handler<FlinkContext<genericAuthContext>, UserCreateReq
35
35
  authentificationMethod,
36
36
  roles,
37
37
  profile,
38
- ctx.plugins.genericAuthPlugin.createPasswordHashAndSaltMethod
38
+ (<any>ctx.plugins)[pluginName].createPasswordHashAndSaltMethod,
39
+ (<any>ctx.plugins)[pluginName].onSuccessfulLogin
39
40
  );
40
41
  if (createUserResponse.status != "success") {
41
42
  switch (createUserResponse.status) {
@@ -34,7 +34,8 @@ const postPasswordResetCompleteHandler: Handler<
34
34
  req.body.passwordResetToken,
35
35
  req.body.code,
36
36
  req.body.password,
37
- ctx.plugins.genericAuthPlugin.createPasswordHashAndSaltMethod
37
+ ctx.plugins.genericAuthPlugin.createPasswordHashAndSaltMethod,
38
+ ctx.plugins.genericAuthPlugin.passwordResetSettings.passwordResetReusableTokens
38
39
  );
39
40
 
40
41
  switch (resp.status) {
@@ -27,7 +27,7 @@ const postPasswordResetStartHandler: Handler<
27
27
 
28
28
  const { jwtSecret, numberOfDigits, lifeTime } = genericAuthPlugin.passwordResetSettings.code;
29
29
 
30
- const resp = await genericAuthPlugin.passwordResetStart(repo, <JwtAuthPlugin>ctx.auth, jwtSecret, req.body.username, numberOfDigits, lifeTime);
30
+ const resp = await genericAuthPlugin.passwordResetStart(repo, <JwtAuthPlugin>ctx.auth, jwtSecret, req.body.username, numberOfDigits, lifeTime, genericAuthPlugin.passwordResetSettings.passwordResetReusableTokens);
31
31
 
32
32
  if (resp.status != "success") {
33
33
  return { data: { status: "success", passwordResetToken: resp.passwordResetToken } };
@@ -8,6 +8,7 @@ export interface User {
8
8
  password?: string;
9
9
  salt? : string;
10
10
 
11
+ pwdResetStartedAt?: string;
11
12
  roles: string[];
12
13
 
13
14
  authentificationMethod : "password" | "sms";
@@ -23,5 +23,6 @@ export interface UserPasswordResetSettings {
23
23
  enablePasswordResetForm?: boolean;
24
24
  passwordResetForm?: string;
25
25
  resetPasswordFormBaseUrl?: string;
26
+ passwordResetReusableTokens?: boolean;
26
27
 
27
28
  }