@otp-service/express 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 otp-service contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
import { GenerateChallengeInput, OtpService, VerifyChallengeInput } from '@otp-service/core';
|
|
3
|
+
|
|
4
|
+
interface CreateGenerateChallengeHandlerOptions {
|
|
5
|
+
mapBody?: (body: unknown) => GenerateChallengeInput;
|
|
6
|
+
otpService: OtpService;
|
|
7
|
+
}
|
|
8
|
+
interface CreateVerifyChallengeHandlerOptions {
|
|
9
|
+
mapBody?: (body: unknown) => VerifyChallengeInput;
|
|
10
|
+
otpService: OtpService;
|
|
11
|
+
}
|
|
12
|
+
declare function createGenerateChallengeHandler(options: CreateGenerateChallengeHandlerOptions): RequestHandler;
|
|
13
|
+
declare function createVerifyChallengeHandler(options: CreateVerifyChallengeHandlerOptions): RequestHandler;
|
|
14
|
+
declare class ExpressOtpValidationError extends Error {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { type CreateGenerateChallengeHandlerOptions, type CreateVerifyChallengeHandlerOptions, ExpressOtpValidationError, createGenerateChallengeHandler, createVerifyChallengeHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function createGenerateChallengeHandler(options) {
|
|
3
|
+
return async (request, response, next) => {
|
|
4
|
+
try {
|
|
5
|
+
const input = (options.mapBody ?? mapGenerateChallengeBody)(request.body);
|
|
6
|
+
const result = await options.otpService.generateChallenge(input);
|
|
7
|
+
response.status(201).json(result);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error instanceof ExpressOtpValidationError) {
|
|
10
|
+
response.status(422).json({
|
|
11
|
+
error: {
|
|
12
|
+
code: "VALIDATION_ERROR",
|
|
13
|
+
message: error.message
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
next(error);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function createVerifyChallengeHandler(options) {
|
|
23
|
+
return async (request, response, next) => {
|
|
24
|
+
try {
|
|
25
|
+
const input = (options.mapBody ?? mapVerifyChallengeBody)(request.body);
|
|
26
|
+
const result = await options.otpService.verifyChallenge(input);
|
|
27
|
+
response.status(statusCodeForVerifyResult(result)).json(result);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof ExpressOtpValidationError) {
|
|
30
|
+
response.status(422).json({
|
|
31
|
+
error: {
|
|
32
|
+
code: "VALIDATION_ERROR",
|
|
33
|
+
message: error.message
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
next(error);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
var ExpressOtpValidationError = class extends Error {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "ExpressOtpValidationError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function mapGenerateChallengeBody(body) {
|
|
49
|
+
const record = requireObject(body, "Generate challenge body must be an object.");
|
|
50
|
+
return {
|
|
51
|
+
channel: requireChannel(record.channel),
|
|
52
|
+
purpose: requireTrimmedString(record.purpose, "Generate challenge purpose must be a non-empty string."),
|
|
53
|
+
recipient: requireTrimmedString(record.recipient, "Generate challenge recipient must be a non-empty string.")
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function mapVerifyChallengeBody(body) {
|
|
57
|
+
const record = requireObject(body, "Verify challenge body must be an object.");
|
|
58
|
+
return {
|
|
59
|
+
challengeId: requireTrimmedString(
|
|
60
|
+
record.challengeId,
|
|
61
|
+
"Verify challenge challengeId must be a non-empty string."
|
|
62
|
+
),
|
|
63
|
+
otp: requireTrimmedString(record.otp, "Verify challenge otp must be a non-empty string.")
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function requireObject(value, message) {
|
|
67
|
+
if (typeof value !== "object" || value === null) {
|
|
68
|
+
throw new ExpressOtpValidationError(message);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function requireTrimmedString(value, message) {
|
|
73
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
74
|
+
throw new ExpressOtpValidationError(message);
|
|
75
|
+
}
|
|
76
|
+
return value.trim();
|
|
77
|
+
}
|
|
78
|
+
function requireChannel(value) {
|
|
79
|
+
if (value === "sms" || value === "email") {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
throw new ExpressOtpValidationError("Generate challenge channel must be either sms or email.");
|
|
83
|
+
}
|
|
84
|
+
function statusCodeForVerifyResult(result) {
|
|
85
|
+
switch (result.status) {
|
|
86
|
+
case "VERIFIED":
|
|
87
|
+
return 200;
|
|
88
|
+
case "INVALID":
|
|
89
|
+
return 400;
|
|
90
|
+
case "EXPIRED":
|
|
91
|
+
return 410;
|
|
92
|
+
case "ATTEMPTS_EXCEEDED":
|
|
93
|
+
return 429;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { ExpressOtpValidationError, createGenerateChallengeHandler, createVerifyChallengeHandler };
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { RequestHandler } from \"express\";\n\nimport type {\n GenerateChallengeInput,\n OtpService,\n VerifyChallengeInput,\n VerifyChallengeResult\n} from \"@otp-service/core\";\n\nexport interface CreateGenerateChallengeHandlerOptions {\n mapBody?: (body: unknown) => GenerateChallengeInput;\n otpService: OtpService;\n}\n\nexport interface CreateVerifyChallengeHandlerOptions {\n mapBody?: (body: unknown) => VerifyChallengeInput;\n otpService: OtpService;\n}\n\nexport function createGenerateChallengeHandler(\n options: CreateGenerateChallengeHandlerOptions\n): RequestHandler {\n return async (request, response, next) => {\n try {\n const input = (options.mapBody ?? mapGenerateChallengeBody)(request.body);\n const result = await options.otpService.generateChallenge(input);\n response.status(201).json(result);\n } catch (error) {\n if (error instanceof ExpressOtpValidationError) {\n response.status(422).json({\n error: {\n code: \"VALIDATION_ERROR\",\n message: error.message\n }\n });\n return;\n }\n\n next(error);\n }\n };\n}\n\nexport function createVerifyChallengeHandler(\n options: CreateVerifyChallengeHandlerOptions\n): RequestHandler {\n return async (request, response, next) => {\n try {\n const input = (options.mapBody ?? mapVerifyChallengeBody)(request.body);\n const result = await options.otpService.verifyChallenge(input);\n response.status(statusCodeForVerifyResult(result)).json(result);\n } catch (error) {\n if (error instanceof ExpressOtpValidationError) {\n response.status(422).json({\n error: {\n code: \"VALIDATION_ERROR\",\n message: error.message\n }\n });\n return;\n }\n\n next(error);\n }\n };\n}\n\nexport class ExpressOtpValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ExpressOtpValidationError\";\n }\n}\n\nfunction mapGenerateChallengeBody(body: unknown): GenerateChallengeInput {\n const record = requireObject(body, \"Generate challenge body must be an object.\");\n return {\n channel: requireChannel(record.channel),\n purpose: requireTrimmedString(record.purpose, \"Generate challenge purpose must be a non-empty string.\"),\n recipient: requireTrimmedString(record.recipient, \"Generate challenge recipient must be a non-empty string.\")\n };\n}\n\nfunction mapVerifyChallengeBody(body: unknown): VerifyChallengeInput {\n const record = requireObject(body, \"Verify challenge body must be an object.\");\n return {\n challengeId: requireTrimmedString(\n record.challengeId,\n \"Verify challenge challengeId must be a non-empty string.\"\n ),\n otp: requireTrimmedString(record.otp, \"Verify challenge otp must be a non-empty string.\")\n };\n}\n\nfunction requireObject(value: unknown, message: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) {\n throw new ExpressOtpValidationError(message);\n }\n\n return value as Record<string, unknown>;\n}\n\nfunction requireTrimmedString(value: unknown, message: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ExpressOtpValidationError(message);\n }\n\n return value.trim();\n}\n\nfunction requireChannel(value: unknown): GenerateChallengeInput[\"channel\"] {\n if (value === \"sms\" || value === \"email\") {\n return value;\n }\n\n throw new ExpressOtpValidationError(\"Generate challenge channel must be either sms or email.\");\n}\n\nfunction statusCodeForVerifyResult(result: VerifyChallengeResult): number {\n switch (result.status) {\n case \"VERIFIED\":\n return 200;\n case \"INVALID\":\n return 400;\n case \"EXPIRED\":\n return 410;\n case \"ATTEMPTS_EXCEEDED\":\n return 429;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@otp-service/express",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Express route helpers for OTP generate and verify flows.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Suraj-H/otp-service-package-v2.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Suraj-H/otp-service-package-v2/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/Suraj-H/otp-service-package-v2/tree/main/packages/express#readme",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.0.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@otp-service/core": "0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"express": "^4.21.0 || ^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/express": "^5.0.3",
|
|
41
|
+
"express": "^5.1.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup --config tsup.config.ts",
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"lint": "eslint src test",
|
|
47
|
+
"test": "vitest run --config vitest.config.ts",
|
|
48
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|