@flowerforce/flowerbase 1.8.4-beta.8 → 1.8.4-beta.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.
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +17 -0
- package/dist/auth/providers/local-userpass/dtos.d.ts +5 -0
- package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
- package/dist/auth/utils.d.ts +16 -0
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/auth/utils.js +3 -1
- package/dist/shared/handleUserRegistration.d.ts +8 -0
- package/dist/shared/handleUserRegistration.d.ts.map +1 -1
- package/dist/shared/handleUserRegistration.js +80 -59
- package/package.json +1 -1
- package/src/auth/providers/local-userpass/__tests__/controller.test.ts +67 -19
- package/src/auth/providers/local-userpass/controller.ts +28 -2
- package/src/auth/providers/local-userpass/dtos.ts +6 -0
- package/src/auth/utils.ts +3 -0
- package/src/shared/handleUserRegistration.ts +104 -74
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/local-userpass/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/local-userpass/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AA+CzC;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,eAAe,iBA0bjE"}
|
|
@@ -20,6 +20,7 @@ const state_1 = require("../../../state");
|
|
|
20
20
|
const context_1 = require("../../../utils/context");
|
|
21
21
|
const crypto_1 = require("../../../utils/crypto");
|
|
22
22
|
const utils_1 = require("../../utils");
|
|
23
|
+
const handleUserRegistration_2 = require("../../../shared/handleUserRegistration");
|
|
23
24
|
const rateLimitStore = new Map();
|
|
24
25
|
const isRateLimited = (key, maxAttempts, windowMs) => {
|
|
25
26
|
var _a;
|
|
@@ -210,6 +211,22 @@ function localUserPassController(app) {
|
|
|
210
211
|
res.status(200);
|
|
211
212
|
return { status: 'confirmed' };
|
|
212
213
|
}));
|
|
214
|
+
app.post(utils_1.AUTH_ENDPOINTS.CONFIRM_SEND, {
|
|
215
|
+
schema: utils_1.CONFIRM_SEND_SCHEMA
|
|
216
|
+
}, (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
217
|
+
const localUserpassProvider = resolveLocalUserpassProvider();
|
|
218
|
+
if (!localUserpassProvider || localUserpassProvider.disabled) {
|
|
219
|
+
throw new Error('Local userpass authentication disabled');
|
|
220
|
+
}
|
|
221
|
+
const key = `confirm-send:${req.ip}`;
|
|
222
|
+
if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
|
|
223
|
+
res.status(429).send({ message: 'Too many requests' });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
yield (0, handleUserRegistration_2.sendConfirmationRequest)(app, req.body.email);
|
|
227
|
+
res.status(202);
|
|
228
|
+
return { status: 'ok' };
|
|
229
|
+
}));
|
|
213
230
|
/**
|
|
214
231
|
* Endpoint for user login.
|
|
215
232
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dtos.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/local-userpass/dtos.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,cAAc,CAAA;IACrB,UAAU,EAAE,iBAAiB,CAAA;CAC9B,CAAA;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,eAAe,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,0BAA0B,CAAA;CACvE;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,OAAO,EAAE,CAAA;KACtB,CAAA;CACF;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;CACF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;CACF"}
|
|
1
|
+
{"version":3,"file":"dtos.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/local-userpass/dtos.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,cAAc,CAAA;IACrB,UAAU,EAAE,iBAAiB,CAAA;CAC9B,CAAA;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,eAAe,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,0BAA0B,CAAA;CACvE;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,OAAO,EAAE,CAAA;KACtB,CAAA;CACF;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;CACF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF"}
|
package/dist/auth/utils.d.ts
CHANGED
|
@@ -95,6 +95,21 @@ export declare const CONFIRM_USER_SCHEMA: {
|
|
|
95
95
|
required: string[];
|
|
96
96
|
};
|
|
97
97
|
};
|
|
98
|
+
export declare const CONFIRM_SEND_SCHEMA: {
|
|
99
|
+
tags: string[];
|
|
100
|
+
body: {
|
|
101
|
+
type: string;
|
|
102
|
+
properties: {
|
|
103
|
+
email: {
|
|
104
|
+
type: string;
|
|
105
|
+
pattern: string;
|
|
106
|
+
minLength: number;
|
|
107
|
+
maxLength: number;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
required: string[];
|
|
111
|
+
};
|
|
112
|
+
};
|
|
98
113
|
export declare const RESET_SCHEMA: {
|
|
99
114
|
tags: string[];
|
|
100
115
|
body: {
|
|
@@ -134,6 +149,7 @@ export declare enum AUTH_ENDPOINTS {
|
|
|
134
149
|
LOGIN = "/login",
|
|
135
150
|
REGISTRATION = "/register",
|
|
136
151
|
CONFIRM = "/confirm",
|
|
152
|
+
CONFIRM_SEND = "/confirm/send",
|
|
137
153
|
PROFILE = "/profile",
|
|
138
154
|
SESSION = "/session",
|
|
139
155
|
RESET = "/reset/send",
|
package/dist/auth/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/auth/utils.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc;;;CAA4C,CAAC;AACxE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;CAexB,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;CAc7B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;CAgB7B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;CAWhC,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;CAU/B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAAoB,CAAA;AAE7C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;CAe/B,CAAA;AAED,oBAAY,cAAc;IACxB,KAAK,WAAW;IAChB,YAAY,cAAc;IAC1B,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,KAAK,gBAAgB;IACrB,UAAU,gBAAgB;IAC1B,aAAa,WAAW;IACxB,UAAU,sBAAsB;CACjC;AAED,oBAAY,WAAW;IACrB,mBAAmB,wBAAwB;IAC3C,aAAa,mCAAmC;IAChD,oBAAoB,sCAAsC;IAC1D,sBAAsB,2BAA2B;IACjD,+BAA+B,oCAAoC;IACnE,kBAAkB,uBAAuB;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,aAAa,CAAA;IAC/B,iBAAiB,EAAE,cAAc,CAAA;IACjC,WAAW,CAAC,EAAE,QAAQ,CAAA;CACvB;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AACD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,kBAAkB,EAAE,MAAM,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,OAAO,CAAA;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,uBAAuB,EAAE,OAAO,CAAA;IAChC,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,8BAA8B,EAAE,MAAM,CAAA;CACvC;AAMD;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAO,UAuCjC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,oBAarC,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,eAAW,WAG3C,CAAA"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/auth/utils.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc;;;CAA4C,CAAC;AACxE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;CAexB,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;CAc7B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;CAgB7B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;CAWhC,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;CAU/B,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;CAAoB,CAAA;AAEpD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAAoB,CAAA;AAE7C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;CAe/B,CAAA;AAED,oBAAY,cAAc;IACxB,KAAK,WAAW;IAChB,YAAY,cAAc;IAC1B,OAAO,aAAa;IACpB,YAAY,kBAAkB;IAC9B,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,KAAK,gBAAgB;IACrB,UAAU,gBAAgB;IAC1B,aAAa,WAAW;IACxB,UAAU,sBAAsB;CACjC;AAED,oBAAY,WAAW;IACrB,mBAAmB,wBAAwB;IAC3C,aAAa,mCAAmC;IAChD,oBAAoB,sCAAsC;IAC1D,sBAAsB,2BAA2B;IACjD,+BAA+B,oCAAoC;IACnE,kBAAkB,uBAAuB;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,aAAa,CAAA;IAC/B,iBAAiB,EAAE,cAAc,CAAA;IACjC,WAAW,CAAC,EAAE,QAAQ,CAAA;CACvB;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AACD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,kBAAkB,EAAE,MAAM,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,OAAO,CAAA;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,uBAAuB,EAAE,OAAO,CAAA;IAChC,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,8BAA8B,EAAE,MAAM,CAAA;CACvC;AAMD;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAO,UAuCjC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,oBAarC,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,eAAW,WAG3C,CAAA"}
|
package/dist/auth/utils.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generatePassword = exports.loadCustomUserData = exports.loadAuthConfig = exports.AUTH_ERRORS = exports.AUTH_ENDPOINTS = exports.REGISTRATION_SCHEMA = exports.RESET_SCHEMA = exports.CONFIRM_USER_SCHEMA = exports.CONFIRM_RESET_SCHEMA = exports.RESET_CALL_SCHEMA = exports.RESET_SEND_SCHEMA = exports.LOGIN_SCHEMA = exports.PASSWORD_RULES = void 0;
|
|
6
|
+
exports.generatePassword = exports.loadCustomUserData = exports.loadAuthConfig = exports.AUTH_ERRORS = exports.AUTH_ENDPOINTS = exports.REGISTRATION_SCHEMA = exports.RESET_SCHEMA = exports.CONFIRM_SEND_SCHEMA = exports.CONFIRM_USER_SCHEMA = exports.CONFIRM_RESET_SCHEMA = exports.RESET_CALL_SCHEMA = exports.RESET_SEND_SCHEMA = exports.LOGIN_SCHEMA = exports.PASSWORD_RULES = void 0;
|
|
7
7
|
const crypto_1 = __importDefault(require("crypto"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -80,6 +80,7 @@ exports.CONFIRM_USER_SCHEMA = {
|
|
|
80
80
|
required: ['token', 'tokenId']
|
|
81
81
|
}
|
|
82
82
|
};
|
|
83
|
+
exports.CONFIRM_SEND_SCHEMA = exports.RESET_SEND_SCHEMA;
|
|
83
84
|
exports.RESET_SCHEMA = exports.RESET_SEND_SCHEMA;
|
|
84
85
|
exports.REGISTRATION_SCHEMA = {
|
|
85
86
|
tags: ['Auth'],
|
|
@@ -102,6 +103,7 @@ var AUTH_ENDPOINTS;
|
|
|
102
103
|
AUTH_ENDPOINTS["LOGIN"] = "/login";
|
|
103
104
|
AUTH_ENDPOINTS["REGISTRATION"] = "/register";
|
|
104
105
|
AUTH_ENDPOINTS["CONFIRM"] = "/confirm";
|
|
106
|
+
AUTH_ENDPOINTS["CONFIRM_SEND"] = "/confirm/send";
|
|
105
107
|
AUTH_ENDPOINTS["PROFILE"] = "/profile";
|
|
106
108
|
AUTH_ENDPOINTS["SESSION"] = "/session";
|
|
107
109
|
AUTH_ENDPOINTS["RESET"] = "/reset/send";
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify/types/instance";
|
|
1
2
|
import { HandleUserRegistration } from "./models/handleUserRegistration.model";
|
|
3
|
+
export declare const sendConfirmationRequest: (app: FastifyInstance, email: string) => Promise<{
|
|
4
|
+
status: "success";
|
|
5
|
+
} | {
|
|
6
|
+
status: "pending";
|
|
7
|
+
} | {
|
|
8
|
+
status: "fail";
|
|
9
|
+
} | null>;
|
|
2
10
|
/**
|
|
3
11
|
* Register user
|
|
4
12
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleUserRegistration.d.ts","sourceRoot":"","sources":["../../src/shared/handleUserRegistration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handleUserRegistration.d.ts","sourceRoot":"","sources":["../../src/shared/handleUserRegistration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAM7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAA;AAI9E,eAAO,MAAM,uBAAuB,GAChC,KAAK,eAAe,EACpB,OAAO,MAAM;;;;;;SAgGhB,CAAA;AAED;;;;;;GAMG;AACH,QAAA,MAAM,sBAAsB,EAAE,sBA+E7B,CAAA;AAED,eAAe,sBAAsB,CAAA"}
|
|
@@ -9,11 +9,89 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sendConfirmationRequest = void 0;
|
|
12
13
|
const constants_1 = require("../constants");
|
|
13
14
|
const monitoring_1 = require("../services/monitoring");
|
|
14
15
|
const state_1 = require("../state");
|
|
15
16
|
const context_1 = require("../utils/context");
|
|
16
17
|
const crypto_1 = require("../utils/crypto");
|
|
18
|
+
const sendConfirmationRequest = (app, email) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
var _a;
|
|
20
|
+
const { authCollection } = constants_1.AUTH_CONFIG;
|
|
21
|
+
const localUserpassConfig = constants_1.AUTH_CONFIG.localUserpassConfig;
|
|
22
|
+
const autoConfirm = (localUserpassConfig === null || localUserpassConfig === void 0 ? void 0 : localUserpassConfig.autoConfirm) === true;
|
|
23
|
+
const runConfirmationFunction = (localUserpassConfig === null || localUserpassConfig === void 0 ? void 0 : localUserpassConfig.runConfirmationFunction) === true;
|
|
24
|
+
const confirmationFunctionName = localUserpassConfig === null || localUserpassConfig === void 0 ? void 0 : localUserpassConfig.confirmationFunctionName;
|
|
25
|
+
const normalizedEmail = email.toLowerCase();
|
|
26
|
+
const mongo = app === null || app === void 0 ? void 0 : app.mongo;
|
|
27
|
+
const db = mongo.client.db(constants_1.AUTH_DB_NAME);
|
|
28
|
+
const authUser = yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).findOne({ email: normalizedEmail }));
|
|
29
|
+
if (!authUser) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (authUser.status === 'confirmed' || autoConfirm) {
|
|
33
|
+
return { status: 'success' };
|
|
34
|
+
}
|
|
35
|
+
if (!runConfirmationFunction) {
|
|
36
|
+
throw new Error('Missing confirmation function');
|
|
37
|
+
}
|
|
38
|
+
if (!confirmationFunctionName) {
|
|
39
|
+
throw new Error('Missing confirmation function name');
|
|
40
|
+
}
|
|
41
|
+
const functionsList = state_1.StateManager.select('functions');
|
|
42
|
+
const services = state_1.StateManager.select('services');
|
|
43
|
+
const confirmationFunction = functionsList[confirmationFunctionName];
|
|
44
|
+
if (!confirmationFunction) {
|
|
45
|
+
throw new Error(`Confirmation function not found: ${confirmationFunctionName}`);
|
|
46
|
+
}
|
|
47
|
+
const token = (0, crypto_1.generateToken)();
|
|
48
|
+
const tokenId = (0, crypto_1.generateToken)();
|
|
49
|
+
yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).updateOne({ email: normalizedEmail }, {
|
|
50
|
+
$set: {
|
|
51
|
+
status: 'pending',
|
|
52
|
+
confirmationToken: token,
|
|
53
|
+
confirmationTokenId: tokenId
|
|
54
|
+
}
|
|
55
|
+
}));
|
|
56
|
+
let confirmationStatus = 'fail';
|
|
57
|
+
try {
|
|
58
|
+
const response = yield (0, context_1.GenerateContext)({
|
|
59
|
+
args: [{
|
|
60
|
+
token,
|
|
61
|
+
tokenId,
|
|
62
|
+
username: normalizedEmail
|
|
63
|
+
}],
|
|
64
|
+
app,
|
|
65
|
+
rules: {},
|
|
66
|
+
user: {},
|
|
67
|
+
currentFunction: confirmationFunction,
|
|
68
|
+
functionName: confirmationFunctionName,
|
|
69
|
+
functionsList,
|
|
70
|
+
services,
|
|
71
|
+
runAsSystem: true
|
|
72
|
+
});
|
|
73
|
+
confirmationStatus = (_a = response === null || response === void 0 ? void 0 : response.status) !== null && _a !== void 0 ? _a : 'fail';
|
|
74
|
+
}
|
|
75
|
+
catch (_b) {
|
|
76
|
+
confirmationStatus = 'fail';
|
|
77
|
+
}
|
|
78
|
+
if (confirmationStatus === 'success') {
|
|
79
|
+
yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).updateOne({ email: normalizedEmail }, {
|
|
80
|
+
$set: { status: 'confirmed' },
|
|
81
|
+
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
82
|
+
}));
|
|
83
|
+
return { status: 'success' };
|
|
84
|
+
}
|
|
85
|
+
if (confirmationStatus === 'pending') {
|
|
86
|
+
return { status: 'pending' };
|
|
87
|
+
}
|
|
88
|
+
yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).updateOne({ email: normalizedEmail }, {
|
|
89
|
+
$set: { status: 'failed' },
|
|
90
|
+
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
91
|
+
}));
|
|
92
|
+
return { status: 'fail' };
|
|
93
|
+
});
|
|
94
|
+
exports.sendConfirmationRequest = sendConfirmationRequest;
|
|
17
95
|
/**
|
|
18
96
|
* Register user
|
|
19
97
|
*
|
|
@@ -22,7 +100,7 @@ const crypto_1 = require("../utils/crypto");
|
|
|
22
100
|
* @returns {Promise<InsertOneResult<Document>>} A promise resolving to the result of the insert operation.
|
|
23
101
|
*/
|
|
24
102
|
const handleUserRegistration = (app, opt) => (_a) => __awaiter(void 0, [_a], void 0, function* ({ email, password }) {
|
|
25
|
-
var _b
|
|
103
|
+
var _b;
|
|
26
104
|
const { run_as_system, skipUserCheck, provider } = opt !== null && opt !== void 0 ? opt : {};
|
|
27
105
|
const origin = (_b = opt === null || opt === void 0 ? void 0 : opt.monitoring) === null || _b === void 0 ? void 0 : _b.invokedFrom;
|
|
28
106
|
const meta = { action: 'registerUser', email, provider };
|
|
@@ -40,8 +118,6 @@ const handleUserRegistration = (app, opt) => (_a) => __awaiter(void 0, [_a], voi
|
|
|
40
118
|
const { authCollection } = constants_1.AUTH_CONFIG;
|
|
41
119
|
const localUserpassConfig = constants_1.AUTH_CONFIG.localUserpassConfig;
|
|
42
120
|
const autoConfirm = (localUserpassConfig === null || localUserpassConfig === void 0 ? void 0 : localUserpassConfig.autoConfirm) === true;
|
|
43
|
-
const runConfirmationFunction = (localUserpassConfig === null || localUserpassConfig === void 0 ? void 0 : localUserpassConfig.runConfirmationFunction) === true;
|
|
44
|
-
const confirmationFunctionName = localUserpassConfig === null || localUserpassConfig === void 0 ? void 0 : localUserpassConfig.confirmationFunctionName;
|
|
45
121
|
const mongo = app === null || app === void 0 ? void 0 : app.mongo;
|
|
46
122
|
const db = mongo.client.db(constants_1.AUTH_DB_NAME);
|
|
47
123
|
const hashedPassword = yield (0, crypto_1.hashPassword)(password);
|
|
@@ -79,62 +155,7 @@ const handleUserRegistration = (app, opt) => (_a) => __awaiter(void 0, [_a], voi
|
|
|
79
155
|
if (!(result === null || result === void 0 ? void 0 : result.insertedId) || skipUserCheck || autoConfirm) {
|
|
80
156
|
return result;
|
|
81
157
|
}
|
|
82
|
-
|
|
83
|
-
throw new Error('Missing confirmation function');
|
|
84
|
-
}
|
|
85
|
-
if (!confirmationFunctionName) {
|
|
86
|
-
throw new Error('Missing confirmation function name');
|
|
87
|
-
}
|
|
88
|
-
const functionsList = state_1.StateManager.select('functions');
|
|
89
|
-
const services = state_1.StateManager.select('services');
|
|
90
|
-
const confirmationFunction = functionsList[confirmationFunctionName];
|
|
91
|
-
if (!confirmationFunction) {
|
|
92
|
-
throw new Error(`Confirmation function not found: ${confirmationFunctionName}`);
|
|
93
|
-
}
|
|
94
|
-
const token = (0, crypto_1.generateToken)();
|
|
95
|
-
const tokenId = (0, crypto_1.generateToken)();
|
|
96
|
-
yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).updateOne({ _id: result.insertedId }, {
|
|
97
|
-
$set: {
|
|
98
|
-
confirmationToken: token,
|
|
99
|
-
confirmationTokenId: tokenId
|
|
100
|
-
}
|
|
101
|
-
}));
|
|
102
|
-
let confirmationStatus = 'fail';
|
|
103
|
-
try {
|
|
104
|
-
const response = yield (0, context_1.GenerateContext)({
|
|
105
|
-
args: [{
|
|
106
|
-
token,
|
|
107
|
-
tokenId,
|
|
108
|
-
username: email
|
|
109
|
-
}],
|
|
110
|
-
app,
|
|
111
|
-
rules: {},
|
|
112
|
-
user: {},
|
|
113
|
-
currentFunction: confirmationFunction,
|
|
114
|
-
functionName: confirmationFunctionName,
|
|
115
|
-
functionsList,
|
|
116
|
-
services,
|
|
117
|
-
runAsSystem: true
|
|
118
|
-
});
|
|
119
|
-
confirmationStatus = (_c = response === null || response === void 0 ? void 0 : response.status) !== null && _c !== void 0 ? _c : 'fail';
|
|
120
|
-
}
|
|
121
|
-
catch (_d) {
|
|
122
|
-
confirmationStatus = 'fail';
|
|
123
|
-
}
|
|
124
|
-
if (confirmationStatus === 'success') {
|
|
125
|
-
yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).updateOne({ _id: result.insertedId }, {
|
|
126
|
-
$set: { status: 'confirmed' },
|
|
127
|
-
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
128
|
-
}));
|
|
129
|
-
return result;
|
|
130
|
-
}
|
|
131
|
-
if (confirmationStatus === 'pending') {
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
yield (db === null || db === void 0 ? void 0 : db.collection(authCollection).updateOne({ _id: result.insertedId }, {
|
|
135
|
-
$set: { status: 'failed' },
|
|
136
|
-
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
137
|
-
}));
|
|
158
|
+
yield (0, exports.sendConfirmationRequest)(app, email);
|
|
138
159
|
return result;
|
|
139
160
|
}
|
|
140
161
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -13,6 +13,11 @@ jest.mock('../../../../constants', () => ({
|
|
|
13
13
|
resetPasswordConfig: {
|
|
14
14
|
runResetFunction: true,
|
|
15
15
|
resetFunctionName: 'customReset'
|
|
16
|
+
},
|
|
17
|
+
localUserpassConfig: {
|
|
18
|
+
autoConfirm: false,
|
|
19
|
+
runConfirmationFunction: true,
|
|
20
|
+
confirmationFunctionName: 'customConfirm'
|
|
16
21
|
}
|
|
17
22
|
},
|
|
18
23
|
AUTH_DB_NAME: 'test-auth-db',
|
|
@@ -32,7 +37,8 @@ jest.mock('../../../../state', () => ({
|
|
|
32
37
|
select: jest.fn((key: string) => {
|
|
33
38
|
if (key === 'functions') {
|
|
34
39
|
return {
|
|
35
|
-
customReset: { name: 'customReset', code: 'exports = async () => ({ status: "success" })' }
|
|
40
|
+
customReset: { name: 'customReset', code: 'exports = async () => ({ status: "success" })' },
|
|
41
|
+
customConfirm: { name: 'customConfirm', code: 'exports = async () => ({ status: "pending" })' }
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
if (key === 'services') {
|
|
@@ -61,15 +67,14 @@ import { hashPassword } from '../../../../utils/crypto'
|
|
|
61
67
|
|
|
62
68
|
describe('localUserPassController reset call', () => {
|
|
63
69
|
const buildApp = () => {
|
|
64
|
-
|
|
65
|
-
| ((req: { body: { email: string; password: string; arguments?: unknown[] }; ip: string }, res: { status: jest.Mock }) => Promise<unknown>)
|
|
66
|
-
| undefined
|
|
70
|
+
const routeHandlers: Record<string, ((req: { body: any; ip: string }, res: { status: jest.Mock; send?: jest.Mock }) => Promise<unknown>) | undefined> = {}
|
|
67
71
|
|
|
68
72
|
const authUsersCollection = {
|
|
69
73
|
findOne: jest.fn().mockResolvedValue({
|
|
70
74
|
_id: 'auth-user-1',
|
|
71
75
|
email: 'john@doe.com',
|
|
72
|
-
password: 'old-hash'
|
|
76
|
+
password: 'old-hash',
|
|
77
|
+
status: 'pending'
|
|
73
78
|
}),
|
|
74
79
|
updateOne: jest.fn().mockResolvedValue({ acknowledged: true })
|
|
75
80
|
}
|
|
@@ -97,14 +102,17 @@ describe('localUserPassController reset call', () => {
|
|
|
97
102
|
}
|
|
98
103
|
const app = {
|
|
99
104
|
mongo: { client: { db: jest.fn().mockReturnValue(db) } },
|
|
100
|
-
post: jest.fn((path: string, _opts: unknown, handler:
|
|
101
|
-
|
|
102
|
-
resetCallHandler = handler
|
|
103
|
-
}
|
|
105
|
+
post: jest.fn((path: string, _opts: unknown, handler: (req: { body: any; ip: string }, res: { status: jest.Mock; send?: jest.Mock }) => Promise<unknown>) => {
|
|
106
|
+
routeHandlers[path] = handler
|
|
104
107
|
})
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
app,
|
|
112
|
+
authUsersCollection,
|
|
113
|
+
resetCollection,
|
|
114
|
+
routeHandlerRef: (path: string) => routeHandlers[path]
|
|
115
|
+
}
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
beforeEach(() => {
|
|
@@ -112,13 +120,13 @@ describe('localUserPassController reset call', () => {
|
|
|
112
120
|
})
|
|
113
121
|
|
|
114
122
|
it('hashes and applies the password when the custom reset function returns success', async () => {
|
|
115
|
-
;(GenerateContext as jest.Mock).mockResolvedValue({ status: 'success' })
|
|
116
|
-
const { app, authUsersCollection, resetCollection,
|
|
123
|
+
; (GenerateContext as jest.Mock).mockResolvedValue({ status: 'success' })
|
|
124
|
+
const { app, authUsersCollection, resetCollection, routeHandlerRef } = buildApp()
|
|
117
125
|
|
|
118
126
|
await localUserPassController(app as never)
|
|
119
127
|
|
|
120
128
|
const res = { status: jest.fn() }
|
|
121
|
-
const result = await
|
|
129
|
+
const result = await routeHandlerRef('/reset/call')?.(
|
|
122
130
|
{
|
|
123
131
|
body: { email: 'john@doe.com', password: 'new-secret', arguments: ['extra'] },
|
|
124
132
|
ip: '127.0.0.1'
|
|
@@ -150,13 +158,13 @@ describe('localUserPassController reset call', () => {
|
|
|
150
158
|
})
|
|
151
159
|
|
|
152
160
|
it('returns pending without changing the password when the custom reset function returns pending', async () => {
|
|
153
|
-
;(GenerateContext as jest.Mock).mockResolvedValue({ status: 'pending' })
|
|
154
|
-
const { app, authUsersCollection, resetCollection,
|
|
161
|
+
; (GenerateContext as jest.Mock).mockResolvedValue({ status: 'pending' })
|
|
162
|
+
const { app, authUsersCollection, resetCollection, routeHandlerRef } = buildApp()
|
|
155
163
|
|
|
156
164
|
await localUserPassController(app as never)
|
|
157
165
|
|
|
158
166
|
const res = { status: jest.fn() }
|
|
159
|
-
const result = await
|
|
167
|
+
const result = await routeHandlerRef('/reset/call')?.(
|
|
160
168
|
{
|
|
161
169
|
body: { email: 'john@doe.com', password: 'new-secret' },
|
|
162
170
|
ip: '127.0.0.1'
|
|
@@ -175,15 +183,15 @@ describe('localUserPassController reset call', () => {
|
|
|
175
183
|
})
|
|
176
184
|
|
|
177
185
|
it('rejects the request when the custom reset function returns fail', async () => {
|
|
178
|
-
;(GenerateContext as jest.Mock).mockResolvedValue({ status: 'fail' })
|
|
179
|
-
const { app, authUsersCollection, resetCollection,
|
|
186
|
+
; (GenerateContext as jest.Mock).mockResolvedValue({ status: 'fail' })
|
|
187
|
+
const { app, authUsersCollection, resetCollection, routeHandlerRef } = buildApp()
|
|
180
188
|
|
|
181
189
|
await localUserPassController(app as never)
|
|
182
190
|
|
|
183
191
|
const res = { status: jest.fn() }
|
|
184
192
|
|
|
185
193
|
await expect(
|
|
186
|
-
|
|
194
|
+
routeHandlerRef('/reset/call')?.(
|
|
187
195
|
{
|
|
188
196
|
body: { email: 'john@doe.com', password: 'new-secret' },
|
|
189
197
|
ip: '127.0.0.1'
|
|
@@ -197,4 +205,44 @@ describe('localUserPassController reset call', () => {
|
|
|
197
205
|
expect(resetCollection.deleteOne).not.toHaveBeenCalled()
|
|
198
206
|
expect(res.status).not.toHaveBeenCalled()
|
|
199
207
|
})
|
|
208
|
+
|
|
209
|
+
it('resends the confirmation email for local-userpass users', async () => {
|
|
210
|
+
; (GenerateContext as jest.Mock).mockResolvedValue({ status: 'pending' })
|
|
211
|
+
const { app, authUsersCollection, routeHandlerRef } = buildApp()
|
|
212
|
+
|
|
213
|
+
await localUserPassController(app as never)
|
|
214
|
+
|
|
215
|
+
const res = { status: jest.fn() }
|
|
216
|
+
const result = await routeHandlerRef('/confirm/send')?.(
|
|
217
|
+
{
|
|
218
|
+
body: { email: 'John@Doe.com' },
|
|
219
|
+
ip: '127.0.0.1'
|
|
220
|
+
},
|
|
221
|
+
res
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
expect(GenerateContext).toHaveBeenCalledWith(expect.objectContaining({
|
|
225
|
+
args: [
|
|
226
|
+
{
|
|
227
|
+
token: 'generated-token',
|
|
228
|
+
tokenId: 'generated-token',
|
|
229
|
+
username: 'john@doe.com'
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
functionName: 'customConfirm',
|
|
233
|
+
runAsSystem: true
|
|
234
|
+
}))
|
|
235
|
+
expect(authUsersCollection.updateOne).toHaveBeenCalledWith(
|
|
236
|
+
{ email: 'john@doe.com' },
|
|
237
|
+
{
|
|
238
|
+
$set: {
|
|
239
|
+
status: 'pending',
|
|
240
|
+
confirmationToken: 'generated-token',
|
|
241
|
+
confirmationTokenId: 'generated-token'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
expect(res.status).toHaveBeenCalledWith(202)
|
|
246
|
+
expect(result).toEqual({ status: 'ok' })
|
|
247
|
+
})
|
|
200
248
|
})
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
AUTH_ENDPOINTS,
|
|
16
16
|
AUTH_ERRORS,
|
|
17
17
|
CONFIRM_RESET_SCHEMA,
|
|
18
|
+
CONFIRM_SEND_SCHEMA,
|
|
18
19
|
CONFIRM_USER_SCHEMA,
|
|
19
20
|
LOGIN_SCHEMA,
|
|
20
21
|
REGISTRATION_SCHEMA,
|
|
@@ -23,12 +24,14 @@ import {
|
|
|
23
24
|
} from '../../utils'
|
|
24
25
|
import {
|
|
25
26
|
ConfirmResetPasswordDto,
|
|
27
|
+
ConfirmUserSendDto,
|
|
26
28
|
ConfirmUserDto,
|
|
27
29
|
LoginDto,
|
|
28
30
|
RegistrationDto,
|
|
29
31
|
ResetPasswordCallDto,
|
|
30
32
|
ResetPasswordSendDto
|
|
31
33
|
} from './dtos'
|
|
34
|
+
import { sendConfirmationRequest } from '../../../shared/handleUserRegistration'
|
|
32
35
|
|
|
33
36
|
const rateLimitStore = new Map<string, number[]>()
|
|
34
37
|
|
|
@@ -267,6 +270,29 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
267
270
|
}
|
|
268
271
|
)
|
|
269
272
|
|
|
273
|
+
app.post<ConfirmUserSendDto>(
|
|
274
|
+
AUTH_ENDPOINTS.CONFIRM_SEND,
|
|
275
|
+
{
|
|
276
|
+
schema: CONFIRM_SEND_SCHEMA
|
|
277
|
+
},
|
|
278
|
+
async (req, res) => {
|
|
279
|
+
const localUserpassProvider = resolveLocalUserpassProvider()
|
|
280
|
+
if (!localUserpassProvider || localUserpassProvider.disabled) {
|
|
281
|
+
throw new Error('Local userpass authentication disabled')
|
|
282
|
+
}
|
|
283
|
+
const key = `confirm-send:${req.ip}`
|
|
284
|
+
if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
|
|
285
|
+
res.status(429).send({ message: 'Too many requests' })
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await sendConfirmationRequest(app, req.body.email)
|
|
290
|
+
|
|
291
|
+
res.status(202)
|
|
292
|
+
return { status: 'ok' }
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
|
|
270
296
|
/**
|
|
271
297
|
* Endpoint for user login.
|
|
272
298
|
*
|
|
@@ -308,8 +334,8 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
308
334
|
const user =
|
|
309
335
|
user_id_field && userCollection
|
|
310
336
|
? await customUserDb
|
|
311
|
-
|
|
312
|
-
|
|
337
|
+
.collection(userCollection)
|
|
338
|
+
.findOne({ [user_id_field]: authUser._id.toString() })
|
|
313
339
|
: {}
|
|
314
340
|
delete authUser?.password
|
|
315
341
|
|
package/src/auth/utils.ts
CHANGED
|
@@ -81,6 +81,8 @@ export const CONFIRM_USER_SCHEMA = {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
export const CONFIRM_SEND_SCHEMA = RESET_SEND_SCHEMA
|
|
85
|
+
|
|
84
86
|
export const RESET_SCHEMA = RESET_SEND_SCHEMA
|
|
85
87
|
|
|
86
88
|
export const REGISTRATION_SCHEMA = {
|
|
@@ -104,6 +106,7 @@ export enum AUTH_ENDPOINTS {
|
|
|
104
106
|
LOGIN = '/login',
|
|
105
107
|
REGISTRATION = '/register',
|
|
106
108
|
CONFIRM = '/confirm',
|
|
109
|
+
CONFIRM_SEND = '/confirm/send',
|
|
107
110
|
PROFILE = '/profile',
|
|
108
111
|
SESSION = '/session',
|
|
109
112
|
RESET = '/reset/send',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify/types/instance"
|
|
1
2
|
import { AUTH_CONFIG, AUTH_DB_NAME } from "../constants"
|
|
2
3
|
import { emitServiceEvent } from "../services/monitoring"
|
|
3
4
|
import { StateManager } from "../state"
|
|
@@ -5,6 +6,108 @@ import { GenerateContext } from "../utils/context"
|
|
|
5
6
|
import { generateToken, hashPassword } from "../utils/crypto"
|
|
6
7
|
import { HandleUserRegistration } from "./models/handleUserRegistration.model"
|
|
7
8
|
|
|
9
|
+
type ConfirmationResult = { status?: 'success' | 'pending' | 'fail' }
|
|
10
|
+
|
|
11
|
+
export const sendConfirmationRequest = async (
|
|
12
|
+
app: FastifyInstance,
|
|
13
|
+
email: string
|
|
14
|
+
) => {
|
|
15
|
+
const { authCollection } = AUTH_CONFIG
|
|
16
|
+
const localUserpassConfig = AUTH_CONFIG.localUserpassConfig
|
|
17
|
+
const autoConfirm = localUserpassConfig?.autoConfirm === true
|
|
18
|
+
const runConfirmationFunction = localUserpassConfig?.runConfirmationFunction === true
|
|
19
|
+
const confirmationFunctionName = localUserpassConfig?.confirmationFunctionName
|
|
20
|
+
const normalizedEmail = email.toLowerCase()
|
|
21
|
+
const mongo = app?.mongo
|
|
22
|
+
const db = mongo.client.db(AUTH_DB_NAME)
|
|
23
|
+
const authUser = await db?.collection(authCollection!).findOne({ email: normalizedEmail }) as {
|
|
24
|
+
status?: string
|
|
25
|
+
} | null
|
|
26
|
+
|
|
27
|
+
if (!authUser) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (authUser.status === 'confirmed' || autoConfirm) {
|
|
32
|
+
return { status: 'success' as const }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!runConfirmationFunction) {
|
|
36
|
+
throw new Error('Missing confirmation function')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!confirmationFunctionName) {
|
|
40
|
+
throw new Error('Missing confirmation function name')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const functionsList = StateManager.select('functions')
|
|
44
|
+
const services = StateManager.select('services')
|
|
45
|
+
const confirmationFunction = functionsList[confirmationFunctionName]
|
|
46
|
+
if (!confirmationFunction) {
|
|
47
|
+
throw new Error(`Confirmation function not found: ${confirmationFunctionName}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const token = generateToken()
|
|
51
|
+
const tokenId = generateToken()
|
|
52
|
+
await db?.collection(authCollection!).updateOne(
|
|
53
|
+
{ email: normalizedEmail },
|
|
54
|
+
{
|
|
55
|
+
$set: {
|
|
56
|
+
status: 'pending',
|
|
57
|
+
confirmationToken: token,
|
|
58
|
+
confirmationTokenId: tokenId
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
let confirmationStatus: ConfirmationResult['status'] = 'fail'
|
|
64
|
+
try {
|
|
65
|
+
const response = await GenerateContext({
|
|
66
|
+
args: [{
|
|
67
|
+
token,
|
|
68
|
+
tokenId,
|
|
69
|
+
username: normalizedEmail
|
|
70
|
+
}],
|
|
71
|
+
app,
|
|
72
|
+
rules: {},
|
|
73
|
+
user: {},
|
|
74
|
+
currentFunction: confirmationFunction,
|
|
75
|
+
functionName: confirmationFunctionName,
|
|
76
|
+
functionsList,
|
|
77
|
+
services,
|
|
78
|
+
runAsSystem: true
|
|
79
|
+
}) as ConfirmationResult
|
|
80
|
+
confirmationStatus = response?.status ?? 'fail'
|
|
81
|
+
} catch {
|
|
82
|
+
confirmationStatus = 'fail'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (confirmationStatus === 'success') {
|
|
86
|
+
await db?.collection(authCollection!).updateOne(
|
|
87
|
+
{ email: normalizedEmail },
|
|
88
|
+
{
|
|
89
|
+
$set: { status: 'confirmed' },
|
|
90
|
+
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
return { status: 'success' as const }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (confirmationStatus === 'pending') {
|
|
97
|
+
return { status: 'pending' as const }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await db?.collection(authCollection!).updateOne(
|
|
101
|
+
{ email: normalizedEmail },
|
|
102
|
+
{
|
|
103
|
+
$set: { status: 'failed' },
|
|
104
|
+
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return { status: 'fail' as const }
|
|
109
|
+
}
|
|
110
|
+
|
|
8
111
|
/**
|
|
9
112
|
* Register user
|
|
10
113
|
*
|
|
@@ -32,8 +135,6 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
32
135
|
const { authCollection } = AUTH_CONFIG
|
|
33
136
|
const localUserpassConfig = AUTH_CONFIG.localUserpassConfig
|
|
34
137
|
const autoConfirm = localUserpassConfig?.autoConfirm === true
|
|
35
|
-
const runConfirmationFunction = localUserpassConfig?.runConfirmationFunction === true
|
|
36
|
-
const confirmationFunctionName = localUserpassConfig?.confirmationFunctionName
|
|
37
138
|
const mongo = app?.mongo
|
|
38
139
|
const db = mongo.client.db(AUTH_DB_NAME)
|
|
39
140
|
const hashedPassword = await hashPassword(password)
|
|
@@ -79,78 +180,7 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
79
180
|
return result
|
|
80
181
|
}
|
|
81
182
|
|
|
82
|
-
|
|
83
|
-
throw new Error('Missing confirmation function')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!confirmationFunctionName) {
|
|
87
|
-
throw new Error('Missing confirmation function name')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const functionsList = StateManager.select('functions')
|
|
91
|
-
const services = StateManager.select('services')
|
|
92
|
-
const confirmationFunction = functionsList[confirmationFunctionName]
|
|
93
|
-
if (!confirmationFunction) {
|
|
94
|
-
throw new Error(`Confirmation function not found: ${confirmationFunctionName}`)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const token = generateToken()
|
|
98
|
-
const tokenId = generateToken()
|
|
99
|
-
await db?.collection(authCollection!).updateOne(
|
|
100
|
-
{ _id: result.insertedId },
|
|
101
|
-
{
|
|
102
|
-
$set: {
|
|
103
|
-
confirmationToken: token,
|
|
104
|
-
confirmationTokenId: tokenId
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
type ConfirmationResult = { status?: 'success' | 'pending' | 'fail' }
|
|
110
|
-
let confirmationStatus: ConfirmationResult['status'] = 'fail'
|
|
111
|
-
try {
|
|
112
|
-
const response = await GenerateContext({
|
|
113
|
-
args: [{
|
|
114
|
-
token,
|
|
115
|
-
tokenId,
|
|
116
|
-
username: email
|
|
117
|
-
}],
|
|
118
|
-
app,
|
|
119
|
-
rules: {},
|
|
120
|
-
user: {},
|
|
121
|
-
currentFunction: confirmationFunction,
|
|
122
|
-
functionName: confirmationFunctionName,
|
|
123
|
-
functionsList,
|
|
124
|
-
services,
|
|
125
|
-
runAsSystem: true
|
|
126
|
-
}) as ConfirmationResult
|
|
127
|
-
confirmationStatus = response?.status ?? 'fail'
|
|
128
|
-
} catch {
|
|
129
|
-
confirmationStatus = 'fail'
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (confirmationStatus === 'success') {
|
|
133
|
-
await db?.collection(authCollection!).updateOne(
|
|
134
|
-
{ _id: result.insertedId },
|
|
135
|
-
{
|
|
136
|
-
$set: { status: 'confirmed' },
|
|
137
|
-
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
return result
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (confirmationStatus === 'pending') {
|
|
144
|
-
return result
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
await db?.collection(authCollection!).updateOne(
|
|
148
|
-
{ _id: result.insertedId },
|
|
149
|
-
{
|
|
150
|
-
$set: { status: 'failed' },
|
|
151
|
-
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
152
|
-
}
|
|
153
|
-
)
|
|
183
|
+
await sendConfirmationRequest(app, email)
|
|
154
184
|
return result
|
|
155
185
|
} catch (error) {
|
|
156
186
|
emitServiceEvent({
|