@proteinjs/user-server 1.20.2 → 1.21.1
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/CHANGELOG.md +22 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/src/authentication/PasswordResetToken.d.ts +45 -0
- package/dist/src/authentication/PasswordResetToken.d.ts.map +1 -0
- package/dist/src/authentication/PasswordResetToken.js +138 -0
- package/dist/src/authentication/PasswordResetToken.js.map +1 -0
- package/dist/src/routes/executePasswordReset.d.ts +4 -4
- package/dist/src/routes/executePasswordReset.d.ts.map +1 -1
- package/dist/src/routes/executePasswordReset.js +27 -26
- package/dist/src/routes/executePasswordReset.js.map +1 -1
- package/dist/src/routes/initiatePasswordReset.js +2 -2
- package/dist/src/routes/initiatePasswordReset.js.map +1 -1
- package/dist/src/routes/validateResetPasswordToken.d.ts.map +1 -1
- package/dist/src/routes/validateResetPasswordToken.js +13 -16
- package/dist/src/routes/validateResetPasswordToken.js.map +1 -1
- package/dist/src/services/Signup.d.ts +14 -0
- package/dist/src/services/Signup.d.ts.map +1 -1
- package/dist/src/services/Signup.js +102 -27
- package/dist/src/services/Signup.js.map +1 -1
- package/dist/test/ExecutePasswordReset.test.d.ts +2 -0
- package/dist/test/ExecutePasswordReset.test.d.ts.map +1 -0
- package/dist/test/ExecutePasswordReset.test.js +394 -0
- package/dist/test/ExecutePasswordReset.test.js.map +1 -0
- package/dist/test/SignupResendInvite.integration.test.d.ts +2 -0
- package/dist/test/SignupResendInvite.integration.test.d.ts.map +1 -0
- package/dist/test/SignupResendInvite.integration.test.js +235 -0
- package/dist/test/SignupResendInvite.integration.test.js.map +1 -0
- package/dist/test/ValidateResetToken.test.js +30 -7
- package/dist/test/ValidateResetToken.test.js.map +1 -1
- package/generated/index.ts +1 -1
- package/package.json +3 -3
- package/src/authentication/PasswordResetToken.ts +96 -0
- package/src/routes/executePasswordReset.ts +25 -24
- package/src/routes/initiatePasswordReset.ts +2 -2
- package/src/routes/validateResetPasswordToken.ts +12 -15
- package/src/services/Signup.ts +71 -21
- package/test/ExecutePasswordReset.test.ts +224 -0
- package/test/SignupResendInvite.integration.test.ts +163 -0
- package/test/ValidateResetToken.test.ts +24 -6
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.PasswordResetToken = void 0;
|
|
43
|
+
var crypto_1 = require("crypto");
|
|
44
|
+
var moment_1 = __importDefault(require("moment"));
|
|
45
|
+
var db_1 = require("@proteinjs/db");
|
|
46
|
+
var user_1 = require("@proteinjs/user");
|
|
47
|
+
/**
|
|
48
|
+
* The password-reset token's one owner: its shape, its mint, the lookup that maps a presented
|
|
49
|
+
* value back to the user it was issued to, and its single-use redemption.
|
|
50
|
+
*
|
|
51
|
+
* A token is 32 bytes from the platform CSPRNG, hex-encoded (64 lowercase hex characters),
|
|
52
|
+
* stored on the user row beside its expiry until it is redeemed or expires.
|
|
53
|
+
*
|
|
54
|
+
* Resolution refuses anything that is not a value of that shape BEFORE any lookup. A query
|
|
55
|
+
* filter built from a request value that is not a token does not compare the way a token does:
|
|
56
|
+
* `null` renders as `IS NULL` and would match every account with no pending reset, an empty
|
|
57
|
+
* string matches an emptied column, and other types reach the driver. The row the lookup
|
|
58
|
+
* returns is then re-checked in code — its stored token must be a string equal to the presented
|
|
59
|
+
* one (compared in constant time) and its expiry a real timestamp still in the future — so the
|
|
60
|
+
* outcome never rests on how the storage compares.
|
|
61
|
+
*/
|
|
62
|
+
var PasswordResetToken = /** @class */ (function () {
|
|
63
|
+
function PasswordResetToken() {
|
|
64
|
+
}
|
|
65
|
+
PasswordResetToken.prototype.mint = function () {
|
|
66
|
+
return (0, crypto_1.randomBytes)(32).toString('hex');
|
|
67
|
+
};
|
|
68
|
+
PasswordResetToken.prototype.resolve = function (presented) {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
70
|
+
var token, user;
|
|
71
|
+
return __generator(this, function (_a) {
|
|
72
|
+
switch (_a.label) {
|
|
73
|
+
case 0:
|
|
74
|
+
token = this.parse(presented);
|
|
75
|
+
if (token === undefined) {
|
|
76
|
+
return [2 /*return*/, { status: 'malformed' }];
|
|
77
|
+
}
|
|
78
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { passwordResetToken: token })];
|
|
79
|
+
case 1:
|
|
80
|
+
user = _a.sent();
|
|
81
|
+
if (!user || !this.matches(user.passwordResetToken, token)) {
|
|
82
|
+
return [2 /*return*/, { status: 'unknown' }];
|
|
83
|
+
}
|
|
84
|
+
if (!this.isLive(user.passwordResetTokenExpiration)) {
|
|
85
|
+
return [2 /*return*/, { status: 'expired', user: user }];
|
|
86
|
+
}
|
|
87
|
+
return [2 /*return*/, { status: 'live', user: user, token: token }];
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Write the new credential and clear the token in one conditional update: the row must still
|
|
94
|
+
* carry this token at write time, so two presentations of the same token cannot both succeed.
|
|
95
|
+
* Resolves false when the token was already redeemed.
|
|
96
|
+
*/
|
|
97
|
+
PasswordResetToken.prototype.redeem = function (user, token, hashedPassword) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
99
|
+
var updated;
|
|
100
|
+
return __generator(this, function (_a) {
|
|
101
|
+
switch (_a.label) {
|
|
102
|
+
case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, { password: hashedPassword, passwordResetToken: null, passwordResetTokenExpiration: null }, { id: user.id, passwordResetToken: token })];
|
|
103
|
+
case 1:
|
|
104
|
+
updated = _a.sent();
|
|
105
|
+
return [2 /*return*/, updated === 1];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
/** A log-safe reference to a presented token: a short digest of a well-formed one, never the value itself. */
|
|
111
|
+
PasswordResetToken.prototype.fingerprint = function (presented) {
|
|
112
|
+
var token = this.parse(presented);
|
|
113
|
+
if (token === undefined) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return (0, crypto_1.createHash)('sha256').update(token).digest('hex').slice(0, 12);
|
|
117
|
+
};
|
|
118
|
+
PasswordResetToken.prototype.parse = function (presented) {
|
|
119
|
+
return typeof presented === 'string' && PasswordResetToken.SHAPE.test(presented) ? presented : undefined;
|
|
120
|
+
};
|
|
121
|
+
PasswordResetToken.prototype.matches = function (stored, presented) {
|
|
122
|
+
if (typeof stored !== 'string' || stored.length !== presented.length) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return (0, crypto_1.timingSafeEqual)(Buffer.from(stored), Buffer.from(presented));
|
|
126
|
+
};
|
|
127
|
+
PasswordResetToken.prototype.isLive = function (expiration) {
|
|
128
|
+
if (!expiration) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
var expiresAt = (0, moment_1.default)(expiration);
|
|
132
|
+
return expiresAt.isValid() && (0, moment_1.default)().isBefore(expiresAt);
|
|
133
|
+
};
|
|
134
|
+
PasswordResetToken.SHAPE = /^[0-9a-f]{64}$/;
|
|
135
|
+
return PasswordResetToken;
|
|
136
|
+
}());
|
|
137
|
+
exports.PasswordResetToken = PasswordResetToken;
|
|
138
|
+
//# sourceMappingURL=PasswordResetToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordResetToken.js","sourceRoot":"","sources":["../../../src/authentication/PasswordResetToken.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,13 +2,13 @@ import { Route } from '@proteinjs/server-api';
|
|
|
2
2
|
/**
|
|
3
3
|
* Route handler for executing a password reset.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Resolves the presented token through `PasswordResetToken` — which refuses anything but a
|
|
6
|
+
* well-formed token before any lookup — checks its expiry, and redeems it: the new password is
|
|
7
|
+
* written and the token cleared in one conditional update, so a token resets a password once.
|
|
8
|
+
* The token itself never reaches the log.
|
|
7
9
|
*
|
|
8
10
|
* @bodyParam {string} token - The password reset token.
|
|
9
11
|
* @bodyParam {string} newPassword - The new password for the user.
|
|
10
|
-
*
|
|
11
|
-
* @throws {Error} If there's an issue with the database operations or if the token is invalid or expired.
|
|
12
12
|
*/
|
|
13
13
|
export declare const executePasswordReset: Route;
|
|
14
14
|
//# sourceMappingURL=executePasswordReset.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executePasswordReset.d.ts","sourceRoot":"","sources":["../../../src/routes/executePasswordReset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"executePasswordReset.d.ts","sourceRoot":"","sources":["../../../src/routes/executePasswordReset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAM9C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAuClC,CAAC"}
|
|
@@ -35,64 +35,65 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
-
};
|
|
41
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
39
|
exports.executePasswordReset = void 0;
|
|
43
|
-
var db_1 = require("@proteinjs/db");
|
|
44
40
|
var user_1 = require("@proteinjs/user");
|
|
45
41
|
var logger_1 = require("@proteinjs/logger");
|
|
46
|
-
var moment_1 = __importDefault(require("moment"));
|
|
47
42
|
var PasswordHasher_1 = require("../authentication/PasswordHasher");
|
|
43
|
+
var PasswordResetToken_1 = require("../authentication/PasswordResetToken");
|
|
48
44
|
/**
|
|
49
45
|
* Route handler for executing a password reset.
|
|
50
46
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
47
|
+
* Resolves the presented token through `PasswordResetToken` — which refuses anything but a
|
|
48
|
+
* well-formed token before any lookup — checks its expiry, and redeems it: the new password is
|
|
49
|
+
* written and the token cleared in one conditional update, so a token resets a password once.
|
|
50
|
+
* The token itself never reaches the log.
|
|
53
51
|
*
|
|
54
52
|
* @bodyParam {string} token - The password reset token.
|
|
55
53
|
* @bodyParam {string} newPassword - The new password for the user.
|
|
56
|
-
*
|
|
57
|
-
* @throws {Error} If there's an issue with the database operations or if the token is invalid or expired.
|
|
58
54
|
*/
|
|
59
55
|
exports.executePasswordReset = {
|
|
60
56
|
path: user_1.routes.executePasswordReset.path,
|
|
61
57
|
method: user_1.routes.executePasswordReset.method,
|
|
62
58
|
onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
|
|
63
|
-
var logger, _a, token, newPassword,
|
|
59
|
+
var logger, _a, token, newPassword, resetToken, resolution, user, hashedPassword;
|
|
64
60
|
return __generator(this, function (_b) {
|
|
65
61
|
switch (_b.label) {
|
|
66
62
|
case 0:
|
|
67
63
|
logger = new logger_1.Logger({ name: 'executePasswordReset' });
|
|
68
64
|
_a = request.body, token = _a.token, newPassword = _a.newPassword;
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
if (typeof newPassword !== 'string' || newPassword.length === 0) {
|
|
66
|
+
response.status(400).send({ error: 'New password cannot be blank' });
|
|
67
|
+
return [2 /*return*/];
|
|
68
|
+
}
|
|
69
|
+
resetToken = new PasswordResetToken_1.PasswordResetToken();
|
|
70
|
+
return [4 /*yield*/, resetToken.resolve(token)];
|
|
71
71
|
case 1:
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
logger.info({
|
|
72
|
+
resolution = _b.sent();
|
|
73
|
+
if (resolution.status === 'malformed' || resolution.status === 'unknown') {
|
|
74
|
+
logger.info({
|
|
75
|
+
message: "Invalid reset token used",
|
|
76
|
+
obj: { reason: resolution.status, token: resetToken.fingerprint(token) },
|
|
77
|
+
});
|
|
75
78
|
response.status(400).send({ error: 'Invalid or expired reset token' });
|
|
76
79
|
return [2 /*return*/];
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (currentTime.isAfter(tokenExpiration)) {
|
|
81
|
-
logger.info({ message: "Expired reset token used", obj: { email: user.email } });
|
|
81
|
+
if (resolution.status === 'expired') {
|
|
82
|
+
logger.info({ message: "Expired reset token used", obj: { email: resolution.user.email } });
|
|
82
83
|
response.status(400).send({ error: 'Reset token has expired' });
|
|
83
84
|
return [2 /*return*/];
|
|
84
85
|
}
|
|
86
|
+
user = resolution.user;
|
|
85
87
|
return [4 /*yield*/, new PasswordHasher_1.PasswordHasher().hash(newPassword)];
|
|
86
88
|
case 2:
|
|
87
89
|
hashedPassword = _b.sent();
|
|
88
|
-
return [4 /*yield*/,
|
|
89
|
-
id: user.id,
|
|
90
|
-
password: hashedPassword,
|
|
91
|
-
passwordResetToken: null,
|
|
92
|
-
passwordResetTokenExpiration: null,
|
|
93
|
-
})];
|
|
90
|
+
return [4 /*yield*/, resetToken.redeem(user, resolution.token, hashedPassword)];
|
|
94
91
|
case 3:
|
|
95
|
-
_b.sent()
|
|
92
|
+
if (!(_b.sent())) {
|
|
93
|
+
logger.info({ message: "Reset token already redeemed", obj: { email: user.email } });
|
|
94
|
+
response.status(400).send({ error: 'Invalid or expired reset token' });
|
|
95
|
+
return [2 /*return*/];
|
|
96
|
+
}
|
|
96
97
|
logger.info({ message: "Password successfully reset", obj: { email: user.email } });
|
|
97
98
|
response.send({ message: 'Password has been successfully reset' });
|
|
98
99
|
return [2 /*return*/];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executePasswordReset.js","sourceRoot":"","sources":["../../../src/routes/executePasswordReset.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"executePasswordReset.js","sourceRoot":"","sources":["../../../src/routes/executePasswordReset.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wCAAyC;AACzC,4CAA2C;AAC3C,mEAAkE;AAClE,2EAA0E;AAE1E;;;;;;;;;;GAUG;AACU,QAAA,oBAAoB,GAAU;IACzC,IAAI,EAAE,aAAM,CAAC,oBAAoB,CAAC,IAAI;IACtC,MAAM,EAAE,aAAM,CAAC,oBAAoB,CAAC,MAAM;IAC1C,SAAS,EAAE,UAAO,OAAO,EAAE,QAAQ;;;;;oBAC3B,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;oBACtD,KAAyB,OAAO,CAAC,IAAI,EAAnC,KAAK,WAAA,EAAE,WAAW,iBAAA,CAAkB;oBAC5C,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;wBAC/D,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;wBACrE,sBAAO;qBACR;oBAEK,UAAU,GAAG,IAAI,uCAAkB,EAAE,CAAC;oBACzB,qBAAM,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAA;;oBAA5C,UAAU,GAAG,SAA+B;oBAClD,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE;wBACxE,MAAM,CAAC,IAAI,CAAC;4BACV,OAAO,EAAE,0BAA0B;4BACnC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;yBACzE,CAAC,CAAC;wBACH,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;wBACvE,sBAAO;qBACR;oBAED,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE;wBACnC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,0BAA0B,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBAC5F,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;wBAChE,sBAAO;qBACR;oBAEO,IAAI,GAAK,UAAU,KAAf,CAAgB;oBACL,qBAAM,IAAI,+BAAc,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAA;;oBAA7D,cAAc,GAAG,SAA4C;oBAC7D,qBAAM,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,EAAA;;oBAArE,IAAI,CAAC,CAAC,SAA+D,CAAC,EAAE;wBACtE,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,8BAA8B,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBACrF,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;wBACvE,sBAAO;qBACR;oBAED,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,6BAA6B,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC,CAAC;;;;SACpE;CACF,CAAC"}
|
|
@@ -56,7 +56,7 @@ var user_1 = require("@proteinjs/user");
|
|
|
56
56
|
var logger_1 = require("@proteinjs/logger");
|
|
57
57
|
var moment_1 = __importDefault(require("moment"));
|
|
58
58
|
var email_server_1 = require("@proteinjs/email-server");
|
|
59
|
-
var
|
|
59
|
+
var PasswordResetToken_1 = require("../authentication/PasswordResetToken");
|
|
60
60
|
/**
|
|
61
61
|
* Route for initiating a password reset process.
|
|
62
62
|
*
|
|
@@ -108,7 +108,7 @@ exports.initiatePasswordReset = {
|
|
|
108
108
|
if (!defaultConfigFactory) {
|
|
109
109
|
throw new Error("Unable to find a @proteinjs/email-server/DefaultPasswordResetEmailConfigFactory implementation when initiating password reset.");
|
|
110
110
|
}
|
|
111
|
-
passwordResetToken =
|
|
111
|
+
passwordResetToken = new PasswordResetToken_1.PasswordResetToken().mint();
|
|
112
112
|
passwordResetTokenExpiration = (0, moment_1.default)().add(1, 'hour');
|
|
113
113
|
_d.label = 2;
|
|
114
114
|
case 2:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initiatePasswordReset.js","sourceRoot":"","sources":["../../../src/routes/initiatePasswordReset.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,oCAA8C;AAC9C,wCAA2D;AAC3D,4CAA2C;AAC3C,kDAA4B;AAC5B,wDAGiC;AACjC,
|
|
1
|
+
{"version":3,"file":"initiatePasswordReset.js","sourceRoot":"","sources":["../../../src/routes/initiatePasswordReset.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateResetPasswordToken.d.ts","sourceRoot":"","sources":["../../../src/routes/validateResetPasswordToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"validateResetPasswordToken.d.ts","sourceRoot":"","sources":["../../../src/routes/validateResetPasswordToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAK9C,eAAO,MAAM,0BAA0B,EAAE,KAkCxC,CAAC"}
|
|
@@ -35,42 +35,39 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
-
};
|
|
41
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
39
|
exports.validateResetPasswordToken = void 0;
|
|
43
|
-
var db_1 = require("@proteinjs/db");
|
|
44
40
|
var user_1 = require("@proteinjs/user");
|
|
45
41
|
var logger_1 = require("@proteinjs/logger");
|
|
46
|
-
var
|
|
42
|
+
var PasswordResetToken_1 = require("../authentication/PasswordResetToken");
|
|
47
43
|
exports.validateResetPasswordToken = {
|
|
48
44
|
path: user_1.routes.validateResetToken.path,
|
|
49
45
|
method: user_1.routes.validateResetToken.method,
|
|
50
46
|
onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
|
|
51
|
-
var logger, token,
|
|
47
|
+
var logger, token, resetToken, resolution;
|
|
52
48
|
return __generator(this, function (_a) {
|
|
53
49
|
switch (_a.label) {
|
|
54
50
|
case 0:
|
|
55
51
|
logger = new logger_1.Logger({ name: 'validateResetToken' });
|
|
56
52
|
token = request.query.token;
|
|
57
|
-
db = (0, db_1.getDbAsSystem)();
|
|
58
53
|
if (!token) {
|
|
59
54
|
response.status(400).send({ isValid: false, message: 'No token provided' });
|
|
60
55
|
return [2 /*return*/];
|
|
61
56
|
}
|
|
62
|
-
|
|
57
|
+
resetToken = new PasswordResetToken_1.PasswordResetToken();
|
|
58
|
+
return [4 /*yield*/, resetToken.resolve(token)];
|
|
63
59
|
case 1:
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
logger.info({
|
|
60
|
+
resolution = _a.sent();
|
|
61
|
+
if (resolution.status === 'malformed' || resolution.status === 'unknown') {
|
|
62
|
+
logger.info({
|
|
63
|
+
message: "Invalid reset token used",
|
|
64
|
+
obj: { reason: resolution.status, token: resetToken.fingerprint(token) },
|
|
65
|
+
});
|
|
67
66
|
response.status(200).send({ isValid: false, message: 'Invalid token' });
|
|
68
67
|
return [2 /*return*/];
|
|
69
68
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (currentTime.isAfter(tokenExpiration)) {
|
|
73
|
-
logger.info({ message: "Expired reset token used", obj: { email: user.email } });
|
|
69
|
+
if (resolution.status === 'expired') {
|
|
70
|
+
logger.info({ message: "Expired reset token used", obj: { email: resolution.user.email } });
|
|
74
71
|
response.status(200).send({ isValid: false, message: 'Token has expired' });
|
|
75
72
|
return [2 /*return*/];
|
|
76
73
|
}
|
|
@@ -78,7 +75,7 @@ exports.validateResetPasswordToken = {
|
|
|
78
75
|
// read-only `autocomplete="username"` field so password managers associate the updated
|
|
79
76
|
// password with the stored credential. The token was delivered to this very inbox, so a
|
|
80
77
|
// valid-token holder learns nothing new; invalid/expired verdicts stay email-free.
|
|
81
|
-
response.status(200).send({ isValid: true, email: user.email });
|
|
78
|
+
response.status(200).send({ isValid: true, email: resolution.user.email });
|
|
82
79
|
return [2 /*return*/];
|
|
83
80
|
}
|
|
84
81
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateResetPasswordToken.js","sourceRoot":"","sources":["../../../src/routes/validateResetPasswordToken.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validateResetPasswordToken.js","sourceRoot":"","sources":["../../../src/routes/validateResetPasswordToken.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wCAAyC;AACzC,4CAA2C;AAC3C,2EAA0E;AAE7D,QAAA,0BAA0B,GAAU;IAC/C,IAAI,EAAE,aAAM,CAAC,kBAAkB,CAAC,IAAI;IACpC,MAAM,EAAE,aAAM,CAAC,kBAAkB,CAAC,MAAM;IACxC,SAAS,EAAE,UAAO,OAAO,EAAE,QAAQ;;;;;oBAC3B,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAC;oBAClD,KAAK,GAAK,OAAO,CAAC,KAAK,MAAlB,CAAmB;oBAChC,IAAI,CAAC,KAAK,EAAE;wBACV,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;wBAC5E,sBAAO;qBACR;oBAEK,UAAU,GAAG,IAAI,uCAAkB,EAAE,CAAC;oBACzB,qBAAM,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAA;;oBAA5C,UAAU,GAAG,SAA+B;oBAClD,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE;wBACxE,MAAM,CAAC,IAAI,CAAC;4BACV,OAAO,EAAE,0BAA0B;4BACnC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;yBACzE,CAAC,CAAC;wBACH,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;wBACxE,sBAAO;qBACR;oBAED,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE;wBACnC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,0BAA0B,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBAC5F,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;wBAC5E,sBAAO;qBACR;oBAED,oFAAoF;oBACpF,uFAAuF;oBACvF,wFAAwF;oBACxF,mFAAmF;oBACnF,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;;;;SAC5E;CACF,CAAC"}
|
|
@@ -50,6 +50,14 @@ export declare class Signup implements SignupService {
|
|
|
50
50
|
*/
|
|
51
51
|
createUser(user: UserSignup, token?: string): Promise<CreateUserResult>;
|
|
52
52
|
sendInvite(email: string): Promise<SendInviteResponse>;
|
|
53
|
+
/**
|
|
54
|
+
* Re-sends a standing invite: the token in the earlier email stops working, a fresh token with
|
|
55
|
+
* a fresh expiry takes its place on the same row (never a second row), and the invite email goes
|
|
56
|
+
* out again through the same config. Refused when no invite stands for the address — there is
|
|
57
|
+
* nothing to re-send — and when the address already has an account (the invite was used).
|
|
58
|
+
* The inviter stays whoever sent it first.
|
|
59
|
+
*/
|
|
60
|
+
resendInvite(email: string): Promise<SendInviteResponse>;
|
|
53
61
|
revokeInvite(email: string): Promise<void>;
|
|
54
62
|
/**
|
|
55
63
|
* Initializes signup process, validating invite configuration and token if provided.
|
|
@@ -73,5 +81,11 @@ export declare class Signup implements SignupService {
|
|
|
73
81
|
}): Promise<'created' | 'exists'>;
|
|
74
82
|
/** Resolves an invite token, distinguishing "expired" from "never existed / already revoked". */
|
|
75
83
|
private lookupInvite;
|
|
84
|
+
/** The invite email content factory the consumer registers; its absence is a misconfiguration, said aloud. */
|
|
85
|
+
private inviteEmailConfig;
|
|
86
|
+
/** A fresh redeemable token with its expiry from the TTL knob — the one minting path for send and re-send. */
|
|
87
|
+
private mintInviteToken;
|
|
88
|
+
/** The invite email itself — the signup link carrying `token`, rendered by the consumer's config. */
|
|
89
|
+
private emailInvite;
|
|
76
90
|
}
|
|
77
91
|
//# sourceMappingURL=Signup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Signup.d.ts","sourceRoot":"","sources":["../../../src/services/Signup.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAIlB,aAAa,EACb,wBAAwB,EAExB,MAAM,EACN,IAAI,EACJ,UAAU,EAEX,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Signup.d.ts","sourceRoot":"","sources":["../../../src/services/Signup.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAIlB,aAAa,EACb,wBAAwB,EAExB,MAAM,EACN,IAAI,EACJ,UAAU,EAEX,MAAM,iBAAiB,CAAC;AAUzB,OAAO,EAAE,QAAQ,EAAoB,MAAM,uBAAuB,CAAC;AAGnE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC;AAEhH,MAAM,WAAW,0BAA2B,SAAQ,QAAQ;IAC1D,SAAS,IAAI,YAAY,CAAC;CAC3B;AAED,eAAO,MAAM,6BAA6B,QAAO,0BAShD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC9B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,MAAO,YAAW,aAAa;IACnC,eAAe;;oCAEM,MAAM,QAAQ,GAAG,EAAE;;MAY7C;IAEF;;;;;OAKG;IACG,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2DvE,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyC5D;;;;;;OAMG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA4BxD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD;;;OAGG;IACG,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAoD1F;;;;;;;OAOG;IACG,aAAa,CAAC,OAAO,EAAE;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,OAAO,CAAC;QACvB,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;KAC9B,GAAG,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC;IAmBjC,iGAAiG;YACnF,YAAY;IAe1B,8GAA8G;IAC9G,OAAO,CAAC,iBAAiB;IAUzB,8GAA8G;IAC9G,OAAO,CAAC,eAAe;IAOvB,qGAAqG;YACvF,WAAW;CAU1B"}
|
|
@@ -82,7 +82,7 @@ var Signup = /** @class */ (function () {
|
|
|
82
82
|
// previously evaluated the check WITHOUT returning it, which made sendInvite/revokeInvite
|
|
83
83
|
// effectively public — any caller (even logged out) could mint themselves a valid signup
|
|
84
84
|
// token and bypass invite-only signup.
|
|
85
|
-
if (methodName === 'sendInvite' || methodName === 'revokeInvite') {
|
|
85
|
+
if (methodName === 'sendInvite' || methodName === 'resendInvite' || methodName === 'revokeInvite') {
|
|
86
86
|
return user_1.UserAuth.hasPermission(user_1.USER_PERMISSIONS.users);
|
|
87
87
|
}
|
|
88
88
|
return true;
|
|
@@ -155,40 +155,33 @@ var Signup = /** @class */ (function () {
|
|
|
155
155
|
});
|
|
156
156
|
};
|
|
157
157
|
Signup.prototype.sendInvite = function (email) {
|
|
158
|
-
var _a;
|
|
159
158
|
return __awaiter(this, void 0, void 0, function () {
|
|
160
|
-
var logger, caseInsensitiveEmail, db, userRecord,
|
|
161
|
-
return __generator(this, function (
|
|
162
|
-
switch (
|
|
159
|
+
var logger, caseInsensitiveEmail, db, userRecord, config, _a, token, tokenExpiresAt, invite, userId, error_1;
|
|
160
|
+
return __generator(this, function (_b) {
|
|
161
|
+
switch (_b.label) {
|
|
163
162
|
case 0:
|
|
164
163
|
logger = new logger_1.Logger({ name: 'Signup.sendInvite' });
|
|
165
164
|
caseInsensitiveEmail = email.toLowerCase();
|
|
166
|
-
|
|
165
|
+
_b.label = 1;
|
|
167
166
|
case 1:
|
|
168
|
-
|
|
167
|
+
_b.trys.push([1, 9, , 10]);
|
|
169
168
|
db = (0, db_1.getDbAsSystem)();
|
|
170
169
|
return [4 /*yield*/, db.get(user_1.tables.User, { email: caseInsensitiveEmail })];
|
|
171
170
|
case 2:
|
|
172
|
-
userRecord =
|
|
171
|
+
userRecord = _b.sent();
|
|
173
172
|
if (userRecord) {
|
|
174
173
|
return [2 /*return*/, { sent: false, error: 'User already exists with that email.' }];
|
|
175
174
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (!defaultConfigFactory) {
|
|
179
|
-
throw new Error("Unable to find a @proteinjs/email-server/DefaultInviteEmailConfigFactory implementation when sending invite.");
|
|
180
|
-
}
|
|
181
|
-
config = defaultConfigFactory.getConfig();
|
|
182
|
-
token = crypto_js_1.lib.WordArray.random(32).toString();
|
|
183
|
-
tokenExpiresAt = (0, moment_1.default)().add(exports.INVITE_TOKEN_TTL_DAYS, 'days');
|
|
175
|
+
config = this.inviteEmailConfig();
|
|
176
|
+
_a = this.mintInviteToken(), token = _a.token, tokenExpiresAt = _a.tokenExpiresAt;
|
|
184
177
|
return [4 /*yield*/, db.get(user_1.tables.Invite, { email: caseInsensitiveEmail })];
|
|
185
178
|
case 3:
|
|
186
|
-
invite =
|
|
179
|
+
invite = _b.sent();
|
|
187
180
|
if (!invite) return [3 /*break*/, 5];
|
|
188
181
|
invite = __assign(__assign({}, invite), { token: token, tokenExpiresAt: tokenExpiresAt });
|
|
189
182
|
return [4 /*yield*/, db.update(user_1.tables.Invite, invite)];
|
|
190
183
|
case 4:
|
|
191
|
-
|
|
184
|
+
_b.sent();
|
|
192
185
|
return [3 /*break*/, 7];
|
|
193
186
|
case 5:
|
|
194
187
|
userId = new user_1.UserRepo().getUser().id;
|
|
@@ -199,16 +192,14 @@ var Signup = /** @class */ (function () {
|
|
|
199
192
|
invitedBy: new db_1.Reference(user_1.tables.User.name, userId),
|
|
200
193
|
})];
|
|
201
194
|
case 6:
|
|
202
|
-
invite =
|
|
203
|
-
|
|
204
|
-
case 7:
|
|
205
|
-
_b = config.getEmailContent("".concat(user_1.uiRoutes.auth.signup, "?token=").concat(token)), text = _b.text, html = _b.html;
|
|
206
|
-
return [4 /*yield*/, emailSender.sendEmail(__assign({ to: caseInsensitiveEmail, subject: ((_a = config.options) === null || _a === void 0 ? void 0 : _a.subject) || "You're Invited", text: text, html: html }, config.options))];
|
|
195
|
+
invite = _b.sent();
|
|
196
|
+
_b.label = 7;
|
|
197
|
+
case 7: return [4 /*yield*/, this.emailInvite(caseInsensitiveEmail, token, config)];
|
|
207
198
|
case 8:
|
|
208
|
-
|
|
199
|
+
_b.sent();
|
|
209
200
|
return [2 /*return*/, { sent: true }];
|
|
210
201
|
case 9:
|
|
211
|
-
error_1 =
|
|
202
|
+
error_1 = _b.sent();
|
|
212
203
|
logger.error({ message: 'Error sending invite', obj: { email: caseInsensitiveEmail }, error: error_1 });
|
|
213
204
|
return [2 /*return*/, {
|
|
214
205
|
sent: false,
|
|
@@ -219,6 +210,58 @@ var Signup = /** @class */ (function () {
|
|
|
219
210
|
});
|
|
220
211
|
});
|
|
221
212
|
};
|
|
213
|
+
/**
|
|
214
|
+
* Re-sends a standing invite: the token in the earlier email stops working, a fresh token with
|
|
215
|
+
* a fresh expiry takes its place on the same row (never a second row), and the invite email goes
|
|
216
|
+
* out again through the same config. Refused when no invite stands for the address — there is
|
|
217
|
+
* nothing to re-send — and when the address already has an account (the invite was used).
|
|
218
|
+
* The inviter stays whoever sent it first.
|
|
219
|
+
*/
|
|
220
|
+
Signup.prototype.resendInvite = function (email) {
|
|
221
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
222
|
+
var logger, caseInsensitiveEmail, db, userRecord, invite, config, _a, token, tokenExpiresAt, error_2;
|
|
223
|
+
return __generator(this, function (_b) {
|
|
224
|
+
switch (_b.label) {
|
|
225
|
+
case 0:
|
|
226
|
+
logger = new logger_1.Logger({ name: 'Signup.resendInvite' });
|
|
227
|
+
caseInsensitiveEmail = email.toLowerCase();
|
|
228
|
+
_b.label = 1;
|
|
229
|
+
case 1:
|
|
230
|
+
_b.trys.push([1, 6, , 7]);
|
|
231
|
+
db = (0, db_1.getDbAsSystem)();
|
|
232
|
+
return [4 /*yield*/, db.get(user_1.tables.User, { email: caseInsensitiveEmail })];
|
|
233
|
+
case 2:
|
|
234
|
+
userRecord = _b.sent();
|
|
235
|
+
if (userRecord) {
|
|
236
|
+
return [2 /*return*/, { sent: false, error: 'User already exists with that email.' }];
|
|
237
|
+
}
|
|
238
|
+
return [4 /*yield*/, db.get(user_1.tables.Invite, { email: caseInsensitiveEmail })];
|
|
239
|
+
case 3:
|
|
240
|
+
invite = _b.sent();
|
|
241
|
+
if (!invite) {
|
|
242
|
+
return [2 /*return*/, { sent: false, error: 'No invite exists for that email.' }];
|
|
243
|
+
}
|
|
244
|
+
config = this.inviteEmailConfig();
|
|
245
|
+
_a = this.mintInviteToken(), token = _a.token, tokenExpiresAt = _a.tokenExpiresAt;
|
|
246
|
+
return [4 /*yield*/, db.update(user_1.tables.Invite, __assign(__assign({}, invite), { token: token, tokenExpiresAt: tokenExpiresAt }))];
|
|
247
|
+
case 4:
|
|
248
|
+
_b.sent();
|
|
249
|
+
return [4 /*yield*/, this.emailInvite(caseInsensitiveEmail, token, config)];
|
|
250
|
+
case 5:
|
|
251
|
+
_b.sent();
|
|
252
|
+
return [2 /*return*/, { sent: true }];
|
|
253
|
+
case 6:
|
|
254
|
+
error_2 = _b.sent();
|
|
255
|
+
logger.error({ message: 'Error re-sending invite', obj: { email: caseInsensitiveEmail }, error: error_2 });
|
|
256
|
+
return [2 /*return*/, {
|
|
257
|
+
sent: false,
|
|
258
|
+
error: 'Error occurred.',
|
|
259
|
+
}];
|
|
260
|
+
case 7: return [2 /*return*/];
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
};
|
|
222
265
|
Signup.prototype.revokeInvite = function (email) {
|
|
223
266
|
return __awaiter(this, void 0, void 0, function () {
|
|
224
267
|
var db;
|
|
@@ -243,7 +286,7 @@ var Signup = /** @class */ (function () {
|
|
|
243
286
|
*/
|
|
244
287
|
Signup.prototype.initializeSignup = function (inviteToken) {
|
|
245
288
|
return __awaiter(this, void 0, void 0, function () {
|
|
246
|
-
var config, isInviteOnly, lookup,
|
|
289
|
+
var config, isInviteOnly, lookup, error_3;
|
|
247
290
|
return __generator(this, function (_a) {
|
|
248
291
|
switch (_a.label) {
|
|
249
292
|
case 0:
|
|
@@ -286,7 +329,7 @@ var Signup = /** @class */ (function () {
|
|
|
286
329
|
isInviteOnly: isInviteOnly,
|
|
287
330
|
}];
|
|
288
331
|
case 3:
|
|
289
|
-
|
|
332
|
+
error_3 = _a.sent();
|
|
290
333
|
return [2 /*return*/, {
|
|
291
334
|
isReady: false,
|
|
292
335
|
error: 'Initializing sign up failed.',
|
|
@@ -360,6 +403,38 @@ var Signup = /** @class */ (function () {
|
|
|
360
403
|
});
|
|
361
404
|
});
|
|
362
405
|
};
|
|
406
|
+
/** The invite email content factory the consumer registers; its absence is a misconfiguration, said aloud. */
|
|
407
|
+
Signup.prototype.inviteEmailConfig = function () {
|
|
408
|
+
var defaultConfigFactory = (0, email_server_1.getDefaultInviteEmailConfigFactory)();
|
|
409
|
+
if (!defaultConfigFactory) {
|
|
410
|
+
throw new Error("Unable to find a @proteinjs/email-server/DefaultInviteEmailConfigFactory implementation when sending invite.");
|
|
411
|
+
}
|
|
412
|
+
return defaultConfigFactory.getConfig();
|
|
413
|
+
};
|
|
414
|
+
/** A fresh redeemable token with its expiry from the TTL knob — the one minting path for send and re-send. */
|
|
415
|
+
Signup.prototype.mintInviteToken = function () {
|
|
416
|
+
return {
|
|
417
|
+
token: crypto_js_1.lib.WordArray.random(32).toString(),
|
|
418
|
+
tokenExpiresAt: (0, moment_1.default)().add(exports.INVITE_TOKEN_TTL_DAYS, 'days'),
|
|
419
|
+
};
|
|
420
|
+
};
|
|
421
|
+
/** The invite email itself — the signup link carrying `token`, rendered by the consumer's config. */
|
|
422
|
+
Signup.prototype.emailInvite = function (email, token, config) {
|
|
423
|
+
var _a;
|
|
424
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
425
|
+
var _b, text, html;
|
|
426
|
+
return __generator(this, function (_c) {
|
|
427
|
+
switch (_c.label) {
|
|
428
|
+
case 0:
|
|
429
|
+
_b = config.getEmailContent("".concat(user_1.uiRoutes.auth.signup, "?token=").concat(token)), text = _b.text, html = _b.html;
|
|
430
|
+
return [4 /*yield*/, new email_server_1.EmailSender().sendEmail(__assign({ to: email, subject: ((_a = config.options) === null || _a === void 0 ? void 0 : _a.subject) || "You're Invited", text: text, html: html }, config.options))];
|
|
431
|
+
case 1:
|
|
432
|
+
_c.sent();
|
|
433
|
+
return [2 /*return*/];
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
};
|
|
363
438
|
return Signup;
|
|
364
439
|
}());
|
|
365
440
|
exports.Signup = Signup;
|