@flink-app/generic-auth-plugin 0.3.8 → 0.3.9

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,7 +1,8 @@
1
- // Generated Sun Oct 16 2022 11:48:22 GMT+0200 (Central European Summer Time)
1
+ // Generated Sun Oct 16 2022 13:16:37 GMT+0200 (Central European Summer 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";
5
+ import * as UserLoginByToken_0 from "../src/handlers/UserLoginByToken";
5
6
  import * as UserPasswordPut_0 from "../src/handlers/UserPasswordPut";
6
7
  import * as UserPasswordResetComplete_0 from "../src/handlers/UserPasswordResetComplete";
7
8
  import * as UserPasswordResetForm_0 from "../src/handlers/UserPasswordResetForm";
@@ -1,4 +1,4 @@
1
- // Generated Sun Oct 16 2022 11:48:22 GMT+0200 (Central European Summer Time)
1
+ // Generated Sun Oct 16 2022 13:16:37 GMT+0200 (Central European Summer Time)
2
2
  import { autoRegisteredRepos } from "@flink-app/flink";
3
3
  export const repos = [];
4
4
  autoRegisteredRepos.push(...repos);
@@ -127,6 +127,66 @@
127
127
  "status"
128
128
  ]
129
129
  },
130
+ "UserLoginByToken_7_ReqSchema": {
131
+ "type": "object",
132
+ "additionalProperties": false,
133
+ "properties": {
134
+ "token": {
135
+ "type": "string"
136
+ },
137
+ "code": {
138
+ "type": "string"
139
+ }
140
+ },
141
+ "required": [
142
+ "code",
143
+ "token"
144
+ ]
145
+ },
146
+ "UserLoginByToken_7_ResSchema": {
147
+ "type": "object",
148
+ "additionalProperties": false,
149
+ "properties": {
150
+ "status": {
151
+ "type": "string",
152
+ "enum": [
153
+ "success",
154
+ "failed",
155
+ "requiresValidation"
156
+ ]
157
+ },
158
+ "user": {
159
+ "type": "object",
160
+ "properties": {
161
+ "_id": {
162
+ "type": "string"
163
+ },
164
+ "username": {
165
+ "type": "string"
166
+ },
167
+ "token": {
168
+ "type": "string"
169
+ },
170
+ "profile": {
171
+ "$ref": "#/definitions/UserProfile"
172
+ }
173
+ },
174
+ "required": [
175
+ "_id",
176
+ "username",
177
+ "token",
178
+ "profile"
179
+ ],
180
+ "additionalProperties": false
181
+ },
182
+ "validationToken": {
183
+ "type": "string"
184
+ }
185
+ },
186
+ "required": [
187
+ "status"
188
+ ]
189
+ },
130
190
  "UserPasswordPut_7_ReqSchema": {
131
191
  "type": "object",
132
192
  "additionalProperties": false,
@@ -2,6 +2,7 @@ import { UserCreateReq } from "../../src/schemas/UserCreateReq";
2
2
  import { UserCreateRes } from "../../src/schemas/UserCreateRes";
3
3
  import { UserLoginReq } from "../../src/schemas/UserLoginReq";
4
4
  import { UserLoginRes } from "../../src/schemas/UserLoginRes";
5
+ import { UserLoginByTokenReq } from "../../src/schemas/UserLoginByTokenReq";
5
6
  import { UserPasswordChangeReq } from "../../src/schemas/UserPasswordChangeReq";
6
7
  import { UserPasswordChangeRes } from "../../src/schemas/UserPasswordChangeRes";
7
8
  import { UserPasswordResetCompleteReq } from "../../src/schemas/UserPasswordResetCompleteReq";
@@ -29,7 +30,7 @@ import { PutManagementUserRolesByUseridRes } from "../../src/schemas/Management/
29
30
  import { PutManagementUserUsernameByUseridReq } from "../../src/schemas/Management/PutUserUsernameByUseridReq";
30
31
  import { PutManagementUserUsernameByUseridRes } from "../../src/schemas/Management/PutUserUsernameByUseridRes";
31
32
 
32
- // Generated Sun Oct 16 2022 11:48:24 GMT+0200 (Central European Summer Time)
33
+ // Generated Sun Oct 16 2022 13:16:45 GMT+0200 (Central European Summer Time)
33
34
  export interface UserCreate_7_ReqSchema extends UserCreateReq {}
34
35
 
35
36
  export interface UserCreate_7_ResSchema extends UserCreateRes {}
@@ -38,6 +39,10 @@ export interface UserLogin_7_ReqSchema extends UserLoginReq {}
38
39
 
39
40
  export interface UserLogin_7_ResSchema extends UserLoginRes {}
40
41
 
42
+ export interface UserLoginByToken_7_ReqSchema extends UserLoginByTokenReq {}
43
+
44
+ export interface UserLoginByToken_7_ResSchema extends UserLoginRes {}
45
+
41
46
  export interface UserPasswordPut_7_ReqSchema extends UserPasswordChangeReq {}
42
47
 
43
48
  export interface UserPasswordPut_7_ResSchema extends UserPasswordChangeRes {}
package/.flink/start.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated Sun Oct 16 2022 11:48:22 GMT+0200 (Central European Summer Time)
1
+ // Generated Sun Oct 16 2022 13:16:38 GMT+0200 (Central European Summer Time)
2
2
  import "./generatedHandlers";
3
3
  import "./generatedRepos";
4
4
  import "../src/index";
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handlers = void 0;
4
- // Generated Sun Oct 16 2022 11:48:22 GMT+0200 (Central European Summer Time)
4
+ // Generated Sun Oct 16 2022 13:16:37 GMT+0200 (Central European Summer 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.repos = void 0;
4
- // Generated Sun Oct 16 2022 11:48:22 GMT+0200 (Central European Summer Time)
4
+ // Generated Sun Oct 16 2022 13:16:37 GMT+0200 (Central European Summer 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);
@@ -2,6 +2,7 @@ import { UserCreateReq } from "../../src/schemas/UserCreateReq";
2
2
  import { UserCreateRes } from "../../src/schemas/UserCreateRes";
3
3
  import { UserLoginReq } from "../../src/schemas/UserLoginReq";
4
4
  import { UserLoginRes } from "../../src/schemas/UserLoginRes";
5
+ import { UserLoginByTokenReq } from "../../src/schemas/UserLoginByTokenReq";
5
6
  import { UserPasswordChangeReq } from "../../src/schemas/UserPasswordChangeReq";
6
7
  import { UserPasswordChangeRes } from "../../src/schemas/UserPasswordChangeRes";
7
8
  import { UserPasswordResetCompleteReq } from "../../src/schemas/UserPasswordResetCompleteReq";
@@ -36,6 +37,10 @@ export interface UserLogin_7_ReqSchema extends UserLoginReq {
36
37
  }
37
38
  export interface UserLogin_7_ResSchema extends UserLoginRes {
38
39
  }
40
+ export interface UserLoginByToken_7_ReqSchema extends UserLoginByTokenReq {
41
+ }
42
+ export interface UserLoginByToken_7_ResSchema extends UserLoginRes {
43
+ }
39
44
  export interface UserPasswordPut_7_ReqSchema extends UserPasswordChangeReq {
40
45
  }
41
46
  export interface UserPasswordPut_7_ResSchema extends UserPasswordChangeRes {
@@ -127,6 +127,66 @@
127
127
  "status"
128
128
  ]
129
129
  },
130
+ "UserLoginByToken_7_ReqSchema": {
131
+ "type": "object",
132
+ "additionalProperties": false,
133
+ "properties": {
134
+ "token": {
135
+ "type": "string"
136
+ },
137
+ "code": {
138
+ "type": "string"
139
+ }
140
+ },
141
+ "required": [
142
+ "code",
143
+ "token"
144
+ ]
145
+ },
146
+ "UserLoginByToken_7_ResSchema": {
147
+ "type": "object",
148
+ "additionalProperties": false,
149
+ "properties": {
150
+ "status": {
151
+ "type": "string",
152
+ "enum": [
153
+ "success",
154
+ "failed",
155
+ "requiresValidation"
156
+ ]
157
+ },
158
+ "user": {
159
+ "type": "object",
160
+ "properties": {
161
+ "_id": {
162
+ "type": "string"
163
+ },
164
+ "username": {
165
+ "type": "string"
166
+ },
167
+ "token": {
168
+ "type": "string"
169
+ },
170
+ "profile": {
171
+ "$ref": "#/definitions/UserProfile"
172
+ }
173
+ },
174
+ "required": [
175
+ "_id",
176
+ "username",
177
+ "token",
178
+ "profile"
179
+ ],
180
+ "additionalProperties": false
181
+ },
182
+ "validationToken": {
183
+ "type": "string"
184
+ }
185
+ },
186
+ "required": [
187
+ "status"
188
+ ]
189
+ },
130
190
  "UserPasswordPut_7_ReqSchema": {
131
191
  "type": "object",
132
192
  "additionalProperties": false,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // Generated Sun Oct 16 2022 11:48:22 GMT+0200 (Central European Summer Time)
3
+ // Generated Sun Oct 16 2022 13:16:38 GMT+0200 (Central European Summer Time)
4
4
  require("./generatedHandlers");
5
5
  require("./generatedRepos");
6
6
  require("../src/index");
@@ -7,6 +7,7 @@ import { UserProfile } from "./schemas/UserProfile";
7
7
  import { UserPasswordChangeRes } from "./schemas/UserPasswordChangeRes";
8
8
  import { UserPasswordResetStartRes } from "./schemas/UserPasswordResetStartRes";
9
9
  import { UserPasswordResetCompleteRes } from "./schemas/UserPasswordResetCompleteRes";
10
+ import { GenericAuthsmsOptions } from "./genericAuthPluginOptions";
10
11
  export declare function getJtwTokenPlugin(secret: string, rolePermissions?: {
11
12
  [role: string]: string[];
12
13
  }, passwordPolicy?: RegExp): JwtAuthPlugin;
@@ -16,9 +17,10 @@ export declare function createUser(repo: FlinkRepo<any, User>, auth: JwtAuthPlug
16
17
  salt: string;
17
18
  } | null>;
18
19
  }): Promise<UserCreateRes>;
20
+ export declare function loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token: string, code: string, jwtSecret: string): Promise<UserLoginRes>;
19
21
  export declare function loginUser(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, username: string, password: string | undefined, validatePasswordMethod?: {
20
22
  (password: string, hash: string, salt: string): Promise<boolean>;
21
- }): Promise<UserLoginRes>;
23
+ }, smsOptions?: GenericAuthsmsOptions): Promise<UserLoginRes>;
22
24
  export declare function changePassword(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, userId: string, newPassword: string, createPasswordHashAndSaltMethod?: {
23
25
  (password: string): Promise<{
24
26
  hash: string;
@@ -39,8 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.passwordResetComplete = exports.passwordResetStart = exports.changePassword = exports.loginUser = exports.createUser = exports.getJtwTokenPlugin = void 0;
43
- var flink_1 = require("@flink-app/flink");
42
+ exports.passwordResetComplete = exports.passwordResetStart = exports.changePassword = exports.loginUser = exports.loginByToken = exports.createUser = exports.getJtwTokenPlugin = void 0;
44
43
  var jwt_auth_plugin_1 = require("@flink-app/jwt-auth-plugin");
45
44
  var jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
46
45
  function getJtwTokenPlugin(secret, rolePermissions, passwordPolicy) {
@@ -119,6 +118,11 @@ function createUser(repo, auth, username, password, authentificationMethod, role
119
118
  return [4 /*yield*/, auth.createToken({ username: username.toLowerCase(), _id: user._id }, roles)];
120
119
  case 8:
121
120
  token = _a.sent();
121
+ if (user.authentificationMethod == "sms") {
122
+ return [2 /*return*/, {
123
+ status: "success",
124
+ }];
125
+ }
122
126
  return [2 /*return*/, {
123
127
  status: "success",
124
128
  user: {
@@ -132,9 +136,47 @@ function createUser(repo, auth, username, password, authentificationMethod, role
132
136
  });
133
137
  }
134
138
  exports.createUser = createUser;
135
- function loginUser(repo, auth, username, password, validatePasswordMethod) {
139
+ function loginByToken(repo, auth, token, code, jwtSecret) {
140
+ return __awaiter(this, void 0, void 0, function () {
141
+ var payload, user, authToken;
142
+ return __generator(this, function (_a) {
143
+ switch (_a.label) {
144
+ case 0:
145
+ try {
146
+ payload = jsonwebtoken_1.default.verify(token, jwtSecret + ":" + code);
147
+ }
148
+ catch (ex) {
149
+ return [2 /*return*/, { status: "failed" }];
150
+ }
151
+ if (payload.type != "smsLogin") {
152
+ return [2 /*return*/, { status: "failed" }];
153
+ }
154
+ return [4 /*yield*/, repo.getById(payload.userId)];
155
+ case 1:
156
+ user = _a.sent();
157
+ if (user == null) {
158
+ return [2 /*return*/, { status: "failed" }];
159
+ }
160
+ return [4 /*yield*/, auth.createToken({ username: user.username.toLowerCase(), _id: user._id }, user.roles)];
161
+ case 2:
162
+ authToken = _a.sent();
163
+ return [2 /*return*/, {
164
+ status: "success",
165
+ user: {
166
+ _id: user._id,
167
+ username: user.username,
168
+ token: authToken,
169
+ profile: user.profile,
170
+ },
171
+ }];
172
+ }
173
+ });
174
+ });
175
+ }
176
+ exports.loginByToken = loginByToken;
177
+ function loginUser(repo, auth, username, password, validatePasswordMethod, smsOptions) {
136
178
  return __awaiter(this, void 0, void 0, function () {
137
- var user, valid, ex_1, token;
179
+ var user, valid, ex_1, code, payload, secret, options, token, token;
138
180
  return __generator(this, function (_a) {
139
181
  switch (_a.label) {
140
182
  case 0: return [4 /*yield*/, repo.getOne({ username: username.toLowerCase() })];
@@ -169,8 +211,27 @@ function loginUser(repo, auth, username, password, validatePasswordMethod) {
169
211
  _a.label = 9;
170
212
  case 9:
171
213
  if (user.authentificationMethod == "sms") {
172
- flink_1.log.error("SMS login is not yet impleted.");
173
- return [2 /*return*/, { status: "failed" }];
214
+ if (!smsOptions)
215
+ throw "SMS options must be specified to use SMS login";
216
+ code = smsOptions.codeType == "numeric" ? generate(smsOptions.codeLength) : generateString(smsOptions.codeLength);
217
+ smsOptions.smsClient.send({
218
+ to: [user.username],
219
+ from: smsOptions.smsFrom,
220
+ message: smsOptions.smsMessage.replace("{{code}}", code)
221
+ });
222
+ payload = {
223
+ type: "smsLogin",
224
+ userId: user._id,
225
+ };
226
+ secret = smsOptions.jwtToken + ":" + code;
227
+ options = {
228
+ expiresIn: "1h",
229
+ };
230
+ token = jsonwebtoken_1.default.sign(payload, secret, options);
231
+ return [2 /*return*/, {
232
+ status: "success",
233
+ validationToken: token
234
+ }];
174
235
  }
175
236
  if (!valid) return [3 /*break*/, 11];
176
237
  return [4 /*yield*/, auth.createToken({ username: username.toLowerCase(), _id: user._id }, user.roles)];
@@ -333,3 +394,12 @@ function generate(n) {
333
394
  var number = Math.floor(Math.random() * (max - min + 1)) + min;
334
395
  return ("" + number).substring(add);
335
396
  }
397
+ function generateString(length) {
398
+ var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
399
+ var result = ' ';
400
+ var charactersLength = characters.length;
401
+ for (var i = 0; i < length; i++) {
402
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
403
+ }
404
+ return result;
405
+ }
@@ -8,11 +8,13 @@ import { UserPasswordChangeRes } from "./schemas/UserPasswordChangeRes";
8
8
  import { UserPasswordResetSettings } from "./schemas/UserPasswordResetSettings";
9
9
  import { UserPasswordResetStartRes } from "./schemas/UserPasswordResetStartRes";
10
10
  import { UserPasswordResetCompleteRes } from "./schemas/UserPasswordResetCompleteRes";
11
+ import { GenericAuthsmsOptions } from "./genericAuthPluginOptions";
11
12
  export interface genericAuthContext {
12
13
  genericAuthPlugin: {
13
14
  loginUser(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, username: string, password?: string, validatePasswordMethod?: {
14
15
  (password: string, hash: string, salt: string): Promise<boolean>;
15
- }): Promise<UserLoginRes>;
16
+ }, smsOptions?: GenericAuthsmsOptions): Promise<UserLoginRes>;
17
+ loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token: string, code: string, jwtSecret: string): Promise<UserLoginRes>;
16
18
  createUser(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, username: string, password: string, authentificationMethod: "password" | "sms", roles: string[], profile: UserProfile, createPasswordHashAndSaltMethod?: {
17
19
  (password: string): Promise<{
18
20
  hash: string;
@@ -44,5 +46,6 @@ export interface genericAuthContext {
44
46
  (password: string, hash: string, salt: string): Promise<boolean>;
45
47
  };
46
48
  usernameFormat: RegExp;
49
+ smsOptions?: GenericAuthsmsOptions;
47
50
  };
48
51
  }
@@ -1,4 +1,5 @@
1
1
  import { UserPasswordResetSettings } from "./schemas/UserPasswordResetSettings";
2
+ import { client as smsClient } from "@flink-app/sms-plugin";
2
3
  export interface GenericAuthPluginOptions {
3
4
  repoName: string;
4
5
  enableRoutes?: boolean;
@@ -20,4 +21,13 @@ export interface GenericAuthPluginOptions {
20
21
  (password: string, hash: string, salt: string): Promise<boolean>;
21
22
  };
22
23
  usernameFormat?: RegExp;
24
+ sms?: GenericAuthsmsOptions;
25
+ }
26
+ export interface GenericAuthsmsOptions {
27
+ smsClient: smsClient;
28
+ smsFrom: string;
29
+ smsMessage: string;
30
+ jwtToken: string;
31
+ codeType: "numeric" | "alphanumeric";
32
+ codeLength: number;
23
33
  }
@@ -47,7 +47,7 @@ var userLoginHandler = function (_a) {
47
47
  case 0:
48
48
  pluginName = origin || "genericAuthPlugin";
49
49
  repo = ctx.repos[ctx.plugins[pluginName].repoName];
50
- return [4 /*yield*/, ctx.plugins.genericAuthPlugin.loginUser(repo, ctx.auth, req.body.username, req.body.password, ctx.plugins.genericAuthPlugin.validatePasswordMethod)];
50
+ return [4 /*yield*/, ctx.plugins.genericAuthPlugin.loginUser(repo, ctx.auth, req.body.username, req.body.password, ctx.plugins.genericAuthPlugin.validatePasswordMethod, ctx.plugins[pluginName].smsOptions)];
51
51
  case 1:
52
52
  loginRespons = _b.sent();
53
53
  if (loginRespons.status != "success") {
@@ -0,0 +1,8 @@
1
+ import { FlinkContext, Handler } from "@flink-app/flink";
2
+ import { genericAuthContext } from "../genericAuthContext";
3
+ import { UserLoginByTokenReq } from "../schemas/UserLoginByTokenReq";
4
+ import { UserLoginRes } from "../schemas/UserLoginRes";
5
+ declare const userLoginHandler: Handler<FlinkContext<genericAuthContext>, UserLoginByTokenReq, UserLoginRes>;
6
+ export default userLoginHandler;
7
+ export declare const __assumedHttpMethod = "", __file = "UserLoginByToken.ts", __query: never[], __params: never[];
8
+ export declare const __schemas: any;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (_) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.__schemas = exports.__params = exports.__query = exports.__file = exports.__assumedHttpMethod = void 0;
40
+ var flink_1 = require("@flink-app/flink");
41
+ var userLoginHandler = function (_a) {
42
+ var ctx = _a.ctx, req = _a.req, origin = _a.origin;
43
+ return __awaiter(void 0, void 0, void 0, function () {
44
+ var pluginName, repo, loginRespons;
45
+ return __generator(this, function (_b) {
46
+ switch (_b.label) {
47
+ case 0:
48
+ pluginName = origin || "genericAuthPlugin";
49
+ repo = ctx.repos[ctx.plugins[pluginName].repoName];
50
+ return [4 /*yield*/, ctx.plugins.genericAuthPlugin.loginByToken(repo, ctx.auth, req.body.token, req.body.code, ctx.plugins[pluginName].smsOptions.jwtToken)];
51
+ case 1:
52
+ loginRespons = _b.sent();
53
+ if (loginRespons.status != "success") {
54
+ switch (loginRespons.status) {
55
+ case "failed":
56
+ return [2 /*return*/, flink_1.unauthorized("Invalid token or code", loginRespons.status)];
57
+ }
58
+ }
59
+ return [2 /*return*/, {
60
+ data: loginRespons,
61
+ status: 200,
62
+ }];
63
+ }
64
+ });
65
+ });
66
+ };
67
+ exports.default = userLoginHandler;
68
+ exports.__assumedHttpMethod = "", exports.__file = "UserLoginByToken.ts", exports.__query = [], exports.__params = [];
69
+ exports.__schemas = { reqSchema: { "type": "object", "additionalProperties": false, "properties": { "token": { "type": "string" }, "code": { "type": "string" } }, "required": ["code", "token"] }, resSchema: { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": ["success", "failed", "requiresValidation"] }, "user": { "type": "object", "properties": { "_id": { "type": "string" }, "username": { "type": "string" }, "token": { "type": "string" }, "profile": { "type": "object" } }, "required": ["_id", "username", "token", "profile"], "additionalProperties": false }, "validationToken": { "type": "string" } }, "required": ["status"] } };
package/dist/src/index.js CHANGED
@@ -26,6 +26,7 @@ var genericAuthPlugin = function (options) {
26
26
  init: function (app) { return init_1.init(app, options); },
27
27
  ctx: {
28
28
  loginUser: coreFunctions_1.loginUser,
29
+ loginByToken: coreFunctions_1.loginByToken,
29
30
  createUser: coreFunctions_1.createUser,
30
31
  changePassword: coreFunctions_1.changePassword,
31
32
  passwordResetStart: coreFunctions_1.passwordResetStart,
@@ -35,6 +36,7 @@ var genericAuthPlugin = function (options) {
35
36
  createPasswordHashAndSaltMethod: options.createPasswordHashAndSaltMethod,
36
37
  validatePasswordMethod: options.validatePasswordMethod,
37
38
  usernameFormat: options.usernameFormat || /.{1,}$/,
39
+ smsOptions: options.sms
38
40
  },
39
41
  };
40
42
  };
package/dist/src/init.js CHANGED
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  exports.init = void 0;
23
23
  var flink_1 = require("@flink-app/flink");
24
24
  var userLoginHandler = __importStar(require("./handlers/UserLogin"));
25
+ var userLoginByTokenHandler = __importStar(require("./handlers/UserLoginByToken"));
25
26
  var userCreateHandler = __importStar(require("./handlers/UserCreate"));
26
27
  var getProfileHandler = __importStar(require("./handlers/UserProfileGet"));
27
28
  var putUserProfileHandler = __importStar(require("./handlers/UserProfilePut"));
@@ -49,6 +50,14 @@ function init(app, options) {
49
50
  docs: "Authenticates a user",
50
51
  origin: options.pluginId,
51
52
  });
53
+ if (options.sms) {
54
+ app.addHandler(userLoginByTokenHandler, {
55
+ method: flink_1.HttpMethod.post,
56
+ path: options.baseUrl + "/login-by-token",
57
+ docs: "Authenticates a user by token",
58
+ origin: options.pluginId,
59
+ });
60
+ }
52
61
  if (options.enableUserCreation) {
53
62
  app.addHandler(userCreateHandler, {
54
63
  method: flink_1.HttpMethod.post,
@@ -0,0 +1,4 @@
1
+ export interface UserLoginByTokenReq {
2
+ token: string;
3
+ code: string;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/generic-auth-plugin",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Flink plugin that provides a generic user authentification solution.",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\"",
@@ -19,6 +19,7 @@
19
19
  "@flink-app/email-plugin": "^0.3.8",
20
20
  "@flink-app/jwt-auth-plugin": "^0.3.8",
21
21
  "@flink-app/management-api-plugin": "^0.3.8",
22
+ "@flink-app/sms-plugin": "^0.3.9",
22
23
  "handlebars": "^4.7.7",
23
24
  "jsonwebtoken": "^8.5.1"
24
25
  },
@@ -29,5 +30,5 @@
29
30
  "ts-node": "^9.1.1",
30
31
  "typescript": "^4.2.4"
31
32
  },
32
- "gitHead": "9c45e69ffff300482f9945074c5ecc3e1d661467"
33
+ "gitHead": "660a90494e67b768a560c3618d8a3da3cc5d5144"
33
34
  }
package/readme.md CHANGED
@@ -742,3 +742,150 @@ export interface Profile{
742
742
  type : myType
743
743
  }
744
744
  ```
745
+
746
+
747
+
748
+ ## Using SMS login
749
+
750
+
751
+ ### prerequisites
752
+ - A SMS client must be setup using the [sms-plugin](https://github.com/FrostDigital/flink-framework/tree/main/packages/sms-plugin)
753
+
754
+
755
+ ### Setup
756
+ - Configure this plugin by setting the sms option:
757
+
758
+
759
+ ```
760
+ import { FlinkApp } from "@flink-app/flink";
761
+ import { Ctx } from "./Ctx";
762
+
763
+ import { getJtwTokenPlugin, genericAuthPlugin } from "@flink-app/generic-auth-plugin"
764
+
765
+ const authPlugin = getJtwTokenPlugin("secret");
766
+
767
+ function start() {
768
+ var app = new FlinkApp<Ctx>({
769
+ name: "My flink app",
770
+ debug: true,
771
+ auth : authPlugin,
772
+ db: {
773
+ uri: "mongodb://localhost:27017/my-flink-app",
774
+ },
775
+ plugins: [
776
+ genericAuthPlugin({
777
+ ...
778
+
779
+
780
+ sms : {
781
+ smsClient: new sms46elksClient({
782
+ username: "XXX",
783
+ password: "YYY",
784
+ }),
785
+ smsFrom: "AUTHMSG",
786
+ smsMessage: "Your code is {{code}}",
787
+ jwtToken: "secret-to-sign-jwt-tokens",
788
+ codeType: "numeric",
789
+ codeLength: 6
790
+ }
791
+
792
+
793
+ ...
794
+
795
+ }
796
+ }),
797
+ ],
798
+ })
799
+ app.start();
800
+ }
801
+ start();
802
+ ```
803
+
804
+
805
+
806
+ ### Register users with SMS-login
807
+ To use SMS-login on a user, the user must be created with the `authentificationMethod` option set to sms.
808
+ Username also have to be the users phone number in the "+4671234567" format.
809
+
810
+ ### POST /user/create
811
+
812
+ Create a user that can login via SMS
813
+
814
+ #### Request data:
815
+
816
+ ```
817
+ {
818
+ "username" : "+4671234567",
819
+ "authentificationMethod" : "sms"
820
+ }
821
+ ```
822
+
823
+ #### Response example
824
+
825
+ ```
826
+ {
827
+ "data": {
828
+ "status": "success",
829
+ },
830
+ ```
831
+
832
+
833
+
834
+ ### Initiate login
835
+ Initiate a user login by sending a SMS with the code to the user.
836
+ Please note that the user HAVE to be created with the `authentificationMethod` option set to sms.
837
+
838
+ ### POST /user/login
839
+
840
+
841
+ #### Request data:
842
+
843
+ ```
844
+ {
845
+ "username" : "+4671234567",
846
+ }
847
+ ```
848
+
849
+ #### Response example
850
+
851
+ ```
852
+ {
853
+ "data": {
854
+ "status": "success",
855
+ "validationToken": "TOKEN"
856
+ },
857
+ }
858
+
859
+ ```
860
+
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
865
+
866
+
867
+ #### Request data:
868
+
869
+ ```
870
+ {
871
+ "token" : "TOKEN",
872
+ "code" : "code"
873
+ }
874
+ ```
875
+
876
+ #### Response example
877
+
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
+ ```
@@ -10,6 +10,7 @@ import { UserPasswordResetStartRes } from "./schemas/UserPasswordResetStartRes";
10
10
  import { UserPasswordResetCompleteRes } from "./schemas/UserPasswordResetCompleteRes";
11
11
 
12
12
  import jsonwebtoken from "jsonwebtoken";
13
+ import { GenericAuthsmsOptions } from "./genericAuthPluginOptions";
13
14
 
14
15
  export function getJtwTokenPlugin(secret: string, rolePermissions?: { [role: string]: string[] }, passwordPolicy?: RegExp) {
15
16
  if (passwordPolicy == undefined) {
@@ -87,6 +88,12 @@ export async function createUser(
87
88
 
88
89
  const token = await auth.createToken({ username: username.toLowerCase(), _id: user._id }, roles);
89
90
 
91
+ if (user.authentificationMethod == "sms") {
92
+ return {
93
+ status: "success",
94
+ };
95
+ }
96
+
90
97
  return {
91
98
  status: "success",
92
99
  user: {
@@ -97,6 +104,51 @@ export async function createUser(
97
104
  };
98
105
  }
99
106
 
107
+ export async function loginByToken(
108
+ repo: FlinkRepo<any, User>,
109
+ auth: JwtAuthPlugin,
110
+ token : string,
111
+ code : string,
112
+ jwtSecret : string
113
+
114
+ ): Promise<UserLoginRes> {
115
+
116
+
117
+ let payload : { type : string, userId : string};
118
+ try{
119
+ payload = jsonwebtoken.verify(token, jwtSecret + ":" + code) as { type : string, userId : string};
120
+ }catch(ex){
121
+ return { status: "failed" };
122
+ }
123
+
124
+
125
+ if(payload.type != "smsLogin"){
126
+ return { status: "failed" };
127
+ }
128
+
129
+
130
+
131
+ const user = await repo.getById(payload.userId)
132
+ if (user == null) {
133
+ return { status: "failed" };
134
+ }
135
+
136
+
137
+ const authToken = await auth.createToken({ username: user.username.toLowerCase(), _id: user._id }, user.roles);
138
+
139
+ return {
140
+ status: "success",
141
+ user: {
142
+ _id: user._id,
143
+ username: user.username,
144
+ token : authToken,
145
+ profile: user.profile,
146
+ },
147
+ };
148
+
149
+ }
150
+
151
+
100
152
  export async function loginUser(
101
153
  repo: FlinkRepo<any, User>,
102
154
  auth: JwtAuthPlugin,
@@ -104,7 +156,8 @@ export async function loginUser(
104
156
  password: string | undefined,
105
157
  validatePasswordMethod?: {
106
158
  (password: string, hash: string, salt: string): Promise<boolean>;
107
- }
159
+ },
160
+ smsOptions? : GenericAuthsmsOptions
108
161
  ): Promise<UserLoginRes> {
109
162
  const user = await repo.getOne({ username: username.toLowerCase() });
110
163
  if (user == null) {
@@ -130,8 +183,35 @@ export async function loginUser(
130
183
  }
131
184
  }
132
185
  if (user.authentificationMethod == "sms") {
133
- log.error("SMS login is not yet impleted.");
134
- return { status: "failed" };
186
+ if(!smsOptions) throw "SMS options must be specified to use SMS login"
187
+ let code = smsOptions.codeType == "numeric" ? generate(smsOptions.codeLength) : generateString(smsOptions.codeLength);
188
+ smsOptions.smsClient.send({
189
+ to : [user.username],
190
+ from : smsOptions.smsFrom,
191
+ message : smsOptions.smsMessage.replace("{{code}}", code)
192
+ })
193
+
194
+ const payload = {
195
+ type: "smsLogin",
196
+ userId: user._id,
197
+ };
198
+
199
+ const secret = smsOptions.jwtToken + ":" + code;
200
+
201
+ const options: jsonwebtoken.SignOptions = {
202
+ expiresIn: "1h",
203
+ };
204
+
205
+ const token = jsonwebtoken.sign(payload, secret, options);
206
+
207
+
208
+ return {
209
+ status: "success",
210
+ validationToken : token
211
+ };
212
+
213
+
214
+
135
215
  }
136
216
 
137
217
  if (valid) {
@@ -297,3 +377,17 @@ function generate(n: number): string {
297
377
 
298
378
  return ("" + number).substring(add);
299
379
  }
380
+
381
+
382
+
383
+
384
+ function generateString(length : number) {
385
+ const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
386
+ let result = ' ';
387
+ const charactersLength = characters.length;
388
+ for ( let i = 0; i < length; i++ ) {
389
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
390
+ }
391
+
392
+ return result;
393
+ }
@@ -8,12 +8,14 @@ import { UserPasswordChangeRes } from "./schemas/UserPasswordChangeRes";
8
8
  import { UserPasswordResetSettings} from "./schemas/UserPasswordResetSettings"
9
9
  import { UserPasswordResetStartRes } from "./schemas/UserPasswordResetStartRes"
10
10
  import { UserPasswordResetCompleteRes } from "./schemas/UserPasswordResetCompleteRes"
11
+ import { GenericAuthsmsOptions } from "./genericAuthPluginOptions";
11
12
 
12
13
  export interface genericAuthContext{
13
14
  genericAuthPlugin : {
14
15
 
15
16
 
16
- loginUser( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, username : string, password? : string, validatePasswordMethod? : { (password : string, hash : string, salt : string) : Promise<boolean> } ) : Promise<UserLoginRes>,
17
+ loginUser( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, username : string, password? : string, validatePasswordMethod? : { (password : string, hash : string, salt : string) : Promise<boolean> }, smsOptions? : GenericAuthsmsOptions ) : Promise<UserLoginRes>,
18
+ loginByToken(repo: FlinkRepo<any, User>, auth: JwtAuthPlugin, token : string, code : string, jwtSecret : string) : Promise<UserLoginRes>,
17
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>,
18
20
  changePassword( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, userId : string, newPassword : string, createPasswordHashAndSaltMethod? : { (password : string) : Promise<{ hash: string; salt: string;} | null> } ) : Promise<UserPasswordChangeRes>,
19
21
  passwordResetStart( repo : FlinkRepo<any, User>, auth : JwtAuthPlugin, jwtSecret : string, username : string, numberOfDigits? : number, lifeTime? : string) : Promise<UserPasswordResetStartRes>,
@@ -23,6 +25,7 @@ export interface genericAuthContext{
23
25
  createPasswordHashAndSaltMethod? : { (password : string) : Promise<{ hash: string; salt: string;} | null> },
24
26
  validatePasswordMethod? : { (password : string, hash : string, salt : string) : Promise<boolean> },
25
27
  usernameFormat : RegExp
28
+ smsOptions? : GenericAuthsmsOptions
26
29
  }
27
30
 
28
31
  }
@@ -1,21 +1,32 @@
1
1
  import { UserPasswordResetSettings } from "./schemas/UserPasswordResetSettings";
2
-
2
+ import { client as smsClient } from "@flink-app/sms-plugin";
3
3
  export interface GenericAuthPluginOptions {
4
- repoName: string;
5
- enableRoutes?: boolean;
6
- enablePasswordReset?: boolean;
7
- enablePushNotificationTokens?: boolean;
8
- passwordResetSettings?: UserPasswordResetSettings;
9
- enableUserCreation?: boolean;
10
- enableProfileUpdate?: boolean;
11
- enablePasswordUpdate?: boolean;
12
- baseUrl?: string;
13
- pluginId?: string;
14
- createPasswordHashAndSaltMethod?: {
15
- (password: string): Promise<{ hash: string; salt: string } | null>;
16
- };
17
- validatePasswordMethod?: {
18
- (password: string, hash: string, salt: string): Promise<boolean>;
19
- };
20
- usernameFormat?: RegExp;
4
+ repoName: string;
5
+ enableRoutes?: boolean;
6
+ enablePasswordReset?: boolean;
7
+ enablePushNotificationTokens?: boolean;
8
+ passwordResetSettings?: UserPasswordResetSettings;
9
+ enableUserCreation?: boolean;
10
+ enableProfileUpdate?: boolean;
11
+ enablePasswordUpdate?: boolean;
12
+ baseUrl?: string;
13
+ pluginId?: string;
14
+ createPasswordHashAndSaltMethod?: {
15
+ (password: string): Promise<{ hash: string; salt: string } | null>;
16
+ };
17
+ validatePasswordMethod?: {
18
+ (password: string, hash: string, salt: string): Promise<boolean>;
19
+ };
20
+
21
+ usernameFormat?: RegExp;
22
+ sms?: GenericAuthsmsOptions;
23
+ }
24
+
25
+ export interface GenericAuthsmsOptions {
26
+ smsClient: smsClient;
27
+ smsFrom: string;
28
+ smsMessage: string;
29
+ jwtToken: string;
30
+ codeType: "numeric" | "alphanumeric";
31
+ codeLength: number;
21
32
  }
@@ -11,12 +11,14 @@ const userLoginHandler: Handler<
11
11
  > = async ({ ctx, req, origin }) => {
12
12
  let pluginName = origin || "genericAuthPlugin";
13
13
  let repo = ctx.repos[(<any>ctx.plugins)[pluginName].repoName];
14
+
14
15
  const loginRespons = await ctx.plugins.genericAuthPlugin.loginUser(
15
16
  repo,
16
17
  <JwtAuthPlugin>ctx.auth,
17
18
  req.body.username,
18
19
  req.body.password,
19
- ctx.plugins.genericAuthPlugin.validatePasswordMethod
20
+ ctx.plugins.genericAuthPlugin.validatePasswordMethod,
21
+ (<any>ctx.plugins)[pluginName].smsOptions
20
22
  );
21
23
 
22
24
  if (loginRespons.status != "success") {
@@ -0,0 +1,39 @@
1
+ import { FlinkContext, Handler, unauthorized } from "@flink-app/flink";
2
+ import { JwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
3
+ import { genericAuthContext } from "../genericAuthContext";
4
+ import { UserLoginByTokenReq } from "../schemas/UserLoginByTokenReq";
5
+ import { UserLoginRes } from "../schemas/UserLoginRes";
6
+
7
+ const userLoginHandler: Handler<
8
+ FlinkContext<genericAuthContext>,
9
+ UserLoginByTokenReq,
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.loginByToken(
16
+ repo,
17
+ <JwtAuthPlugin>ctx.auth,
18
+ req.body.token,
19
+ req.body.code,
20
+ (<any>ctx.plugins)[pluginName].smsOptions.jwtToken
21
+ );
22
+
23
+ if (loginRespons.status != "success") {
24
+ switch (loginRespons.status) {
25
+ case "failed":
26
+ return unauthorized(
27
+ "Invalid token or code",
28
+ loginRespons.status
29
+ );
30
+ }
31
+ }
32
+
33
+ return {
34
+ data: loginRespons,
35
+ status: 200,
36
+ };
37
+ };
38
+
39
+ export default userLoginHandler;
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  changePassword,
6
6
  passwordResetComplete,
7
7
  passwordResetStart,
8
+ loginByToken
8
9
  } from "./coreFunctions";
9
10
  import { init } from "./init";
10
11
  import { GenericAuthPluginOptions } from "./genericAuthPluginOptions";
@@ -24,6 +25,7 @@ export const genericAuthPlugin = (
24
25
  init: (app) => init(app, options),
25
26
  ctx: {
26
27
  loginUser,
28
+ loginByToken,
27
29
  createUser,
28
30
  changePassword,
29
31
  passwordResetStart,
@@ -33,6 +35,7 @@ export const genericAuthPlugin = (
33
35
  createPasswordHashAndSaltMethod: options.createPasswordHashAndSaltMethod,
34
36
  validatePasswordMethod: options.validatePasswordMethod,
35
37
  usernameFormat: options.usernameFormat || /.{1,}$/,
38
+ smsOptions : options.sms
36
39
  },
37
40
  };
38
41
  };
package/src/init.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { FlinkApp, HttpMethod } from "@flink-app/flink";
2
2
  import * as userLoginHandler from "./handlers/UserLogin";
3
+ import * as userLoginByTokenHandler from "./handlers/UserLoginByToken";
3
4
  import * as userCreateHandler from "./handlers/UserCreate";
4
5
  import * as getProfileHandler from "./handlers/UserProfileGet";
5
6
  import * as putUserProfileHandler from "./handlers/UserProfilePut";
@@ -25,6 +26,14 @@ export function init(app: FlinkApp<any>, options: GenericAuthPluginOptions) {
25
26
  docs: "Authenticates a user",
26
27
  origin: options.pluginId,
27
28
  });
29
+ if(options.sms){
30
+ app.addHandler(userLoginByTokenHandler, {
31
+ method: HttpMethod.post,
32
+ path: options.baseUrl + "/login-by-token",
33
+ docs: "Authenticates a user by token",
34
+ origin: options.pluginId,
35
+ });
36
+ }
28
37
  if (options.enableUserCreation) {
29
38
  app.addHandler(userCreateHandler, {
30
39
  method: HttpMethod.post,
@@ -1,8 +1,8 @@
1
1
  export interface UserCreateRes {
2
- status : "success" | "error" | "userExists" | "passwordError";
3
- user? : {
4
- _id : string;
5
- username : string;
6
- token : string;
7
- }
8
- }
2
+ status: "success" | "error" | "userExists" | "passwordError";
3
+ user?: {
4
+ _id: string;
5
+ username: string;
6
+ token: string;
7
+ };
8
+ }
@@ -0,0 +1,4 @@
1
+ export interface UserLoginByTokenReq {
2
+ token : string,
3
+ code : string
4
+ }