@flink-app/otp-auth-plugin 0.12.1-alpha.40

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +879 -0
  3. package/dist/OtpAuthPlugin.d.ts +64 -0
  4. package/dist/OtpAuthPlugin.js +231 -0
  5. package/dist/OtpAuthPluginContext.d.ts +10 -0
  6. package/dist/OtpAuthPluginContext.js +2 -0
  7. package/dist/OtpAuthPluginOptions.d.ts +112 -0
  8. package/dist/OtpAuthPluginOptions.js +2 -0
  9. package/dist/OtpInternalContext.d.ts +11 -0
  10. package/dist/OtpInternalContext.js +2 -0
  11. package/dist/functions/initiate.d.ts +18 -0
  12. package/dist/functions/initiate.js +104 -0
  13. package/dist/functions/verify.d.ts +20 -0
  14. package/dist/functions/verify.js +142 -0
  15. package/dist/handlers/PostOtpInitiate.d.ts +7 -0
  16. package/dist/handlers/PostOtpInitiate.js +70 -0
  17. package/dist/handlers/PostOtpVerify.d.ts +7 -0
  18. package/dist/handlers/PostOtpVerify.js +86 -0
  19. package/dist/index.d.ts +11 -0
  20. package/dist/index.js +24 -0
  21. package/dist/repos/OtpSessionRepo.d.ts +13 -0
  22. package/dist/repos/OtpSessionRepo.js +145 -0
  23. package/dist/schemas/InitiateRequest.d.ts +8 -0
  24. package/dist/schemas/InitiateRequest.js +2 -0
  25. package/dist/schemas/InitiateResponse.d.ts +8 -0
  26. package/dist/schemas/InitiateResponse.js +2 -0
  27. package/dist/schemas/OtpSession.d.ts +25 -0
  28. package/dist/schemas/OtpSession.js +2 -0
  29. package/dist/schemas/VerifyRequest.d.ts +6 -0
  30. package/dist/schemas/VerifyRequest.js +2 -0
  31. package/dist/schemas/VerifyResponse.d.ts +12 -0
  32. package/dist/schemas/VerifyResponse.js +2 -0
  33. package/dist/utils/otp-utils.d.ts +43 -0
  34. package/dist/utils/otp-utils.js +95 -0
  35. package/examples/basic-usage.ts +145 -0
  36. package/package.json +37 -0
  37. package/spec/OtpAuthPlugin.spec.ts +159 -0
  38. package/spec/OtpSessionRepo.spec.ts +194 -0
  39. package/spec/otp-utils.spec.ts +172 -0
  40. package/spec/support/jasmine.json +7 -0
  41. package/src/OtpAuthPlugin.ts +163 -0
  42. package/src/OtpAuthPluginContext.ts +11 -0
  43. package/src/OtpAuthPluginOptions.ts +135 -0
  44. package/src/OtpInternalContext.ts +12 -0
  45. package/src/functions/initiate.ts +86 -0
  46. package/src/functions/verify.ts +123 -0
  47. package/src/handlers/PostOtpInitiate.ts +28 -0
  48. package/src/handlers/PostOtpVerify.ts +42 -0
  49. package/src/index.ts +17 -0
  50. package/src/repos/OtpSessionRepo.ts +47 -0
  51. package/src/schemas/InitiateRequest.ts +8 -0
  52. package/src/schemas/InitiateResponse.ts +8 -0
  53. package/src/schemas/OtpSession.ts +25 -0
  54. package/src/schemas/VerifyRequest.ts +6 -0
  55. package/src/schemas/VerifyResponse.ts +12 -0
  56. package/src/utils/otp-utils.ts +89 -0
  57. package/tsconfig.dist.json +4 -0
  58. package/tsconfig.json +24 -0
@@ -0,0 +1,64 @@
1
+ import { FlinkPlugin } from "@flink-app/flink";
2
+ import { OtpAuthPluginOptions } from "./OtpAuthPluginOptions";
3
+ /**
4
+ * OTP Auth Plugin Factory Function
5
+ *
6
+ * Creates a Flink plugin for OTP (One-Time Password) authentication via SMS or email.
7
+ * Integrates with JWT Auth Plugin for token generation.
8
+ *
9
+ * @param options - OTP auth plugin configuration options
10
+ * @returns FlinkPlugin instance
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
15
+ * import { otpAuthPlugin } from '@flink-app/otp-auth-plugin';
16
+ *
17
+ * const app = new FlinkApp({
18
+ * auth: jwtAuthPlugin({
19
+ * secret: process.env.JWT_SECRET!,
20
+ * getUser: async (tokenData) => {
21
+ * return ctx.repos.userRepo.getById(tokenData.userId);
22
+ * },
23
+ * rolePermissions: {
24
+ * user: ['read', 'write']
25
+ * }
26
+ * }),
27
+ *
28
+ * plugins: [
29
+ * otpAuthPlugin({
30
+ * codeLength: 6,
31
+ * codeTTL: 300, // 5 minutes
32
+ * maxAttempts: 3,
33
+ * onSendCode: async (code, identifier, method) => {
34
+ * if (method === 'sms') {
35
+ * await ctx.plugins.sms.send(identifier, `Your code: ${code}`);
36
+ * } else {
37
+ * await ctx.plugins.email.send({
38
+ * to: identifier,
39
+ * subject: 'Your verification code',
40
+ * text: `Your code: ${code}`
41
+ * });
42
+ * }
43
+ * return true;
44
+ * },
45
+ * onGetUser: async (identifier, method) => {
46
+ * if (method === 'sms') {
47
+ * return await ctx.repos.userRepo.findOne({ phoneNumber: identifier });
48
+ * } else {
49
+ * return await ctx.repos.userRepo.findOne({ email: identifier });
50
+ * }
51
+ * },
52
+ * onVerifySuccess: async (user, identifier, method) => {
53
+ * const token = await ctx.auth.createToken(
54
+ * { userId: user._id, email: user.email },
55
+ * user.roles
56
+ * );
57
+ * return { user, token };
58
+ * }
59
+ * })
60
+ * ]
61
+ * });
62
+ * ```
63
+ */
64
+ export declare function otpAuthPlugin(options: OtpAuthPluginOptions): FlinkPlugin;
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
+ return new (P || (P = Promise))(function (resolve, reject) {
39
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
40
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
41
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
43
+ });
44
+ };
45
+ var __generator = (this && this.__generator) || function (thisArg, body) {
46
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
47
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
48
+ function verb(n) { return function (v) { return step([n, v]); }; }
49
+ function step(op) {
50
+ if (f) throw new TypeError("Generator is already executing.");
51
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
52
+ 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;
53
+ if (y = 0, t) op = [op[0] & 2, t.value];
54
+ switch (op[0]) {
55
+ case 0: case 1: t = op; break;
56
+ case 4: _.label++; return { value: op[1], done: false };
57
+ case 5: _.label++; y = op[1]; op = [0]; continue;
58
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
59
+ default:
60
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
61
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
62
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
63
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
64
+ if (t[2]) _.ops.pop();
65
+ _.trys.pop(); continue;
66
+ }
67
+ op = body.call(thisArg, _);
68
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
69
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
70
+ }
71
+ };
72
+ var __importDefault = (this && this.__importDefault) || function (mod) {
73
+ return (mod && mod.__esModule) ? mod : { "default": mod };
74
+ };
75
+ Object.defineProperty(exports, "__esModule", { value: true });
76
+ exports.otpAuthPlugin = void 0;
77
+ var flink_1 = require("@flink-app/flink");
78
+ var initiate_1 = require("./functions/initiate");
79
+ var verify_1 = require("./functions/verify");
80
+ var PostOtpInitiate = __importStar(require("./handlers/PostOtpInitiate"));
81
+ var PostOtpVerify = __importStar(require("./handlers/PostOtpVerify"));
82
+ var OtpSessionRepo_1 = __importDefault(require("./repos/OtpSessionRepo"));
83
+ /**
84
+ * OTP Auth Plugin Factory Function
85
+ *
86
+ * Creates a Flink plugin for OTP (One-Time Password) authentication via SMS or email.
87
+ * Integrates with JWT Auth Plugin for token generation.
88
+ *
89
+ * @param options - OTP auth plugin configuration options
90
+ * @returns FlinkPlugin instance
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * import { jwtAuthPlugin } from '@flink-app/jwt-auth-plugin';
95
+ * import { otpAuthPlugin } from '@flink-app/otp-auth-plugin';
96
+ *
97
+ * const app = new FlinkApp({
98
+ * auth: jwtAuthPlugin({
99
+ * secret: process.env.JWT_SECRET!,
100
+ * getUser: async (tokenData) => {
101
+ * return ctx.repos.userRepo.getById(tokenData.userId);
102
+ * },
103
+ * rolePermissions: {
104
+ * user: ['read', 'write']
105
+ * }
106
+ * }),
107
+ *
108
+ * plugins: [
109
+ * otpAuthPlugin({
110
+ * codeLength: 6,
111
+ * codeTTL: 300, // 5 minutes
112
+ * maxAttempts: 3,
113
+ * onSendCode: async (code, identifier, method) => {
114
+ * if (method === 'sms') {
115
+ * await ctx.plugins.sms.send(identifier, `Your code: ${code}`);
116
+ * } else {
117
+ * await ctx.plugins.email.send({
118
+ * to: identifier,
119
+ * subject: 'Your verification code',
120
+ * text: `Your code: ${code}`
121
+ * });
122
+ * }
123
+ * return true;
124
+ * },
125
+ * onGetUser: async (identifier, method) => {
126
+ * if (method === 'sms') {
127
+ * return await ctx.repos.userRepo.findOne({ phoneNumber: identifier });
128
+ * } else {
129
+ * return await ctx.repos.userRepo.findOne({ email: identifier });
130
+ * }
131
+ * },
132
+ * onVerifySuccess: async (user, identifier, method) => {
133
+ * const token = await ctx.auth.createToken(
134
+ * { userId: user._id, email: user.email },
135
+ * user.roles
136
+ * );
137
+ * return { user, token };
138
+ * }
139
+ * })
140
+ * ]
141
+ * });
142
+ * ```
143
+ */
144
+ function otpAuthPlugin(options) {
145
+ // Validation
146
+ if (!options.onSendCode) {
147
+ throw new Error("OTP Auth Plugin: onSendCode callback is required");
148
+ }
149
+ if (!options.onGetUser) {
150
+ throw new Error("OTP Auth Plugin: onGetUser callback is required");
151
+ }
152
+ if (!options.onVerifySuccess) {
153
+ throw new Error("OTP Auth Plugin: onVerifySuccess callback is required");
154
+ }
155
+ // Validate code length
156
+ var codeLength = options.codeLength || 6;
157
+ if (codeLength < 4 || codeLength > 8) {
158
+ throw new Error("OTP Auth Plugin: codeLength must be between 4 and 8");
159
+ }
160
+ // Validate code TTL
161
+ var codeTTL = options.codeTTL || 300;
162
+ if (codeTTL < 30 || codeTTL > 3600) {
163
+ flink_1.log.warn("OTP Auth Plugin: codeTTL should be between 30 and 3600 seconds for security");
164
+ }
165
+ // Validate max attempts
166
+ var maxAttempts = options.maxAttempts || 3;
167
+ if (maxAttempts < 1 || maxAttempts > 10) {
168
+ throw new Error("OTP Auth Plugin: maxAttempts must be between 1 and 10");
169
+ }
170
+ var flinkApp;
171
+ var sessionRepo;
172
+ /**
173
+ * Plugin initialization
174
+ */
175
+ function init(app, db) {
176
+ return __awaiter(this, void 0, void 0, function () {
177
+ var collectionName, keepSessionsSec, error_1;
178
+ return __generator(this, function (_a) {
179
+ switch (_a.label) {
180
+ case 0:
181
+ flink_1.log.info("Initializing OTP Auth Plugin...");
182
+ flinkApp = app;
183
+ _a.label = 1;
184
+ case 1:
185
+ _a.trys.push([1, 3, , 4]);
186
+ if (!db) {
187
+ throw new Error("OTP Auth Plugin: Database connection is required");
188
+ }
189
+ collectionName = options.otpSessionsCollectionName || "otp_sessions";
190
+ sessionRepo = new OtpSessionRepo_1.default(collectionName, db);
191
+ flinkApp.addRepo("otpSessionRepo", sessionRepo);
192
+ keepSessionsSec = options.keepSessionsSec || 86400;
193
+ return [4 /*yield*/, sessionRepo.ensureExpiringIndex(keepSessionsSec)];
194
+ case 2:
195
+ _a.sent();
196
+ // Register OTP handlers
197
+ // Only register handlers if registerRoutes is enabled (default: true)
198
+ if (options.registerRoutes !== false) {
199
+ flinkApp.addHandler(PostOtpInitiate);
200
+ flinkApp.addHandler(PostOtpVerify);
201
+ flink_1.log.info("OTP Auth Plugin: Registered HTTP endpoints");
202
+ }
203
+ flink_1.log.info("OTP Auth Plugin initialized (codeLength: ".concat(codeLength, ", codeTTL: ").concat(codeTTL, "s, maxAttempts: ").concat(maxAttempts, ")"));
204
+ return [3 /*break*/, 4];
205
+ case 3:
206
+ error_1 = _a.sent();
207
+ flink_1.log.error("Failed to initialize OTP Auth Plugin:", error_1);
208
+ throw error_1;
209
+ case 4: return [2 /*return*/];
210
+ }
211
+ });
212
+ });
213
+ }
214
+ /**
215
+ * Plugin context exposed via ctx.plugins.otpAuth
216
+ */
217
+ var pluginCtx = {
218
+ options: Object.freeze(__assign({}, options)),
219
+ initiate: function (initiateOptions) { return (0, initiate_1.initiate)(flinkApp.ctx, initiateOptions); },
220
+ verify: function (verifyOptions) { return (0, verify_1.verify)(flinkApp.ctx, verifyOptions); },
221
+ };
222
+ return {
223
+ id: "otpAuth",
224
+ db: {
225
+ useHostDb: true,
226
+ },
227
+ ctx: pluginCtx,
228
+ init: init,
229
+ };
230
+ }
231
+ exports.otpAuthPlugin = otpAuthPlugin;
@@ -0,0 +1,10 @@
1
+ import { OtpAuthPluginOptions } from "./OtpAuthPluginOptions";
2
+ import { InitiateOptions, InitiateResponse } from "./functions/initiate";
3
+ import { VerifyOptions, VerifyResponse } from "./functions/verify";
4
+ export interface OtpAuthPluginContext {
5
+ otpAuth: {
6
+ options: OtpAuthPluginOptions;
7
+ initiate: (options: InitiateOptions) => Promise<InitiateResponse>;
8
+ verify: (options: VerifyOptions) => Promise<VerifyResponse>;
9
+ };
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,112 @@
1
+ export interface AuthSuccessCallbackResponse {
2
+ user: any;
3
+ token: string;
4
+ }
5
+ export interface OtpAuthPluginOptions {
6
+ /**
7
+ * Number of digits in the OTP code (4-8).
8
+ * Default is 6.
9
+ */
10
+ codeLength?: number;
11
+ /**
12
+ * How long the OTP code is valid in seconds.
13
+ * Default is 300 (5 minutes).
14
+ */
15
+ codeTTL?: number;
16
+ /**
17
+ * Maximum number of verification attempts before session is locked.
18
+ * Default is 3.
19
+ */
20
+ maxAttempts?: number;
21
+ /**
22
+ * Callback to send the OTP code via SMS or email.
23
+ * Should return true if sending succeeded, false otherwise.
24
+ *
25
+ * @param code - The OTP code to send
26
+ * @param identifier - The user identifier (phone number or email)
27
+ * @param method - The delivery method ('sms' or 'email')
28
+ * @param payload - Optional custom payload from initiation
29
+ * @returns Promise that resolves to true if sent successfully
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * onSendCode: async (code, identifier, method, payload) => {
34
+ * if (method === 'sms') {
35
+ * await ctx.plugins.sms.send(identifier, `Your code is: ${code}`);
36
+ * } else {
37
+ * await ctx.plugins.email.send({
38
+ * to: identifier,
39
+ * subject: 'Your verification code',
40
+ * text: `Your code is: ${code}`
41
+ * });
42
+ * }
43
+ * return true;
44
+ * }
45
+ * ```
46
+ */
47
+ onSendCode: (code: string, identifier: string, method: "sms" | "email", payload?: Record<string, any>) => Promise<boolean>;
48
+ /**
49
+ * Callback to retrieve user by identifier (phone number or email).
50
+ * Return null if user is not found.
51
+ *
52
+ * @param identifier - The user identifier (phone number or email)
53
+ * @param method - The delivery method ('sms' or 'email')
54
+ * @param payload - Optional custom payload from initiation
55
+ * @returns Promise that resolves to user object or null
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * onGetUser: async (identifier, method) => {
60
+ * if (method === 'sms') {
61
+ * return await ctx.repos.userRepo.findOne({ phoneNumber: identifier });
62
+ * } else {
63
+ * return await ctx.repos.userRepo.findOne({ email: identifier });
64
+ * }
65
+ * }
66
+ * ```
67
+ */
68
+ onGetUser: (identifier: string, method: "sms" | "email", payload?: Record<string, any>) => Promise<any | null>;
69
+ /**
70
+ * Callback invoked when OTP verification is successful.
71
+ * Must return user object and JWT token.
72
+ *
73
+ * @param user - The user object returned from onGetUser
74
+ * @param identifier - The user identifier (phone number or email)
75
+ * @param method - The delivery method ('sms' or 'email')
76
+ * @param payload - Optional custom payload from initiation
77
+ * @returns Promise that resolves to user and token
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * onVerifySuccess: async (user, identifier, method, payload) => {
82
+ * const token = await ctx.auth.createToken(
83
+ * { userId: user._id, email: user.email },
84
+ * user.roles
85
+ * );
86
+ * return { user, token };
87
+ * }
88
+ * ```
89
+ */
90
+ onVerifySuccess: (user: any, identifier: string, method: "sms" | "email", payload?: Record<string, any>) => Promise<AuthSuccessCallbackResponse>;
91
+ /**
92
+ * For how long to keep sessions in database (in seconds).
93
+ * This is for data retention purposes only.
94
+ *
95
+ * An expiring index will be created in the database to automatically
96
+ * remove old sessions based on this.
97
+ *
98
+ * Default is 86400 (24 hours).
99
+ */
100
+ keepSessionsSec?: number;
101
+ /**
102
+ * The name of the MongoDB collection to use for storing OTP sessions.
103
+ * Default is "otp_sessions".
104
+ */
105
+ otpSessionsCollectionName?: string;
106
+ /**
107
+ * Whether to register the default HTTP routes for OTP operations.
108
+ * If false, you'll need to implement your own handlers using the functions.
109
+ * Default is true.
110
+ */
111
+ registerRoutes?: boolean;
112
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ import { FlinkContext } from "@flink-app/flink";
2
+ import { OtpAuthPluginContext } from "./OtpAuthPluginContext";
3
+ import OtpSessionRepo from "./repos/OtpSessionRepo";
4
+ export interface OtpInternalContext extends FlinkContext {
5
+ plugins: {
6
+ otpAuth: OtpAuthPluginContext["otpAuth"];
7
+ };
8
+ repos: {
9
+ otpSessionRepo: OtpSessionRepo;
10
+ };
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ import { OtpInternalContext } from "../OtpInternalContext";
2
+ export interface InitiateOptions {
3
+ /** User identifier (phone number or email) */
4
+ identifier: string;
5
+ /** Delivery method for the OTP code */
6
+ method: "sms" | "email";
7
+ /** Optional custom payload to attach to the session */
8
+ payload?: Record<string, any>;
9
+ }
10
+ export interface InitiateResponse {
11
+ /** Unique session ID for this OTP session */
12
+ sessionId: string;
13
+ /** When the code expires */
14
+ expiresAt: Date;
15
+ /** Time-to-live in seconds */
16
+ ttl: number;
17
+ }
18
+ export declare function initiate(ctx: OtpInternalContext, options: InitiateOptions): Promise<InitiateResponse>;
@@ -0,0 +1,104 @@
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 (g && (g = 0, op[0] && (_ = 0)), _) 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.initiate = void 0;
40
+ var flink_1 = require("@flink-app/flink");
41
+ var otp_utils_1 = require("../utils/otp-utils");
42
+ function initiate(ctx, options) {
43
+ return __awaiter(this, void 0, void 0, function () {
44
+ var identifier, method, payload, pluginOptions, normalizedIdentifier, codeLength, code, sessionId, codeTTL, expiresAt, session, sent, error_1;
45
+ return __generator(this, function (_a) {
46
+ switch (_a.label) {
47
+ case 0:
48
+ identifier = options.identifier, method = options.method, payload = options.payload;
49
+ pluginOptions = ctx.plugins.otpAuth.options;
50
+ // Validate identifier format
51
+ if (!(0, otp_utils_1.validateIdentifier)(identifier, method)) {
52
+ flink_1.log.warn("Invalid identifier format for method ".concat(method, ": ").concat(identifier));
53
+ throw (0, flink_1.badRequest)("Invalid ".concat(method === "email" ? "email address" : "phone number", " format"));
54
+ }
55
+ normalizedIdentifier = (0, otp_utils_1.normalizeIdentifier)(identifier, method);
56
+ codeLength = pluginOptions.codeLength || 6;
57
+ if (codeLength < 4 || codeLength > 8) {
58
+ throw new Error("OTP code length must be between 4 and 8 digits");
59
+ }
60
+ code = (0, otp_utils_1.generateOtpCode)(codeLength);
61
+ sessionId = (0, otp_utils_1.generateSessionId)();
62
+ codeTTL = pluginOptions.codeTTL || 300;
63
+ expiresAt = new Date(Date.now() + codeTTL * 1000);
64
+ session = {
65
+ sessionId: sessionId,
66
+ identifier: normalizedIdentifier,
67
+ method: method,
68
+ code: code,
69
+ attempts: 0,
70
+ maxAttempts: pluginOptions.maxAttempts || 3,
71
+ status: "pending",
72
+ createdAt: new Date(),
73
+ expiresAt: expiresAt,
74
+ payload: payload,
75
+ };
76
+ return [4 /*yield*/, ctx.repos.otpSessionRepo.createSession(session)];
77
+ case 1:
78
+ _a.sent();
79
+ _a.label = 2;
80
+ case 2:
81
+ _a.trys.push([2, 4, , 5]);
82
+ return [4 /*yield*/, pluginOptions.onSendCode(code, normalizedIdentifier, method, payload)];
83
+ case 3:
84
+ sent = _a.sent();
85
+ if (!sent) {
86
+ flink_1.log.error("Failed to send OTP code to ".concat(normalizedIdentifier, " via ").concat(method));
87
+ throw new Error("Failed to send verification code");
88
+ }
89
+ flink_1.log.info("OTP code sent to ".concat(normalizedIdentifier, " via ").concat(method, " (session: ").concat(sessionId, ")"));
90
+ return [3 /*break*/, 5];
91
+ case 4:
92
+ error_1 = _a.sent();
93
+ flink_1.log.error("Error sending OTP code: ".concat(error_1));
94
+ throw new Error("Failed to send verification code");
95
+ case 5: return [2 /*return*/, {
96
+ sessionId: sessionId,
97
+ expiresAt: expiresAt,
98
+ ttl: codeTTL,
99
+ }];
100
+ }
101
+ });
102
+ });
103
+ }
104
+ exports.initiate = initiate;
@@ -0,0 +1,20 @@
1
+ import { OtpInternalContext } from "../OtpInternalContext";
2
+ export interface VerifyOptions {
3
+ /** The session ID from the initiate response */
4
+ sessionId: string;
5
+ /** The OTP code entered by the user */
6
+ code: string;
7
+ }
8
+ export interface VerifyResponse {
9
+ /** Verification status */
10
+ status: "success" | "invalid_code" | "expired" | "locked" | "not_found";
11
+ /** JWT token if successful */
12
+ token?: string;
13
+ /** User object if successful */
14
+ user?: any;
15
+ /** Remaining attempts if code was invalid */
16
+ remainingAttempts?: number;
17
+ /** Error message */
18
+ message?: string;
19
+ }
20
+ export declare function verify(ctx: OtpInternalContext, options: VerifyOptions): Promise<VerifyResponse>;