@proteinjs/user-server 1.4.5 → 1.5.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.
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ 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;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ var moment_1 = __importDefault(require("moment"));
54
+ /**
55
+ * Covers invite token lifetime and the expired-token user experience.
56
+ *
57
+ * Two things are asserted here:
58
+ * 1. `sendInvite` stamps `tokenExpiresAt` from the `INVITE_TOKEN_TTL_DAYS` knob (not a literal),
59
+ * on both the create and the refresh path.
60
+ * 2. An expired token produces an honest, actionable message at `initializeSignup` — the surface
61
+ * the signup page reads — and is distinguishable from a token that never existed / was revoked.
62
+ * Previously both collapsed into "not found or has expired", and outside invite-only mode an
63
+ * expired token wasn't reported at all: signup rendered with the email field hidden and the user
64
+ * could only reach a generic "Sign up failed." on submit.
65
+ *
66
+ * The service's collaborators are stubbed at the module boundary; no db or mail server needed.
67
+ */
68
+ var dbGet = jest.fn();
69
+ var dbInsert = jest.fn();
70
+ var dbUpdate = jest.fn();
71
+ var dbDelete = jest.fn();
72
+ var sendEmail = jest.fn();
73
+ var db = { get: dbGet, insert: dbInsert, update: dbUpdate, delete: dbDelete };
74
+ // Only the db accessors are stubbed; the table/column classes stay real because the table
75
+ // definitions in @proteinjs/user extend them at import time.
76
+ jest.mock('@proteinjs/db', function () { return (__assign(__assign({}, jest.requireActual('@proteinjs/db')), { getDb: function () { return db; }, getDbAsSystem: function () { return db; } })); });
77
+ jest.mock('@proteinjs/email-server', function () { return ({
78
+ EmailSender: jest.fn().mockImplementation(function () { return ({ sendEmail: sendEmail }); }),
79
+ getDefaultInviteEmailConfigFactory: function () { return ({
80
+ getConfig: function () { return ({ getEmailContent: function () { return ({ text: 'invite', html: '<p>invite</p>' }); } }); },
81
+ }); },
82
+ getDefaultSignupConfirmationEmailConfigFactory: function () { return ({
83
+ getConfig: function () { return ({ getNewUserEmailContent: function () { return ({ text: 'welcome', html: '<p>welcome</p>' }); } }); },
84
+ }); },
85
+ }); });
86
+ // `merge` is kept real so the generated source graphs still load; only lookup is stubbed, so
87
+ // `getDefaultInviteConfigFactory` falls back to its invite-optional default.
88
+ jest.mock('@proteinjs/reflection', function () { return (__assign(__assign({}, jest.requireActual('@proteinjs/reflection')), { SourceRepository: __assign(__assign({}, jest.requireActual('@proteinjs/reflection').SourceRepository), { get: function () { return ({ object: function () { return undefined; } }); } }) })); });
89
+ var user_1 = require("@proteinjs/user");
90
+ var Signup_1 = require("../src/services/Signup");
91
+ var EXPECTED_TTL_DAYS = 90;
92
+ /** Tolerance for the clock advancing between the service stamping the token and the assertion. */
93
+ var CLOCK_SKEW_TOLERANCE_SECONDS = 60;
94
+ /**
95
+ * Compares against the same `moment().add(days, 'days')` arithmetic the service uses, so a DST
96
+ * boundary inside the window (90 days crosses one) cancels out instead of showing up as an hour of
97
+ * drift. A wrong TTL is still off by days, so this stays a biting assertion.
98
+ */
99
+ var secondsFromExpectedTtl = function (tokenExpiresAt, ttlDays) {
100
+ return Math.abs((0, moment_1.default)(tokenExpiresAt).diff((0, moment_1.default)().add(ttlDays, 'days'), 'seconds'));
101
+ };
102
+ describe('invite token lifetime', function () {
103
+ beforeEach(function () {
104
+ jest.restoreAllMocks();
105
+ jest.clearAllMocks();
106
+ dbInsert.mockImplementation(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
107
+ return [2 /*return*/, record];
108
+ }); }); });
109
+ // `sendInvite` stamps the inviter from the session; there is no session in a unit test.
110
+ jest.spyOn(user_1.UserRepo.prototype, 'getUser').mockReturnValue({ id: 'inviter-1' });
111
+ });
112
+ it('is configured by the INVITE_TOKEN_TTL_DAYS knob', function () {
113
+ expect(Signup_1.INVITE_TOKEN_TTL_DAYS).toBe(EXPECTED_TTL_DAYS);
114
+ });
115
+ it('stamps tokenExpiresAt from the knob when creating an invite', function () { return __awaiter(void 0, void 0, void 0, function () {
116
+ var response, _a, inserted;
117
+ return __generator(this, function (_b) {
118
+ switch (_b.label) {
119
+ case 0:
120
+ dbGet.mockResolvedValue(undefined); // no existing user, no existing invite
121
+ return [4 /*yield*/, new Signup_1.Signup().sendInvite('Invitee@Test.local')];
122
+ case 1:
123
+ response = _b.sent();
124
+ expect(response.sent).toBe(true);
125
+ expect(dbInsert).toHaveBeenCalledTimes(1);
126
+ _a = dbInsert.mock.calls[0], inserted = _a[1];
127
+ expect(inserted.email).toBe('invitee@test.local');
128
+ expect(inserted.invitedBy).toBe('inviter-1');
129
+ expect(secondsFromExpectedTtl(inserted.tokenExpiresAt, Signup_1.INVITE_TOKEN_TTL_DAYS)).toBeLessThan(CLOCK_SKEW_TOLERANCE_SECONDS);
130
+ // Pin the value too, so changing the constant is a deliberate act rather than a drive-by.
131
+ expect(secondsFromExpectedTtl(inserted.tokenExpiresAt, EXPECTED_TTL_DAYS)).toBeLessThan(CLOCK_SKEW_TOLERANCE_SECONDS);
132
+ return [2 /*return*/];
133
+ }
134
+ });
135
+ }); });
136
+ it('stamps tokenExpiresAt from the knob when refreshing an existing invite', function () { return __awaiter(void 0, void 0, void 0, function () {
137
+ var _a, updated;
138
+ return __generator(this, function (_b) {
139
+ switch (_b.label) {
140
+ case 0:
141
+ dbGet
142
+ .mockResolvedValueOnce(undefined) // user lookup
143
+ .mockResolvedValueOnce({ id: 'invite-1', email: 'invitee@test.local', token: 'old-token' });
144
+ return [4 /*yield*/, new Signup_1.Signup().sendInvite('invitee@test.local')];
145
+ case 1:
146
+ _b.sent();
147
+ expect(dbUpdate).toHaveBeenCalledTimes(1);
148
+ _a = dbUpdate.mock.calls[0], updated = _a[1];
149
+ expect(updated.token).not.toBe('old-token');
150
+ expect(secondsFromExpectedTtl(updated.tokenExpiresAt, Signup_1.INVITE_TOKEN_TTL_DAYS)).toBeLessThan(CLOCK_SKEW_TOLERANCE_SECONDS);
151
+ return [2 /*return*/];
152
+ }
153
+ });
154
+ }); });
155
+ });
156
+ describe('expired invite user experience', function () {
157
+ beforeEach(function () {
158
+ jest.clearAllMocks();
159
+ });
160
+ it('tells the user the invite expired, and what to do about it', function () { return __awaiter(void 0, void 0, void 0, function () {
161
+ var response;
162
+ return __generator(this, function (_a) {
163
+ switch (_a.label) {
164
+ case 0:
165
+ dbGet.mockResolvedValue({
166
+ id: 'invite-1',
167
+ email: 'invitee@test.local',
168
+ token: 'expired-token',
169
+ tokenExpiresAt: (0, moment_1.default)().subtract(1, 'day'),
170
+ });
171
+ return [4 /*yield*/, new Signup_1.Signup().initializeSignup('expired-token')];
172
+ case 1:
173
+ response = _a.sent();
174
+ expect(response.isReady).toBe(false);
175
+ expect(response.error).toMatch(/expired/i);
176
+ // The actionable half — an honest error still has to tell the user their way out.
177
+ expect(response.error).toMatch(/new one/i);
178
+ // It must not read as a generic failure or leave signup half-rendered.
179
+ expect(response.error).not.toMatch(/not found/i);
180
+ expect(response.invite).toBeUndefined();
181
+ return [2 /*return*/];
182
+ }
183
+ });
184
+ }); });
185
+ it('reports an unknown or revoked token distinctly from an expired one', function () { return __awaiter(void 0, void 0, void 0, function () {
186
+ var response;
187
+ return __generator(this, function (_a) {
188
+ switch (_a.label) {
189
+ case 0:
190
+ dbGet.mockResolvedValue(undefined);
191
+ return [4 /*yield*/, new Signup_1.Signup().initializeSignup('unknown-token')];
192
+ case 1:
193
+ response = _a.sent();
194
+ expect(response.isReady).toBe(false);
195
+ expect(response.error).toMatch(/no longer valid/i);
196
+ expect(response.error).not.toMatch(/expired/i);
197
+ return [2 /*return*/];
198
+ }
199
+ });
200
+ }); });
201
+ it('accepts a token that has not expired yet', function () { return __awaiter(void 0, void 0, void 0, function () {
202
+ var invite, response;
203
+ var _a;
204
+ return __generator(this, function (_b) {
205
+ switch (_b.label) {
206
+ case 0:
207
+ invite = {
208
+ id: 'invite-1',
209
+ email: 'invitee@test.local',
210
+ token: 'good-token',
211
+ tokenExpiresAt: (0, moment_1.default)().add(1, 'day'),
212
+ };
213
+ dbGet.mockResolvedValue(invite);
214
+ return [4 /*yield*/, new Signup_1.Signup().initializeSignup('good-token')];
215
+ case 1:
216
+ response = _b.sent();
217
+ expect(response.isReady).toBe(true);
218
+ expect(response.error).toBeUndefined();
219
+ expect((_a = response.invite) === null || _a === void 0 ? void 0 : _a.email).toBe('invitee@test.local');
220
+ return [2 /*return*/];
221
+ }
222
+ });
223
+ }); });
224
+ it('still blocks tokenless signup when invite-only, without an expiry message', function () { return __awaiter(void 0, void 0, void 0, function () {
225
+ var response;
226
+ return __generator(this, function (_a) {
227
+ switch (_a.label) {
228
+ case 0: return [4 /*yield*/, new Signup_1.Signup().initializeSignup(undefined)];
229
+ case 1:
230
+ response = _a.sent();
231
+ // Default config is invite-optional, so this is the open case.
232
+ expect(response.isReady).toBe(true);
233
+ expect(dbGet).not.toHaveBeenCalled();
234
+ return [2 /*return*/];
235
+ }
236
+ });
237
+ }); });
238
+ });
239
+ //# sourceMappingURL=SignupInvite.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignupInvite.test.js","sourceRoot":"","sources":["../../test/SignupInvite.test.ts"],"names":[],"mappings":"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"}
@@ -11,10 +11,11 @@ import 'crypto-js';
11
11
  import 'express-session';
12
12
  import 'moment';
13
13
 
14
+
14
15
  /** Generate Source Graph */
15
16
 
16
- const sourceGraph =
17
- '{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/user-server/DbSessionStore","value":{"packageName":"@proteinjs/user-server","name":"DbSessionStore","filePath":"/home/runner/work/user/user/packages/server/src/authentication/DbSessionStore.ts","qualifiedName":"@proteinjs/user-server/DbSessionStore","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"TWELVE_HOURS","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"TOUCH_INTERVAL","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"lastTouchMs","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"get","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"set","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"touch","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"destroy","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"isTransactionAbort","returnType":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private","parameters":[{"name":"error","type":{"packageName":"","name":"any","filePath":null,"qualifiedName":"/any","typeParameters":null,"directParents":null}}]},{"name":"insertOrUpdate","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"sessionId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"session","type":{"packageName":"","name":"Express.SessionData","filePath":null,"qualifiedName":"/Express.SessionData","typeParameters":null,"directParents":null}},{"name":"cb","type":{"packageName":"","name":"(error?: any) => void","filePath":null,"qualifiedName":"/(error?: any) => void","typeParameters":null,"directParents":null}}]},{"name":"sweep","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]}],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"express-session","name":"Store","filePath":null,"qualifiedName":"express-session/Store","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"express-session/Store"},{"v":"@proteinjs/user-server/SocketIOSessionWatcher","value":{"packageName":"@proteinjs/user-server","name":"SocketIOSessionWatcher","filePath":"/home/runner/work/user/user/packages/server/src/authentication/SocketIOSessionWatcher.ts","qualifiedName":"@proteinjs/user-server/SocketIOSessionWatcher","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"name","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"table","returnType":{"packageName":"","name":"Table<Session>","filePath":null,"qualifiedName":"/Table<Session>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"afterDelete","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"recordDeleteCount","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}},{"name":"deletedRecords","type":{"packageName":"","name":"T[]","filePath":null,"qualifiedName":"/T[]","typeParameters":null,"directParents":null}},{"name":"initialQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null}},{"name":"deleteQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"TableWatcher","filePath":null,"qualifiedName":"@proteinjs/db/TableWatcher","properties":[],"methods":[],"typeParameters":["@proteinjs/user/Session"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/TableWatcher"},{"v":"@proteinjs/user-server/userCache","value":{"packageName":"@proteinjs/user-server","name":"userCache","filePath":"/home/runner/work/user/user/packages/server/src/authorization/userCache.ts","qualifiedName":"@proteinjs/user-server/userCache","type":{"packageName":"","name":"SessionDataCache<User>","filePath":null,"qualifiedName":"/SessionDataCache<User>","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"SessionDataCache","filePath":null,"qualifiedName":"@proteinjs/server-api/SessionDataCache","typeParameters":["@proteinjs/user/User"],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/server-api/SessionDataCache"},{"v":"@proteinjs/user-server/devLogin","value":{"packageName":"@proteinjs/user-server","name":"devLogin","filePath":"/home/runner/work/user/user/packages/server/src/routes/devLogin.ts","qualifiedName":"@proteinjs/user-server/devLogin","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/server-api/Route"},{"v":"@proteinjs/user-server/executePasswordReset","value":{"packageName":"@proteinjs/user-server","name":"executePasswordReset","filePath":"/home/runner/work/user/user/packages/server/src/routes/executePasswordReset.ts","qualifiedName":"@proteinjs/user-server/executePasswordReset","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/user-server/initiatePasswordReset","value":{"packageName":"@proteinjs/user-server","name":"initiatePasswordReset","filePath":"/home/runner/work/user/user/packages/server/src/routes/initiatePasswordReset.ts","qualifiedName":"@proteinjs/user-server/initiatePasswordReset","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/user-server/login","value":{"packageName":"@proteinjs/user-server","name":"login","filePath":"/home/runner/work/user/user/packages/server/src/routes/login.ts","qualifiedName":"@proteinjs/user-server/login","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/user-server/logout","value":{"packageName":"@proteinjs/user-server","name":"logout","filePath":"/home/runner/work/user/user/packages/server/src/routes/logout.ts","qualifiedName":"@proteinjs/user-server/logout","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/user-server/validateResetPasswordToken","value":{"packageName":"@proteinjs/user-server","name":"validateResetPasswordToken","filePath":"/home/runner/work/user/user/packages/server/src/routes/validateResetPasswordToken.ts","qualifiedName":"@proteinjs/user-server/validateResetPasswordToken","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/user-server/AccessInvite","value":{"packageName":"@proteinjs/user-server","name":"AccessInvite","filePath":"/home/runner/work/user/user/packages/server/src/services/AccessInvite.ts","qualifiedName":"@proteinjs/user-server/AccessInvite","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"serviceMetadata","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"createAccessInvite","returnType":{"packageName":"","name":"Promise<CreateAccessInviteResponse>","filePath":null,"qualifiedName":"/Promise<CreateAccessInviteResponse>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"params","type":{"packageName":"@proteinjs/user","name":"CreateAccessInviteParams","filePath":null,"qualifiedName":"@proteinjs/user/CreateAccessInviteParams","typeParameters":null,"directParents":null}}]},{"name":"acceptAccessInvite","returnType":{"packageName":"","name":"Promise<T | undefined>","filePath":null,"qualifiedName":"/Promise<T | undefined>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"token","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":["/T extends Record"],"directParentInterfaces":[{"packageName":"@proteinjs/user","name":"AccessInviteService","filePath":null,"qualifiedName":"@proteinjs/user/AccessInviteService","properties":[],"methods":[],"typeParameters":["/T"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/user/AccessInviteService"},{"v":"@proteinjs/user-server/DefaultInviteConfigFactory","value":{"packageName":"@proteinjs/user-server","name":"DefaultInviteConfigFactory","filePath":"/home/runner/work/user/user/packages/server/src/services/Signup.ts","qualifiedName":"@proteinjs/user-server/DefaultInviteConfigFactory","properties":[],"methods":[{"name":"getConfig","returnType":{"packageName":"@proteinjs/user-server","name":"InviteConfig","filePath":null,"qualifiedName":"@proteinjs/user-server/InviteConfig","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/reflection","name":"Loadable","filePath":null,"qualifiedName":"@proteinjs/reflection/Loadable","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/reflection/Loadable"},{"v":"@proteinjs/user-server/Signup","value":{"packageName":"@proteinjs/user-server","name":"Signup","filePath":"/home/runner/work/user/user/packages/server/src/services/Signup.ts","qualifiedName":"@proteinjs/user-server/Signup","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"serviceMetadata","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"createUser","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"user","type":{"packageName":"@proteinjs/user","name":"UserSignup","filePath":null,"qualifiedName":"@proteinjs/user/UserSignup","typeParameters":null,"directParents":null}},{"name":"token","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"sendInvite","returnType":{"packageName":"","name":"Promise<SendInviteResponse>","filePath":null,"qualifiedName":"/Promise<SendInviteResponse>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"email","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"revokeInvite","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"email","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"initializeSignup","returnType":{"packageName":"","name":"Promise<InitializeSignupResponse>","filePath":null,"qualifiedName":"/Promise<InitializeSignupResponse>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"inviteToken","type":{"packageName":"","name":"string | undefined","filePath":null,"qualifiedName":"/string | undefined","typeParameters":null,"directParents":null}}]},{"name":"getValidInvite","returnType":{"packageName":"","name":"Promise<Invite | null>","filePath":null,"qualifiedName":"/Promise<Invite | null>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"token","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/user","name":"SignupService","filePath":null,"qualifiedName":"@proteinjs/user/SignupService","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/user/SignupService"},{"v":"@proteinjs/user-server/UpdateUserInfo","value":{"packageName":"@proteinjs/user-server","name":"UpdateUserInfo","filePath":"/home/runner/work/user/user/packages/server/src/services/UpdateUserInfo.ts","qualifiedName":"@proteinjs/user-server/UpdateUserInfo","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"serviceMetadata","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"updateName","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"name","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updatePassword","returnType":{"packageName":"","name":"Promise<UpdatePasswordResponse>","filePath":null,"qualifiedName":"/Promise<UpdatePasswordResponse>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"currentPassword","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"newPassword","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/user","name":"UpdateUserInfoService","filePath":null,"qualifiedName":"@proteinjs/user/UpdateUserInfoService","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/user/UpdateUserInfoService"}],"edges":[{"v":"@proteinjs/user-server/DbSessionStore","w":"express-session/Store","value":"extends class"},{"v":"@proteinjs/user-server/SocketIOSessionWatcher","w":"@proteinjs/db/TableWatcher","value":"implements interface"},{"v":"@proteinjs/user-server/userCache","w":"@proteinjs/server-api/SessionDataCache","value":"has type"},{"v":"@proteinjs/user-server/devLogin","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/user-server/executePasswordReset","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/user-server/initiatePasswordReset","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/user-server/login","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/user-server/logout","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/user-server/validateResetPasswordToken","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/user-server/AccessInvite","w":"@proteinjs/user/AccessInviteService","value":"implements interface"},{"v":"@proteinjs/user-server/DefaultInviteConfigFactory","w":"@proteinjs/reflection/Loadable","value":"extends interface"},{"v":"@proteinjs/user-server/Signup","w":"@proteinjs/user/SignupService","value":"implements interface"},{"v":"@proteinjs/user-server/UpdateUserInfo","w":"@proteinjs/user/UpdateUserInfoService","value":"implements interface"}]}';
17
+ const sourceGraph = "{\"options\":{\"directed\":true,\"multigraph\":false,\"compound\":false},\"nodes\":[{\"v\":\"@proteinjs/user-server/DbSessionStore\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"DbSessionStore\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/authentication/DbSessionStore.ts\",\"qualifiedName\":\"@proteinjs/user-server/DbSessionStore\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"TWELVE_HOURS\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"TOUCH_INTERVAL\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"logger\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"lastTouchMs\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"get\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"set\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"touch\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"destroy\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"isTransactionAbort\",\"returnType\":{\"packageName\":\"\",\"name\":\"boolean\",\"filePath\":null,\"qualifiedName\":\"/boolean\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\",\"parameters\":[{\"name\":\"error\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"insertOrUpdate\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"sessionId\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"session\",\"type\":{\"packageName\":\"\",\"name\":\"Express.SessionData\",\"filePath\":null,\"qualifiedName\":\"/Express.SessionData\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"cb\",\"type\":{\"packageName\":\"\",\"name\":\"(error?: any) => void\",\"filePath\":null,\"qualifiedName\":\"/(error?: any) => void\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"sweep\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[]}],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[{\"packageName\":\"express-session\",\"name\":\"Store\",\"filePath\":null,\"qualifiedName\":\"express-session/Store\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[]}],\"sourceType\":2}},{\"v\":\"express-session/Store\"},{\"v\":\"@proteinjs/user-server/SocketIOSessionWatcher\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"SocketIOSessionWatcher\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/authentication/SocketIOSessionWatcher.ts\",\"qualifiedName\":\"@proteinjs/user-server/SocketIOSessionWatcher\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[{\"name\":\"name\",\"returnType\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]},{\"name\":\"table\",\"returnType\":{\"packageName\":\"\",\"name\":\"Table<Session>\",\"filePath\":null,\"qualifiedName\":\"/Table<Session>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]},{\"name\":\"afterDelete\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"recordDeleteCount\",\"type\":{\"packageName\":\"\",\"name\":\"number\",\"filePath\":null,\"qualifiedName\":\"/number\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"deletedRecords\",\"type\":{\"packageName\":\"\",\"name\":\"T[]\",\"filePath\":null,\"qualifiedName\":\"/T[]\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"initialQb\",\"type\":{\"packageName\":\"\",\"name\":\"QueryBuilder<T>\",\"filePath\":null,\"qualifiedName\":\"/QueryBuilder<T>\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"deleteQb\",\"type\":{\"packageName\":\"\",\"name\":\"QueryBuilder<T>\",\"filePath\":null,\"qualifiedName\":\"/QueryBuilder<T>\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"TableWatcher\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/TableWatcher\",\"properties\":[],\"methods\":[],\"typeParameters\":[\"@proteinjs/user/Session\"],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db/TableWatcher\"},{\"v\":\"@proteinjs/user-server/userCache\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"userCache\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/authorization/userCache.ts\",\"qualifiedName\":\"@proteinjs/user-server/userCache\",\"type\":{\"packageName\":\"\",\"name\":\"SessionDataCache<User>\",\"filePath\":null,\"qualifiedName\":\"/SessionDataCache<User>\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"SessionDataCache\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/SessionDataCache\",\"typeParameters\":[\"@proteinjs/user/User\"],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/server-api/SessionDataCache\"},{\"v\":\"@proteinjs/user-server/devLogin\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"devLogin\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/routes/devLogin.ts\",\"qualifiedName\":\"@proteinjs/user-server/devLogin\",\"type\":{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/server-api/Route\"},{\"v\":\"@proteinjs/user-server/executePasswordReset\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"executePasswordReset\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/routes/executePasswordReset.ts\",\"qualifiedName\":\"@proteinjs/user-server/executePasswordReset\",\"type\":{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/user-server/initiatePasswordReset\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"initiatePasswordReset\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/routes/initiatePasswordReset.ts\",\"qualifiedName\":\"@proteinjs/user-server/initiatePasswordReset\",\"type\":{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/user-server/login\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"login\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/routes/login.ts\",\"qualifiedName\":\"@proteinjs/user-server/login\",\"type\":{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/user-server/logout\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"logout\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/routes/logout.ts\",\"qualifiedName\":\"@proteinjs/user-server/logout\",\"type\":{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/user-server/validateResetPasswordToken\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"validateResetPasswordToken\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/routes/validateResetPasswordToken.ts\",\"qualifiedName\":\"@proteinjs/user-server/validateResetPasswordToken\",\"type\":{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/server-api\",\"name\":\"Route\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/server-api/Route\",\"typeParameters\":[],\"directParents\":null}]},\"isExported\":true,\"isConst\":true,\"sourceType\":0}},{\"v\":\"@proteinjs/user-server/AccessInvite\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"AccessInvite\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/services/AccessInvite.ts\",\"qualifiedName\":\"@proteinjs/user-server/AccessInvite\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"serviceMetadata\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"createAccessInvite\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<CreateAccessInviteResponse>\",\"filePath\":null,\"qualifiedName\":\"/Promise<CreateAccessInviteResponse>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"params\",\"type\":{\"packageName\":\"@proteinjs/user\",\"name\":\"CreateAccessInviteParams\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/CreateAccessInviteParams\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"acceptAccessInvite\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<T | undefined>\",\"filePath\":null,\"qualifiedName\":\"/Promise<T | undefined>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"token\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[\"/T extends Record\"],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/user\",\"name\":\"AccessInviteService\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/AccessInviteService\",\"properties\":[],\"methods\":[],\"typeParameters\":[\"/T\"],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/user/AccessInviteService\"},{\"v\":\"@proteinjs/user-server/DefaultInviteConfigFactory\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"DefaultInviteConfigFactory\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/services/Signup.ts\",\"qualifiedName\":\"@proteinjs/user-server/DefaultInviteConfigFactory\",\"properties\":[],\"methods\":[{\"name\":\"getConfig\",\"returnType\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"InviteConfig\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user-server/InviteConfig\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":true,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]}],\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/reflection\",\"name\":\"Loadable\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/reflection/Loadable\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"sourceType\":3}},{\"v\":\"@proteinjs/reflection/Loadable\"},{\"v\":\"@proteinjs/user-server/Signup\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"Signup\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/services/Signup.ts\",\"qualifiedName\":\"@proteinjs/user-server/Signup\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"serviceMetadata\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"createUser\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"user\",\"type\":{\"packageName\":\"@proteinjs/user\",\"name\":\"UserSignup\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/UserSignup\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"token\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"sendInvite\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<SendInviteResponse>\",\"filePath\":null,\"qualifiedName\":\"/Promise<SendInviteResponse>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"email\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"revokeInvite\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"email\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"initializeSignup\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<InitializeSignupResponse>\",\"filePath\":null,\"qualifiedName\":\"/Promise<InitializeSignupResponse>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"inviteToken\",\"type\":{\"packageName\":\"\",\"name\":\"string | undefined\",\"filePath\":null,\"qualifiedName\":\"/string | undefined\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"lookupInvite\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<InviteLookup>\",\"filePath\":null,\"qualifiedName\":\"/Promise<InviteLookup>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"token\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/user\",\"name\":\"SignupService\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/SignupService\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/user/SignupService\"},{\"v\":\"@proteinjs/user-server/UpdateUserInfo\",\"value\":{\"packageName\":\"@proteinjs/user-server\",\"name\":\"UpdateUserInfo\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa4/packages/proteinjs/packages/user/packages/server/src/services/UpdateUserInfo.ts\",\"qualifiedName\":\"@proteinjs/user-server/UpdateUserInfo\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"serviceMetadata\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"updateName\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"updatePassword\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<UpdatePasswordResponse>\",\"filePath\":null,\"qualifiedName\":\"/Promise<UpdatePasswordResponse>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"currentPassword\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"newPassword\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/user\",\"name\":\"UpdateUserInfoService\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/UpdateUserInfoService\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/user/UpdateUserInfoService\"}],\"edges\":[{\"v\":\"@proteinjs/user-server/DbSessionStore\",\"w\":\"express-session/Store\",\"value\":\"extends class\"},{\"v\":\"@proteinjs/user-server/SocketIOSessionWatcher\",\"w\":\"@proteinjs/db/TableWatcher\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/user-server/userCache\",\"w\":\"@proteinjs/server-api/SessionDataCache\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/devLogin\",\"w\":\"@proteinjs/server-api/Route\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/executePasswordReset\",\"w\":\"@proteinjs/server-api/Route\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/initiatePasswordReset\",\"w\":\"@proteinjs/server-api/Route\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/login\",\"w\":\"@proteinjs/server-api/Route\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/logout\",\"w\":\"@proteinjs/server-api/Route\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/validateResetPasswordToken\",\"w\":\"@proteinjs/server-api/Route\",\"value\":\"has type\"},{\"v\":\"@proteinjs/user-server/AccessInvite\",\"w\":\"@proteinjs/user/AccessInviteService\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/user-server/DefaultInviteConfigFactory\",\"w\":\"@proteinjs/reflection/Loadable\",\"value\":\"extends interface\"},{\"v\":\"@proteinjs/user-server/Signup\",\"w\":\"@proteinjs/user/SignupService\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/user-server/UpdateUserInfo\",\"w\":\"@proteinjs/user/UpdateUserInfoService\",\"value\":\"implements interface\"}]}";
18
+
18
19
 
19
20
  /** Generate Source Links */
20
21
 
@@ -32,23 +33,25 @@ import { Signup } from '../src/services/Signup';
32
33
  import { UpdateUserInfo } from '../src/services/UpdateUserInfo';
33
34
 
34
35
  const sourceLinks = {
35
- '@proteinjs/user-server/DbSessionStore': DbSessionStore,
36
- '@proteinjs/user-server/SocketIOSessionWatcher': SocketIOSessionWatcher,
37
- '@proteinjs/user-server/userCache': userCache,
38
- '@proteinjs/user-server/devLogin': devLogin,
39
- '@proteinjs/user-server/executePasswordReset': executePasswordReset,
40
- '@proteinjs/user-server/initiatePasswordReset': initiatePasswordReset,
41
- '@proteinjs/user-server/login': login,
42
- '@proteinjs/user-server/logout': logout,
43
- '@proteinjs/user-server/validateResetPasswordToken': validateResetPasswordToken,
44
- '@proteinjs/user-server/AccessInvite': AccessInvite,
45
- '@proteinjs/user-server/Signup': Signup,
46
- '@proteinjs/user-server/UpdateUserInfo': UpdateUserInfo,
36
+ '@proteinjs/user-server/DbSessionStore': DbSessionStore,
37
+ '@proteinjs/user-server/SocketIOSessionWatcher': SocketIOSessionWatcher,
38
+ '@proteinjs/user-server/userCache': userCache,
39
+ '@proteinjs/user-server/devLogin': devLogin,
40
+ '@proteinjs/user-server/executePasswordReset': executePasswordReset,
41
+ '@proteinjs/user-server/initiatePasswordReset': initiatePasswordReset,
42
+ '@proteinjs/user-server/login': login,
43
+ '@proteinjs/user-server/logout': logout,
44
+ '@proteinjs/user-server/validateResetPasswordToken': validateResetPasswordToken,
45
+ '@proteinjs/user-server/AccessInvite': AccessInvite,
46
+ '@proteinjs/user-server/Signup': Signup,
47
+ '@proteinjs/user-server/UpdateUserInfo': UpdateUserInfo,
47
48
  };
48
49
 
50
+
49
51
  /** Load Source Graph and Links */
50
52
 
51
53
  import { SourceRepository } from '@proteinjs/reflection';
52
54
  SourceRepository.merge(sourceGraph, sourceLinks);
53
55
 
54
- export * from '../index';
56
+
57
+ export * from '../index';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/user-server",
3
- "version": "1.4.5",
3
+ "version": "1.5.1",
4
4
  "description": "User server components",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,13 +22,13 @@
22
22
  "test": "jest --passWithNoTests"
23
23
  },
24
24
  "dependencies": {
25
- "@proteinjs/db": "^1.25.2",
25
+ "@proteinjs/db": "^1.27.0",
26
26
  "@proteinjs/email-server": "^1.2.18",
27
27
  "@proteinjs/logger": "^1.0.21",
28
28
  "@proteinjs/reflection": "^1.1.12",
29
- "@proteinjs/server": "^3.1.12",
29
+ "@proteinjs/server": "^3.1.14",
30
30
  "@proteinjs/server-api": "^3.0.10",
31
- "@proteinjs/user": "^1.5.14",
31
+ "@proteinjs/user": "^1.6.1",
32
32
  "crypto-js": "4.0.0",
33
33
  "express-session": "1.17.1",
34
34
  "moment": "2.29.4"
@@ -49,6 +49,5 @@
49
49
  "typescript": "5.2.2"
50
50
  },
51
51
  "main": "./dist/generated/index.js",
52
- "types": "./dist/generated/index.d.ts",
53
- "gitHead": "186c444e79c7d3b40274f9fd1e6d7e080a3b20d4"
52
+ "types": "./dist/generated/index.d.ts"
54
53
  }
@@ -21,10 +21,23 @@ import {
21
21
  import sha256 from 'crypto-js/sha256';
22
22
  import { Loadable, SourceRepository } from '@proteinjs/reflection';
23
23
 
24
+ /**
25
+ * How long an invite link stays usable. Deliberately generous: a legitimate invite that gets
26
+ * clicked late should still work, and revocation (surfaced in the admin UI) is the real control.
27
+ * Expiry stays non-infinite so a forgotten invite isn't a permanent credential sitting in a mailbox.
28
+ */
29
+ export const INVITE_TOKEN_TTL_DAYS = 90;
30
+
24
31
  export interface InviteConfig {
25
32
  isInviteOnly: boolean;
26
33
  }
27
34
 
35
+ /**
36
+ * Result of resolving an invite token. `expired` is kept distinct from `notFound` so signup can
37
+ * tell the user which one happened instead of a conflated "not found or expired".
38
+ */
39
+ export type InviteLookup = { status: 'valid'; invite: Invite } | { status: 'expired' } | { status: 'notFound' };
40
+
28
41
  export interface DefaultInviteConfigFactory extends Loadable {
29
42
  getConfig(): InviteConfig;
30
43
  }
@@ -65,7 +78,9 @@ export class Signup implements SignupService {
65
78
  throw new Error(initSignupResponse.error);
66
79
  }
67
80
 
68
- const invite = token ? await this.getValidInvite(token) : null;
81
+ // `initializeSignup` is the single place invite tokens are validated; reuse its result rather
82
+ // than re-resolving the token here (two lookups meant two chances to disagree).
83
+ const invite = initSignupResponse.invite ?? null;
69
84
  if (token) {
70
85
  await db.delete(tables.Invite, { token });
71
86
  }
@@ -135,7 +150,7 @@ export class Signup implements SignupService {
135
150
  const config = defaultConfigFactory.getConfig();
136
151
 
137
152
  const token = lib.WordArray.random(32).toString();
138
- const tokenExpiresAt = moment().add(7, 'days');
153
+ const tokenExpiresAt = moment().add(INVITE_TOKEN_TTL_DAYS, 'days');
139
154
  let invite = await db.get(tables.Invite, { email: caseInsensitiveEmail });
140
155
  if (invite) {
141
156
  invite = {
@@ -190,29 +205,45 @@ export class Signup implements SignupService {
190
205
  try {
191
206
  const config = getDefaultInviteConfigFactory().getConfig();
192
207
  const { isInviteOnly } = config;
193
- const invite = inviteToken ? await this.getValidInvite(inviteToken) : undefined;
194
208
 
195
- if (isInviteOnly) {
196
- if (!inviteToken) {
209
+ // A bad token is reported whether or not signup is invite-only. Silently ignoring it left the
210
+ // form rendered with the email field hidden (the UI hides it whenever a token is present),
211
+ // so the user could only ever reach a generic "Sign up failed." on submit.
212
+ if (inviteToken) {
213
+ const lookup = await this.lookupInvite(inviteToken);
214
+ if (lookup.status === 'expired') {
197
215
  return {
198
216
  isReady: false,
199
- error: 'An invite is required to sign up.',
217
+ error: 'This invite has expired. Ask whoever invited you to send a new one.',
200
218
  isInviteOnly,
201
219
  };
202
220
  }
203
- if (!invite) {
221
+ if (lookup.status === 'notFound') {
204
222
  return {
205
223
  isReady: false,
206
- error: 'The provided invite was not found or has expired.',
224
+ error: 'This invite link is no longer valid. Ask whoever invited you to send a new one.',
207
225
  isInviteOnly,
208
226
  };
209
227
  }
228
+
229
+ return {
230
+ isReady: true,
231
+ isInviteOnly,
232
+ invite: lookup.invite,
233
+ };
234
+ }
235
+
236
+ if (isInviteOnly) {
237
+ return {
238
+ isReady: false,
239
+ error: 'An invite is required to sign up.',
240
+ isInviteOnly,
241
+ };
210
242
  }
211
243
 
212
244
  return {
213
245
  isReady: true,
214
246
  isInviteOnly,
215
- invite: invite ? invite : undefined,
216
247
  };
217
248
  } catch (error: any) {
218
249
  return {
@@ -222,20 +253,19 @@ export class Signup implements SignupService {
222
253
  }
223
254
  }
224
255
 
225
- /** Returns a valid invite if it exists and is not expired, returns `null` otherwise. */
226
- private async getValidInvite(token: string): Promise<Invite | null> {
256
+ /** Resolves an invite token, distinguishing "expired" from "never existed / already revoked". */
257
+ private async lookupInvite(token: string): Promise<InviteLookup> {
227
258
  const db = getDbAsSystem();
228
259
  const invite = await db.get(tables.Invite, { token });
229
260
 
230
261
  if (!invite) {
231
- return null;
262
+ return { status: 'notFound' };
232
263
  }
233
264
 
234
- const currentTime = moment();
235
- if (invite.tokenExpiresAt && moment(invite.tokenExpiresAt).isBefore(currentTime)) {
236
- return null;
265
+ if (invite.tokenExpiresAt && moment(invite.tokenExpiresAt).isBefore(moment())) {
266
+ return { status: 'expired' };
237
267
  }
238
268
 
239
- return invite;
269
+ return { status: 'valid', invite };
240
270
  }
241
271
  }