@flink-app/generic-auth-plugin 0.12.1-alpha.3 → 0.12.1-alpha.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.flink/generatedHandlers.ts +1 -1
  2. package/.flink/generatedJobs.ts +1 -1
  3. package/.flink/generatedRepos.ts +1 -1
  4. package/.flink/schemas/schemas.json +7 -2
  5. package/.flink/schemas/schemas.ts +1 -1
  6. package/.flink/start.ts +2 -1
  7. package/dist/.flink/generatedHandlers.js +1 -1
  8. package/dist/.flink/generatedJobs.js +1 -1
  9. package/dist/.flink/generatedRepos.js +1 -1
  10. package/dist/.flink/schemas/schemas.json +7 -2
  11. package/dist/.flink/start.d.ts +2 -0
  12. package/dist/.flink/start.js +2 -1
  13. package/dist/src/coreFunctions.d.ts +5 -5
  14. package/dist/src/coreFunctions.js +22 -4
  15. package/dist/src/genericAuthContext.d.ts +5 -5
  16. package/dist/src/genericAuthPluginOptions.d.ts +2 -1
  17. package/dist/src/handlers/UserCreate.js +4 -4
  18. package/dist/src/handlers/UserLogin.js +39 -7
  19. package/dist/src/handlers/UserPasswordResetComplete.js +1 -1
  20. package/dist/src/handlers/UserPushRegisterToken.js +1 -1
  21. package/dist/src/schemas/User.d.ts +2 -1
  22. package/dist/src/schemas/UserCreateReq.d.ts +2 -1
  23. package/dist/src/schemas/UserPasswordResetCompleteRes.d.ts +4 -0
  24. package/package.json +8 -8
  25. package/readme.md +627 -565
  26. package/src/coreFunctions.ts +29 -7
  27. package/src/genericAuthContext.ts +7 -5
  28. package/src/genericAuthPluginOptions.ts +2 -1
  29. package/src/handlers/UserCreate.ts +3 -2
  30. package/src/handlers/UserLogin.ts +56 -31
  31. package/src/handlers/UserPushRegisterToken.ts +1 -1
  32. package/src/schemas/User.ts +2 -1
  33. package/src/schemas/UserCreateReq.ts +5 -4
  34. package/src/schemas/UserPasswordResetCompleteRes.ts +8 -3
  35. package/CLAUDE.md +0 -32
@@ -1,4 +1,4 @@
1
- import { FlinkRepo, FlinkAuthUser, log } from "@flink-app/flink";
1
+ import { FlinkRepo, FlinkAuthUser, log, FlinkRequest } from "@flink-app/flink";
2
2
  import { JwtAuthPlugin, jwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
3
3
 
4
4
  import { User } from "./schemas/User";
@@ -45,7 +45,7 @@ export async function createUser(
45
45
  auth: JwtAuthPlugin,
46
46
  username: string,
47
47
  password: string,
48
- authentificationMethod: "password" | "sms",
48
+ authentificationMethod: "password" | "sms" | "bankid",
49
49
  roles: string[],
50
50
  profile: UserProfile,
51
51
  createPasswordHashAndSaltMethod?: {
@@ -53,7 +53,8 @@ export async function createUser(
53
53
  },
54
54
  onUserCreated?: {
55
55
  (user: User): Promise<void>;
56
- }
56
+ },
57
+ personalNumber?: string
57
58
  ): Promise<UserCreateRes> {
58
59
  if (!roles.includes("user")) roles.push("user");
59
60
 
@@ -71,6 +72,17 @@ export async function createUser(
71
72
  pushNotificationTokens: [],
72
73
  };
73
74
 
75
+ if (personalNumber) {
76
+ userData.personalNumber = personalNumber;
77
+ }
78
+
79
+ if (authentificationMethod == "bankid") {
80
+ if (!personalNumber) {
81
+ log.warn("BankID login requested but no personal number found for user");
82
+ return { status: "error" };
83
+ }
84
+ }
85
+
74
86
  if (authentificationMethod == "password") {
75
87
  let passwordAndSalt = null;
76
88
  if (createPasswordHashAndSaltMethod != null) {
@@ -152,8 +164,9 @@ export async function loginUser(
152
164
  },
153
165
  smsOptions?: GenericAuthsmsOptions,
154
166
  onSuccessfulLogin?: {
155
- (user: User): Promise<void>;
156
- }
167
+ (user: User, req?: FlinkRequest): Promise<void>;
168
+ },
169
+ req?: FlinkRequest
157
170
  ): Promise<UserLoginRes> {
158
171
  const user = await repo.getOne({ username: username.toLowerCase() });
159
172
  if (user == null) {
@@ -205,12 +218,21 @@ export async function loginUser(
205
218
  validationToken: token,
206
219
  };
207
220
  }
221
+ if (user.authentificationMethod == "bankid") {
222
+ if (!user.personalNumber) {
223
+ log.warn("BankID login requested but no personal number found for user");
224
+ return { status: "failed" };
225
+ }
226
+
227
+ log.warn("BankID login required to be handled in other way, i.e. using flink bankid plugin");
228
+ return { status: "failed" };
229
+ }
208
230
 
209
231
  if (valid) {
210
232
  const token = await auth.createToken({ username: username.toLowerCase(), _id: user._id }, user.roles);
211
233
 
212
234
  if (onSuccessfulLogin) {
213
- await onSuccessfulLogin(user);
235
+ await onSuccessfulLogin(user, req);
214
236
  }
215
237
 
216
238
  return {
@@ -382,7 +404,7 @@ export async function passwordResetComplete(
382
404
  pwdResetStartedAt: null,
383
405
  });
384
406
 
385
- return { status: "success" };
407
+ return { status: "success", user };
386
408
  }
387
409
 
388
410
  function generate(n: number): string {
@@ -1,4 +1,4 @@
1
- import { FlinkRepo } from "@flink-app/flink";
1
+ import { FlinkRepo, FlinkRequest } 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";
@@ -19,7 +19,8 @@ export interface genericAuthContext {
19
19
  password?: string,
20
20
  validatePasswordMethod?: { (password: string, hash: string, salt: string): Promise<boolean> },
21
21
  smsOptions?: GenericAuthsmsOptions,
22
- onSuccessfulLogin?: (user: User) => Promise<void>
22
+ onSuccessfulLogin?: (user: User, req?: FlinkRequest) => Promise<void>,
23
+ req?: FlinkRequest
23
24
  ): Promise<UserLoginRes>;
24
25
  loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token: string, code: string, jwtSecret: string): Promise<UserLoginRes>;
25
26
  createUser(
@@ -27,13 +28,14 @@ export interface genericAuthContext {
27
28
  auth: JwtAuthPlugin,
28
29
  username: string,
29
30
  password: string,
30
- authentificationMethod: "password" | "sms",
31
+ authentificationMethod: "password" | "sms" | "bankid",
31
32
  roles: string[],
32
33
  profile: UserProfile,
33
34
  createPasswordHashAndSaltMethod?: {
34
35
  (password: string): Promise<{ hash: string; salt: string } | null>;
35
36
  },
36
- onUserCreated?: (user: User) => Promise<void>
37
+ onUserCreated?: (user: User) => Promise<void>,
38
+ personalNumber?: string
37
39
  ): Promise<UserCreateRes>;
38
40
  changePassword(
39
41
  repo: FlinkRepo<any, User>,
@@ -67,7 +69,7 @@ export interface genericAuthContext {
67
69
  validatePasswordMethod?: { (password: string, hash: string, salt: string): Promise<boolean> };
68
70
  usernameFormat: RegExp;
69
71
  smsOptions?: GenericAuthsmsOptions;
70
- onSuccessfulLogin?: { (user: User): Promise<void> };
72
+ onSuccessfulLogin?: { (user: User, req?: FlinkRequest): Promise<void> };
71
73
  onUserCreated?: { (user: User): Promise<void> };
72
74
  };
73
75
  }
@@ -1,3 +1,4 @@
1
+ import { FlinkRequest } from "@flink-app/flink";
1
2
  import { User } from "./schemas/User";
2
3
  import { UserPasswordResetSettings } from "./schemas/UserPasswordResetSettings";
3
4
  import { client as smsClient } from "@flink-app/sms-plugin";
@@ -23,7 +24,7 @@ export interface GenericAuthPluginOptions {
23
24
  usernameFormat?: RegExp;
24
25
  sms?: GenericAuthsmsOptions;
25
26
  onSuccessfulLogin?: {
26
- (user: User): Promise<void>;
27
+ (user: User, req?: FlinkRequest): Promise<void>;
27
28
  };
28
29
  onUserCreated?: {
29
30
  (user: User): Promise<void>;
@@ -5,7 +5,7 @@ import { UserCreateReq } from "../schemas/UserCreateReq";
5
5
  import { UserCreateRes } from "../schemas/UserCreateRes";
6
6
 
7
7
  const userCreateHandler: Handler<FlinkContext<genericAuthContext>, UserCreateReq, UserCreateRes> = async ({ ctx, req, origin }) => {
8
- let { password, username, authentificationMethod, profile } = req.body;
8
+ let { password, username, authentificationMethod, profile, personalNumber } = req.body;
9
9
  if (authentificationMethod == null) {
10
10
  authentificationMethod = "password";
11
11
  }
@@ -36,7 +36,8 @@ const userCreateHandler: Handler<FlinkContext<genericAuthContext>, UserCreateReq
36
36
  roles,
37
37
  profile,
38
38
  (<any>ctx.plugins)[pluginName].createPasswordHashAndSaltMethod,
39
- (<any>ctx.plugins)[pluginName].onUserCreated
39
+ (<any>ctx.plugins)[pluginName].onUserCreated,
40
+ personalNumber
40
41
  );
41
42
  if (createUserResponse.status != "success") {
42
43
  switch (createUserResponse.status) {
@@ -1,41 +1,66 @@
1
- import { FlinkContext, Handler, unauthorized } from "@flink-app/flink";
1
+ import { FlinkContext, FlinkResponse, Handler, internalServerError, log, unauthorized } from "@flink-app/flink";
2
+ import { JwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
2
3
  import { genericAuthContext } from "../genericAuthContext";
3
4
  import { UserLoginReq } from "../schemas/UserLoginReq";
4
5
  import { UserLoginRes } from "../schemas/UserLoginRes";
5
- import { JwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
6
6
 
7
- const userLoginHandler: Handler<
8
- FlinkContext<genericAuthContext>,
9
- UserLoginReq,
10
- UserLoginRes
11
- > = async ({ ctx, req, origin }) => {
12
- let pluginName = origin || "genericAuthPlugin";
13
- let repo = ctx.repos[(<any>ctx.plugins)[pluginName].repoName];
14
-
15
- const loginRespons = await ctx.plugins.genericAuthPlugin.loginUser(
16
- repo,
17
- <JwtAuthPlugin>ctx.auth,
18
- req.body.username,
19
- req.body.password,
20
- ctx.plugins.genericAuthPlugin.validatePasswordMethod,
21
- (<any>ctx.plugins)[pluginName].smsOptions,
22
- (<any>ctx.plugins)[pluginName].onSuccessfulLogin
23
- );
24
-
25
- if (loginRespons.status != "success") {
26
- switch (loginRespons.status) {
27
- case "failed":
28
- return unauthorized(
29
- "Invalid username or password",
30
- loginRespons.status
7
+ const userLoginHandler: Handler<FlinkContext<genericAuthContext>, UserLoginReq, UserLoginRes> = async ({ ctx, req, origin }) => {
8
+ let pluginName = origin || "genericAuthPlugin";
9
+ let repo = ctx.repos[(<any>ctx.plugins)[pluginName].repoName];
10
+
11
+ let loginResponse: UserLoginRes | undefined = undefined;
12
+
13
+ try {
14
+ loginResponse = await ctx.plugins.genericAuthPlugin.loginUser(
15
+ repo,
16
+ <JwtAuthPlugin>ctx.auth,
17
+ req.body.username,
18
+ req.body.password,
19
+ ctx.plugins.genericAuthPlugin.validatePasswordMethod,
20
+ (<any>ctx.plugins)[pluginName].smsOptions,
21
+ (<any>ctx.plugins)[pluginName].onSuccessfulLogin,
22
+ req
31
23
  );
24
+ } catch (error: any) {
25
+ // Convert any thrown error that conforms to flink error structure to a proper response
26
+ // Note that any auth failures would not have been thrown, but returned as part of loginResponse
27
+ // but with this it is possible to throw errors from callbacks like onSuccessfulLogin
28
+ if (isFlinkError(error)) {
29
+ log.debug("Caught FlinkError in userLoginHandler:", error);
30
+ return {
31
+ status: error.status,
32
+ error: {
33
+ id: error.error.id,
34
+ title: error.error.title,
35
+ code: error.error.code,
36
+ detail: error.error.detail,
37
+ },
38
+ } as FlinkResponse;
39
+ }
40
+
41
+ // For other errors, return a generic 500 response
42
+ log.error("Error in userLoginHandler:", error);
43
+ return internalServerError();
32
44
  }
33
- }
34
45
 
35
- return {
36
- data: loginRespons,
37
- status: 200,
38
- };
46
+ if (loginResponse?.status != "success") {
47
+ switch (loginResponse?.status) {
48
+ case "failed":
49
+ return unauthorized("Invalid username or password", loginResponse.status);
50
+ }
51
+ }
52
+
53
+ return {
54
+ data: loginResponse,
55
+ status: 200,
56
+ };
39
57
  };
40
58
 
41
59
  export default userLoginHandler;
60
+
61
+ function isFlinkError(res: any) {
62
+ if (res && res.status && typeof res.status === "number" && res.error && res.error.id) {
63
+ return true;
64
+ }
65
+ return false;
66
+ }
@@ -43,7 +43,7 @@ const postUserPushRegisterTokenHandler: Handler<FlinkContext<genericAuthContext>
43
43
  if (deregisterOtherDevices) {
44
44
  const otherRegistrations = <User[]>await repo.findAll({
45
45
  $or: [{ "pushNotificationTokens.deviceId": req.body.deviceId }, { "pushNotificationTokens.token": req.body.token }],
46
- _id: { $ne: user._id },
46
+ _id: { $ne: repo.buildId(user._id) },
47
47
  });
48
48
 
49
49
  log.debug(`Found ${otherRegistrations.length} other registrations for device ${req.body.deviceId} or token ${req.body.token}`);
@@ -4,6 +4,7 @@ import { UserProfile } from "./UserProfile";
4
4
  export interface User {
5
5
  _id: string;
6
6
  username: string;
7
+ personalNumber?: string;
7
8
 
8
9
  password?: string;
9
10
  salt?: string;
@@ -11,7 +12,7 @@ export interface User {
11
12
  pwdResetStartedAt?: string | null;
12
13
  roles: string[];
13
14
 
14
- authentificationMethod: "password" | "sms";
15
+ authentificationMethod: "password" | "sms" | "bankid";
15
16
  profile: UserProfile;
16
17
  pushNotificationTokens: Array<PushNotificationToken>;
17
18
  }
@@ -1,8 +1,9 @@
1
1
  import { UserProfile } from "./UserProfile";
2
2
 
3
- export interface UserCreateReq{
3
+ export interface UserCreateReq {
4
4
  username: string;
5
5
  password?: string;
6
- authentificationMethod? : "password" | "sms"
7
- profile? : UserProfile
8
- }
6
+ personalNumber?: string;
7
+ authentificationMethod?: "password" | "sms" | "bankid";
8
+ profile?: UserProfile;
9
+ }
@@ -1,3 +1,8 @@
1
- export interface UserPasswordResetCompleteRes{
2
- status : "success" | "userNotFound" | "invalidCode" | "passwordError";
3
- }
1
+ export interface UserPasswordResetCompleteRes {
2
+ status: "success" | "userNotFound" | "invalidCode" | "passwordError";
3
+
4
+ /**
5
+ * The user object is returned only if the status is "success".
6
+ */
7
+ user?: any;
8
+ }
package/CLAUDE.md DELETED
@@ -1,32 +0,0 @@
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