@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,235 @@
|
|
|
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
|
+
var moment_1 = __importDefault(require("moment"));
|
|
43
|
+
/**
|
|
44
|
+
* Re-sending an invite (`SignupService.resendInvite`), against the emulator — outcomes on the row
|
|
45
|
+
* and on the transport fake, never call counts alone:
|
|
46
|
+
*
|
|
47
|
+
* - the token in the earlier email stops resolving, the fresh one resolves to the SAME invite;
|
|
48
|
+
* - the row count for the address stays one, its expiry re-stamped from the TTL knob, its inviter kept;
|
|
49
|
+
* - exactly one email goes out for the re-send, to the address, carrying the fresh signup link;
|
|
50
|
+
* - an address with no standing invite is refused: nothing written, nothing sent;
|
|
51
|
+
* - an address that already has an account is refused the same way (the invite was used).
|
|
52
|
+
*
|
|
53
|
+
* The email transport is stubbed at the module boundary (the SignupInvite.test.ts pattern) — SMTP
|
|
54
|
+
* is an external transport; the config fake echoes the signup path so the link can be asserted.
|
|
55
|
+
* The service door (`serviceMetadata.auth`) is pinned here too: re-send rides the 'users' permission
|
|
56
|
+
* exactly like send and revoke — the earlier gap that left invite minting public must not reopen.
|
|
57
|
+
*/
|
|
58
|
+
var sendEmail = jest.fn();
|
|
59
|
+
jest.mock('@proteinjs/email-server', function () { return ({
|
|
60
|
+
EmailSender: jest.fn().mockImplementation(function () { return ({ sendEmail: sendEmail }); }),
|
|
61
|
+
getDefaultInviteEmailConfigFactory: function () { return ({
|
|
62
|
+
getConfig: function () { return ({
|
|
63
|
+
options: { subject: 'Your invite' },
|
|
64
|
+
getEmailContent: function (signupPathWithToken) { return ({
|
|
65
|
+
text: "Accept: /".concat(signupPathWithToken),
|
|
66
|
+
html: "<a href=\"/".concat(signupPathWithToken, "\">Accept</a>"),
|
|
67
|
+
}); },
|
|
68
|
+
}); },
|
|
69
|
+
}); },
|
|
70
|
+
getDefaultSignupConfirmationEmailConfigFactory: function () { return ({
|
|
71
|
+
getConfig: function () { return ({ getNewUserEmailContent: function () { return ({ text: 'welcome', html: '<p>welcome</p>' }); } }); },
|
|
72
|
+
}); },
|
|
73
|
+
}); });
|
|
74
|
+
var db_1 = require("@proteinjs/db");
|
|
75
|
+
var reflection_1 = require("@proteinjs/reflection");
|
|
76
|
+
var user_1 = require("@proteinjs/user");
|
|
77
|
+
var Signup_1 = require("../src/services/Signup");
|
|
78
|
+
var UserServerTestEnvironment_1 = require("./UserServerTestEnvironment");
|
|
79
|
+
var TIMEOUT = 60000;
|
|
80
|
+
/** Tolerance for the clock advancing between the stamp and the assertion. */
|
|
81
|
+
var CLOCK_SKEW_TOLERANCE_SECONDS = 60;
|
|
82
|
+
var inviteRows = function (email) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
83
|
+
switch (_a.label) {
|
|
84
|
+
case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().query(user_1.tables.Invite, { email: email })];
|
|
85
|
+
case 1: return [2 /*return*/, (_a.sent())];
|
|
86
|
+
}
|
|
87
|
+
}); }); };
|
|
88
|
+
/** The re-send's own transport call: the one addressed to `email` after the earlier `before` calls. */
|
|
89
|
+
var emailsTo = function (email) {
|
|
90
|
+
return sendEmail.mock.calls.map(function (_a) {
|
|
91
|
+
var message = _a[0];
|
|
92
|
+
return message;
|
|
93
|
+
}).filter(function (message) { return message.to === email; });
|
|
94
|
+
};
|
|
95
|
+
describe('SignupService.resendInvite — against the emulator', function () {
|
|
96
|
+
var env = new UserServerTestEnvironment_1.UserServerTestEnvironment();
|
|
97
|
+
var inviter;
|
|
98
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
99
|
+
return __generator(this, function (_a) {
|
|
100
|
+
switch (_a.label) {
|
|
101
|
+
case 0: return [4 /*yield*/, env.beforeAll()];
|
|
102
|
+
case 1:
|
|
103
|
+
_a.sent();
|
|
104
|
+
// The invite-mode factory the token lookup consults (`initializeSignup`) — seeded like the
|
|
105
|
+
// environment seeds its own loadables (the suites load src, never the generated source
|
|
106
|
+
// graph, so an unseeded lookup has no graph to walk); invite-optional, the library default.
|
|
107
|
+
reflection_1.SourceRepository.get().objectCache['@proteinjs/user-server/DefaultInviteConfigFactory'] = [{ getConfig: function () { return ({ isInviteOnly: false }); } }];
|
|
108
|
+
return [4 /*yield*/, env.createUser({ name: 'Inviter', email: 'inviter@example.com', roles: ['admin'] })];
|
|
109
|
+
case 2:
|
|
110
|
+
inviter = _a.sent();
|
|
111
|
+
env.actAs(inviter);
|
|
112
|
+
return [2 /*return*/];
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}); }, TIMEOUT);
|
|
116
|
+
afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
117
|
+
return __generator(this, function (_a) {
|
|
118
|
+
switch (_a.label) {
|
|
119
|
+
case 0: return [4 /*yield*/, env.afterAll()];
|
|
120
|
+
case 1:
|
|
121
|
+
_a.sent();
|
|
122
|
+
return [2 /*return*/];
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}); }, TIMEOUT);
|
|
126
|
+
beforeEach(function () {
|
|
127
|
+
sendEmail.mockClear();
|
|
128
|
+
});
|
|
129
|
+
it('retires the earlier token, mints a fresh one on the SAME row, and emails the fresh link exactly once', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
130
|
+
var email, sent, standing, earlierToken, earlierExpiry, resent, rows, refreshed, earlier, fresh, messages, resend;
|
|
131
|
+
var _a, _b;
|
|
132
|
+
return __generator(this, function (_c) {
|
|
133
|
+
switch (_c.label) {
|
|
134
|
+
case 0:
|
|
135
|
+
email = 'guest@example.com';
|
|
136
|
+
return [4 /*yield*/, new Signup_1.Signup().sendInvite(email)];
|
|
137
|
+
case 1:
|
|
138
|
+
sent = _c.sent();
|
|
139
|
+
expect(sent).toEqual({ sent: true });
|
|
140
|
+
return [4 /*yield*/, inviteRows(email)];
|
|
141
|
+
case 2:
|
|
142
|
+
standing = (_c.sent())[0];
|
|
143
|
+
earlierToken = standing.token;
|
|
144
|
+
earlierExpiry = (0, moment_1.default)(standing.tokenExpiresAt);
|
|
145
|
+
expect(emailsTo(email)).toHaveLength(1);
|
|
146
|
+
return [4 /*yield*/, new Signup_1.Signup().resendInvite('Guest@Example.com')];
|
|
147
|
+
case 3:
|
|
148
|
+
resent = _c.sent();
|
|
149
|
+
expect(resent).toEqual({ sent: true });
|
|
150
|
+
return [4 /*yield*/, inviteRows(email)];
|
|
151
|
+
case 4:
|
|
152
|
+
rows = _c.sent();
|
|
153
|
+
expect(rows).toHaveLength(1);
|
|
154
|
+
refreshed = rows[0];
|
|
155
|
+
expect(refreshed.id).toBe(standing.id);
|
|
156
|
+
expect(refreshed.token).not.toBe(earlierToken);
|
|
157
|
+
expect((_a = refreshed.invitedBy) === null || _a === void 0 ? void 0 : _a._id).toBe(inviter.id);
|
|
158
|
+
// The expiry is re-stamped from the knob (fresh window), never left at the earlier stamp.
|
|
159
|
+
expect((0, moment_1.default)(refreshed.tokenExpiresAt).isSameOrAfter(earlierExpiry)).toBe(true);
|
|
160
|
+
expect(Math.abs((0, moment_1.default)(refreshed.tokenExpiresAt).diff((0, moment_1.default)().add(Signup_1.INVITE_TOKEN_TTL_DAYS, 'days'), 'seconds'))).toBeLessThan(CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
161
|
+
return [4 /*yield*/, new Signup_1.Signup().initializeSignup(earlierToken)];
|
|
162
|
+
case 5:
|
|
163
|
+
earlier = _c.sent();
|
|
164
|
+
expect(earlier.isReady).toBe(false);
|
|
165
|
+
expect(earlier.error).toMatch(/no longer valid/i);
|
|
166
|
+
return [4 /*yield*/, new Signup_1.Signup().initializeSignup(refreshed.token)];
|
|
167
|
+
case 6:
|
|
168
|
+
fresh = _c.sent();
|
|
169
|
+
expect(fresh.isReady).toBe(true);
|
|
170
|
+
expect((_b = fresh.invite) === null || _b === void 0 ? void 0 : _b.email).toBe(email);
|
|
171
|
+
messages = emailsTo(email);
|
|
172
|
+
expect(messages).toHaveLength(2);
|
|
173
|
+
resend = messages[1];
|
|
174
|
+
expect(resend.subject).toBe('Your invite');
|
|
175
|
+
expect(resend.text).toContain("signup?token=".concat(refreshed.token));
|
|
176
|
+
expect(resend.html).toContain("signup?token=".concat(refreshed.token));
|
|
177
|
+
expect(resend.text).not.toContain(earlierToken);
|
|
178
|
+
return [2 /*return*/];
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}); }, TIMEOUT);
|
|
182
|
+
it('refuses an address with no standing invite — nothing written, nothing sent', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
183
|
+
var email, response, _a;
|
|
184
|
+
return __generator(this, function (_b) {
|
|
185
|
+
switch (_b.label) {
|
|
186
|
+
case 0:
|
|
187
|
+
email = 'nobody@example.com';
|
|
188
|
+
return [4 /*yield*/, new Signup_1.Signup().resendInvite(email)];
|
|
189
|
+
case 1:
|
|
190
|
+
response = _b.sent();
|
|
191
|
+
expect(response.sent).toBe(false);
|
|
192
|
+
expect(response.error).toMatch(/no invite/i);
|
|
193
|
+
_a = expect;
|
|
194
|
+
return [4 /*yield*/, inviteRows(email)];
|
|
195
|
+
case 2:
|
|
196
|
+
_a.apply(void 0, [_b.sent()]).toHaveLength(0);
|
|
197
|
+
expect(emailsTo(email)).toHaveLength(0);
|
|
198
|
+
return [2 /*return*/];
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}); }, TIMEOUT);
|
|
202
|
+
it('refuses an address that already has an account — the invite was used', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
203
|
+
var email, response;
|
|
204
|
+
return __generator(this, function (_a) {
|
|
205
|
+
switch (_a.label) {
|
|
206
|
+
case 0:
|
|
207
|
+
email = 'member@example.com';
|
|
208
|
+
return [4 /*yield*/, env.createUser({ name: 'Member', email: email })];
|
|
209
|
+
case 1:
|
|
210
|
+
_a.sent();
|
|
211
|
+
return [4 /*yield*/, new Signup_1.Signup().resendInvite(email)];
|
|
212
|
+
case 2:
|
|
213
|
+
response = _a.sent();
|
|
214
|
+
expect(response.sent).toBe(false);
|
|
215
|
+
expect(response.error).toMatch(/already exists/i);
|
|
216
|
+
expect(emailsTo(email)).toHaveLength(0);
|
|
217
|
+
return [2 /*return*/];
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}); }, TIMEOUT);
|
|
221
|
+
it('rides the users-permission door like send and revoke (never public)', function () {
|
|
222
|
+
var hasPermission = jest.spyOn(user_1.UserAuth, 'hasPermission');
|
|
223
|
+
try {
|
|
224
|
+
hasPermission.mockReturnValue(false);
|
|
225
|
+
expect(new Signup_1.Signup().serviceMetadata.auth.canAccess('resendInvite', ['x@example.com'])).toBe(false);
|
|
226
|
+
hasPermission.mockReturnValue(true);
|
|
227
|
+
expect(new Signup_1.Signup().serviceMetadata.auth.canAccess('resendInvite', ['x@example.com'])).toBe(true);
|
|
228
|
+
expect(hasPermission).toHaveBeenCalledWith(user_1.USER_PERMISSIONS.users);
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
hasPermission.mockRestore();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
//# sourceMappingURL=SignupResendInvite.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignupResendInvite.integration.test.js","sourceRoot":"","sources":["../../test/SignupResendInvite.integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,6 +43,7 @@ var moment_1 = __importDefault(require("moment"));
|
|
|
43
43
|
var db_1 = require("@proteinjs/db");
|
|
44
44
|
var user_1 = require("@proteinjs/user");
|
|
45
45
|
var validateResetPasswordToken_1 = require("../src/routes/validateResetPasswordToken");
|
|
46
|
+
var PasswordResetToken_1 = require("../src/authentication/PasswordResetToken");
|
|
46
47
|
var UserServerTestEnvironment_1 = require("./UserServerTestEnvironment");
|
|
47
48
|
var testEnv = new UserServerTestEnvironment_1.UserServerTestEnvironment();
|
|
48
49
|
var invokeValidate = function (query) { return __awaiter(void 0, void 0, void 0, function () {
|
|
@@ -67,6 +68,7 @@ var invokeValidate = function (query) { return __awaiter(void 0, void 0, void 0,
|
|
|
67
68
|
}
|
|
68
69
|
});
|
|
69
70
|
}); };
|
|
71
|
+
var mintToken = function () { return new PasswordResetToken_1.PasswordResetToken().mint(); };
|
|
70
72
|
var armResetToken = function (email, token, expiration) { return __awaiter(void 0, void 0, void 0, function () {
|
|
71
73
|
var user;
|
|
72
74
|
return __generator(this, function (_a) {
|
|
@@ -107,13 +109,15 @@ describe('validateResetPasswordToken route', function () {
|
|
|
107
109
|
});
|
|
108
110
|
}); });
|
|
109
111
|
it('a valid token resolves isValid WITH the account email (the reset form identifier)', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
110
|
-
var outcome;
|
|
112
|
+
var token, outcome;
|
|
111
113
|
return __generator(this, function (_a) {
|
|
112
114
|
switch (_a.label) {
|
|
113
|
-
case 0:
|
|
115
|
+
case 0:
|
|
116
|
+
token = mintToken();
|
|
117
|
+
return [4 /*yield*/, armResetToken('reset-valid@test.local', token, (0, moment_1.default)().add(1, 'hour'))];
|
|
114
118
|
case 1:
|
|
115
119
|
_a.sent();
|
|
116
|
-
return [4 /*yield*/, invokeValidate({ token:
|
|
120
|
+
return [4 /*yield*/, invokeValidate({ token: token })];
|
|
117
121
|
case 2:
|
|
118
122
|
outcome = _a.sent();
|
|
119
123
|
expect(outcome.status).toBe(200);
|
|
@@ -123,13 +127,15 @@ describe('validateResetPasswordToken route', function () {
|
|
|
123
127
|
});
|
|
124
128
|
}); });
|
|
125
129
|
it('an expired token resolves invalid and leaks no email', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
126
|
-
var outcome;
|
|
130
|
+
var token, outcome;
|
|
127
131
|
return __generator(this, function (_a) {
|
|
128
132
|
switch (_a.label) {
|
|
129
|
-
case 0:
|
|
133
|
+
case 0:
|
|
134
|
+
token = mintToken();
|
|
135
|
+
return [4 /*yield*/, armResetToken('reset-expired@test.local', token, (0, moment_1.default)().subtract(1, 'minute'))];
|
|
130
136
|
case 1:
|
|
131
137
|
_a.sent();
|
|
132
|
-
return [4 /*yield*/, invokeValidate({ token:
|
|
138
|
+
return [4 /*yield*/, invokeValidate({ token: token })];
|
|
133
139
|
case 2:
|
|
134
140
|
outcome = _a.sent();
|
|
135
141
|
expect(outcome.status).toBe(200);
|
|
@@ -143,7 +149,7 @@ describe('validateResetPasswordToken route', function () {
|
|
|
143
149
|
var outcome;
|
|
144
150
|
return __generator(this, function (_a) {
|
|
145
151
|
switch (_a.label) {
|
|
146
|
-
case 0: return [4 /*yield*/, invokeValidate({ token:
|
|
152
|
+
case 0: return [4 /*yield*/, invokeValidate({ token: mintToken() })];
|
|
147
153
|
case 1:
|
|
148
154
|
outcome = _a.sent();
|
|
149
155
|
expect(outcome.status).toBe(200);
|
|
@@ -153,6 +159,23 @@ describe('validateResetPasswordToken route', function () {
|
|
|
153
159
|
}
|
|
154
160
|
});
|
|
155
161
|
}); });
|
|
162
|
+
it.each([
|
|
163
|
+
['a string of another shape', 'tok-never-issued'],
|
|
164
|
+
['a parsed array', ['a', 'b']],
|
|
165
|
+
['a parsed object', { passwordResetToken: null }],
|
|
166
|
+
])('%s resolves invalid without a lookup and leaks no email', function (_label, token) { return __awaiter(void 0, void 0, void 0, function () {
|
|
167
|
+
var outcome;
|
|
168
|
+
return __generator(this, function (_a) {
|
|
169
|
+
switch (_a.label) {
|
|
170
|
+
case 0: return [4 /*yield*/, invokeValidate({ token: token })];
|
|
171
|
+
case 1:
|
|
172
|
+
outcome = _a.sent();
|
|
173
|
+
expect(outcome.status).toBe(200);
|
|
174
|
+
expect(outcome.body).toEqual({ isValid: false, message: 'Invalid token' });
|
|
175
|
+
return [2 /*return*/];
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}); });
|
|
156
179
|
it('a missing token is a 400', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
157
180
|
var outcome;
|
|
158
181
|
return __generator(this, function (_a) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidateResetToken.test.js","sourceRoot":"","sources":["../../test/ValidateResetToken.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAC5B,oCAA8C;AAC9C,wCAAyC;AACzC,uFAAsF;AACtF,yEAAwE;AAExE,IAAM,OAAO,GAAG,IAAI,qDAAyB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"ValidateResetToken.test.js","sourceRoot":"","sources":["../../test/ValidateResetToken.test.ts"],"names":[],"mappings":"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"}
|