@proteinjs/user-server 1.22.1 → 1.22.3
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 +28 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/src/authentication/PasswordHasher.d.ts +9 -0
- package/dist/src/authentication/PasswordHasher.d.ts.map +1 -1
- package/dist/src/authentication/PasswordHasher.js +25 -0
- package/dist/src/authentication/PasswordHasher.js.map +1 -1
- package/dist/src/authentication/SocketIOSessionWatcher.d.ts +5 -0
- package/dist/src/authentication/SocketIOSessionWatcher.d.ts.map +1 -1
- package/dist/src/authentication/SocketIOSessionWatcher.js +13 -2
- package/dist/src/authentication/SocketIOSessionWatcher.js.map +1 -1
- package/dist/src/authentication/UserStatusTableWatcher.d.ts.map +1 -1
- package/dist/src/authentication/UserStatusTableWatcher.js +2 -1
- package/dist/src/authentication/UserStatusTableWatcher.js.map +1 -1
- package/dist/src/authentication/authenticate.d.ts +15 -0
- package/dist/src/authentication/authenticate.d.ts.map +1 -1
- package/dist/src/authentication/authenticate.js +55 -20
- package/dist/src/authentication/authenticate.js.map +1 -1
- package/dist/src/authorization/userCache.d.ts.map +1 -1
- package/dist/src/authorization/userCache.js +3 -2
- package/dist/src/authorization/userCache.js.map +1 -1
- package/dist/src/routes/devLogin.d.ts.map +1 -1
- package/dist/src/routes/devLogin.js +29 -14
- package/dist/src/routes/devLogin.js.map +1 -1
- package/dist/src/routes/executePasswordReset.d.ts +4 -0
- package/dist/src/routes/executePasswordReset.d.ts.map +1 -1
- package/dist/src/routes/executePasswordReset.js +12 -4
- package/dist/src/routes/executePasswordReset.js.map +1 -1
- package/dist/src/routes/initiatePasswordReset.d.ts +7 -0
- package/dist/src/routes/initiatePasswordReset.d.ts.map +1 -1
- package/dist/src/routes/initiatePasswordReset.js +28 -10
- package/dist/src/routes/initiatePasswordReset.js.map +1 -1
- package/dist/src/routes/login.d.ts +8 -0
- package/dist/src/routes/login.d.ts.map +1 -1
- package/dist/src/routes/login.js +63 -22
- package/dist/src/routes/login.js.map +1 -1
- package/dist/src/routes/signup.d.ts.map +1 -1
- package/dist/src/routes/signup.js +5 -2
- package/dist/src/routes/signup.js.map +1 -1
- package/dist/src/routes/validateResetPasswordToken.d.ts.map +1 -1
- package/dist/src/routes/validateResetPasswordToken.js +5 -1
- package/dist/src/routes/validateResetPasswordToken.js.map +1 -1
- package/dist/src/services/MachineCredentials.d.ts +10 -1
- package/dist/src/services/MachineCredentials.d.ts.map +1 -1
- package/dist/src/services/MachineCredentials.js +46 -15
- package/dist/src/services/MachineCredentials.js.map +1 -1
- package/dist/src/services/Roles.d.ts.map +1 -1
- package/dist/src/services/Roles.js +6 -2
- package/dist/src/services/Roles.js.map +1 -1
- package/dist/src/services/Signup.d.ts +5 -0
- package/dist/src/services/Signup.d.ts.map +1 -1
- package/dist/src/services/Signup.js +28 -6
- package/dist/src/services/Signup.js.map +1 -1
- package/dist/src/throttle/ClientAddress.d.ts +35 -0
- package/dist/src/throttle/ClientAddress.d.ts.map +1 -0
- package/dist/src/throttle/ClientAddress.js +48 -0
- package/dist/src/throttle/ClientAddress.js.map +1 -0
- package/dist/src/throttle/PasswordResetThrottle.d.ts +39 -0
- package/dist/src/throttle/PasswordResetThrottle.d.ts.map +1 -0
- package/dist/src/throttle/PasswordResetThrottle.js +59 -0
- package/dist/src/throttle/PasswordResetThrottle.js.map +1 -0
- package/dist/src/throttle/RequestDigests.d.ts +42 -0
- package/dist/src/throttle/RequestDigests.d.ts.map +1 -0
- package/dist/src/throttle/RequestDigests.js +96 -0
- package/dist/src/throttle/RequestDigests.js.map +1 -0
- package/dist/src/throttle/SignInThrottle.d.ts +67 -0
- package/dist/src/throttle/SignInThrottle.d.ts.map +1 -0
- package/dist/src/throttle/SignInThrottle.js +91 -0
- package/dist/src/throttle/SignInThrottle.js.map +1 -0
- package/dist/src/throttle/SlidingWindow.d.ts +51 -0
- package/dist/src/throttle/SlidingWindow.d.ts.map +1 -0
- package/dist/src/throttle/SlidingWindow.js +85 -0
- package/dist/src/throttle/SlidingWindow.js.map +1 -0
- package/dist/test/ClientAddress.test.d.ts +2 -0
- package/dist/test/ClientAddress.test.d.ts.map +1 -0
- package/dist/test/ClientAddress.test.js +51 -0
- package/dist/test/ClientAddress.test.js.map +1 -0
- package/dist/test/DevLogin.test.js +68 -0
- package/dist/test/DevLogin.test.js.map +1 -1
- package/dist/test/InitiatePasswordReset.test.js +373 -5
- package/dist/test/InitiatePasswordReset.test.js.map +1 -1
- package/dist/test/LogCapture.d.ts +10 -0
- package/dist/test/LogCapture.d.ts.map +1 -1
- package/dist/test/LogCapture.js +19 -0
- package/dist/test/LogCapture.js.map +1 -1
- package/dist/test/LogCapture.test.d.ts +2 -0
- package/dist/test/LogCapture.test.d.ts.map +1 -0
- package/dist/test/LogCapture.test.js +86 -0
- package/dist/test/LogCapture.test.js.map +1 -0
- package/dist/test/LoginThrottle.integration.test.d.ts +2 -0
- package/dist/test/LoginThrottle.integration.test.d.ts.map +1 -0
- package/dist/test/LoginThrottle.integration.test.js +616 -0
- package/dist/test/LoginThrottle.integration.test.js.map +1 -0
- package/dist/test/MachineAccounts.integration.test.js +156 -28
- package/dist/test/MachineAccounts.integration.test.js.map +1 -1
- package/dist/test/NoAddressesInLogs.integration.test.d.ts +2 -0
- package/dist/test/NoAddressesInLogs.integration.test.d.ts.map +1 -0
- package/dist/test/NoAddressesInLogs.integration.test.js +717 -0
- package/dist/test/NoAddressesInLogs.integration.test.js.map +1 -0
- package/dist/test/RequestDigests.test.d.ts +2 -0
- package/dist/test/RequestDigests.test.d.ts.map +1 -0
- package/dist/test/RequestDigests.test.js +73 -0
- package/dist/test/RequestDigests.test.js.map +1 -0
- package/dist/test/SignupRoute.test.js +80 -1
- package/dist/test/SignupRoute.test.js.map +1 -1
- package/dist/test/SlidingWindow.test.d.ts +2 -0
- package/dist/test/SlidingWindow.test.d.ts.map +1 -0
- package/dist/test/SlidingWindow.test.js +72 -0
- package/dist/test/SlidingWindow.test.js.map +1 -0
- package/dist/test/SocketIOSessionWatcher.test.d.ts +2 -0
- package/dist/test/SocketIOSessionWatcher.test.d.ts.map +1 -0
- package/dist/test/SocketIOSessionWatcher.test.js +144 -0
- package/dist/test/SocketIOSessionWatcher.test.js.map +1 -0
- package/generated/index.ts +1 -1
- package/index.ts +5 -0
- package/package.json +6 -5
- package/src/authentication/PasswordHasher.ts +18 -1
- package/src/authentication/SocketIOSessionWatcher.ts +13 -1
- package/src/authentication/UserStatusTableWatcher.ts +2 -1
- package/src/authentication/authenticate.ts +36 -9
- package/src/authorization/userCache.ts +3 -2
- package/src/routes/devLogin.ts +22 -10
- package/src/routes/executePasswordReset.ts +11 -3
- package/src/routes/initiatePasswordReset.ts +29 -11
- package/src/routes/login.ts +39 -8
- package/src/routes/signup.ts +5 -2
- package/src/routes/validateResetPasswordToken.ts +5 -1
- package/src/services/MachineCredentials.ts +44 -9
- package/src/services/Roles.ts +6 -2
- package/src/services/Signup.ts +30 -4
- package/src/throttle/ClientAddress.ts +47 -0
- package/src/throttle/PasswordResetThrottle.ts +62 -0
- package/src/throttle/RequestDigests.ts +97 -0
- package/src/throttle/SignInThrottle.ts +98 -0
- package/src/throttle/SlidingWindow.ts +90 -0
- package/test/ClientAddress.test.ts +54 -0
- package/test/DevLogin.test.ts +35 -1
- package/test/InitiatePasswordReset.test.ts +212 -6
- package/test/LogCapture.test.ts +29 -0
- package/test/LogCapture.ts +16 -0
- package/test/LoginThrottle.integration.test.ts +339 -0
- package/test/MachineAccounts.integration.test.ts +97 -22
- package/test/NoAddressesInLogs.integration.test.ts +409 -0
- package/test/RequestDigests.test.ts +81 -0
- package/test/SignupRoute.test.ts +54 -1
- package/test/SlidingWindow.test.ts +85 -0
- package/test/SocketIOSessionWatcher.test.ts +86 -0
- package/test/setup.js +2 -0
package/test/SignupRoute.test.ts
CHANGED
|
@@ -35,7 +35,7 @@ jest.mock('@proteinjs/email-server', () => ({
|
|
|
35
35
|
|
|
36
36
|
import { getDbAsSystem, Reference } from '@proteinjs/db';
|
|
37
37
|
import { SourceRepository } from '@proteinjs/reflection';
|
|
38
|
-
import { tables } from '@proteinjs/user';
|
|
38
|
+
import { MachineAccount, tables } from '@proteinjs/user';
|
|
39
39
|
import { signup } from '../src/routes/signup';
|
|
40
40
|
import { createPassportRequest } from './passportSessionHarness';
|
|
41
41
|
import { UserServerTestEnvironment } from './UserServerTestEnvironment';
|
|
@@ -45,6 +45,31 @@ const testEnv = new UserServerTestEnvironment();
|
|
|
45
45
|
const INVITE_CONFIG_KEY = '@proteinjs/user-server/DefaultInviteConfigFactory';
|
|
46
46
|
|
|
47
47
|
type ObjectCache = { objectCache: Record<string, unknown[]> };
|
|
48
|
+
type NamedObjectCache = {
|
|
49
|
+
namedObjectCache: Record<string, { qualifiedName: string; packageName: string; object: unknown }[]>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** A code-declared machine account owning an address (the declaration only — never booted here). */
|
|
53
|
+
class DeclaredMachineAccount extends MachineAccount {
|
|
54
|
+
id = 'declared-machine';
|
|
55
|
+
email = 'declared.machine@test.local';
|
|
56
|
+
accountName = 'Declared machine';
|
|
57
|
+
roles = [];
|
|
58
|
+
secretName = 'declared-machine-secret';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const LOADER_KEY = '@proteinjs/db/SourceRecordLoader';
|
|
62
|
+
|
|
63
|
+
/** Seed the build's source-record declarations (the machine-account read resolves them by name). */
|
|
64
|
+
const setDeclarations = (declarations: unknown[]) => {
|
|
65
|
+
(SourceRepository.get() as unknown as NamedObjectCache).namedObjectCache[LOADER_KEY] = declarations.map(
|
|
66
|
+
(object, i) => ({
|
|
67
|
+
qualifiedName: `@proteinjs/user-server-test/Declared${i}`,
|
|
68
|
+
packageName: '@test/declarer',
|
|
69
|
+
object,
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
};
|
|
48
73
|
|
|
49
74
|
/** Seed the invite-config lookup (tests don't load the generated source graph). */
|
|
50
75
|
const setInviteConfig = (factories: unknown[]) => {
|
|
@@ -94,12 +119,14 @@ describe('signup route — auto-login after signup', () => {
|
|
|
94
119
|
}, 120000);
|
|
95
120
|
|
|
96
121
|
afterAll(async () => {
|
|
122
|
+
delete (SourceRepository.get() as unknown as NamedObjectCache).namedObjectCache[LOADER_KEY];
|
|
97
123
|
await testEnv.afterAll();
|
|
98
124
|
});
|
|
99
125
|
|
|
100
126
|
beforeEach(async () => {
|
|
101
127
|
jest.clearAllMocks();
|
|
102
128
|
setInviteConfig([]);
|
|
129
|
+
setDeclarations([]);
|
|
103
130
|
const db = getDbAsSystem();
|
|
104
131
|
await db.delete(tables.Invite, {});
|
|
105
132
|
await db.delete(tables.User, {});
|
|
@@ -180,6 +207,32 @@ describe('signup route — auto-login after signup', () => {
|
|
|
180
207
|
expect(sendEmail).toHaveBeenCalledTimes(1); // "account already exists" email
|
|
181
208
|
});
|
|
182
209
|
|
|
210
|
+
it(`an address a machine-account declaration owns can't be registered — by signup or by invite: plain words, no account, no session`, async () => {
|
|
211
|
+
setDeclarations([new DeclaredMachineAccount()]);
|
|
212
|
+
|
|
213
|
+
const direct = await invokeSignup({
|
|
214
|
+
name: 'Squatter',
|
|
215
|
+
email: 'Declared.Machine@test.local',
|
|
216
|
+
password: 'pw-squat-1',
|
|
217
|
+
});
|
|
218
|
+
expect(direct.body).toEqual({ error: "This address can't be registered." });
|
|
219
|
+
expect(direct.loggedInAs).toBeUndefined();
|
|
220
|
+
expect(direct.sessionEvents).toEqual([]);
|
|
221
|
+
|
|
222
|
+
await getDbAsSystem().insert(tables.Invite, {
|
|
223
|
+
email: 'declared.machine@test.local',
|
|
224
|
+
token: 'invite-token-declared',
|
|
225
|
+
tokenExpiresAt: moment().add(1, 'day'),
|
|
226
|
+
invitedBy: new Reference(tables.User.name, 'inviter-1'),
|
|
227
|
+
});
|
|
228
|
+
const invited = await invokeSignup({ name: 'Squatter', password: 'pw-squat-2', token: 'invite-token-declared' });
|
|
229
|
+
expect(invited.body).toEqual({ error: "This address can't be registered." });
|
|
230
|
+
expect(invited.loggedInAs).toBeUndefined();
|
|
231
|
+
|
|
232
|
+
expect(await getUserRow('declared.machine@test.local')).toBeUndefined();
|
|
233
|
+
expect(sendEmail).not.toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
|
|
183
236
|
it('an unknown invite token gets the honest error — no session, no account', async () => {
|
|
184
237
|
const outcome = await invokeSignup({
|
|
185
238
|
name: 'Token Guess',
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SlidingWindow } from '../src/throttle/SlidingWindow';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The per-key sliding window every throttled door shares (the sign-in and reset doors here, the
|
|
5
|
+
* invite-request door in the consuming app): `hit` records and answers whether the key is over,
|
|
6
|
+
* `isOver` only reads, `record` only counts, `clear` forgets. Attempts refused as over the window
|
|
7
|
+
* are not counted, so a key drains exactly one window after its last counted attempt.
|
|
8
|
+
*/
|
|
9
|
+
describe('SlidingWindow', () => {
|
|
10
|
+
const MINUTE = 60 * 1000;
|
|
11
|
+
let now = 1_700_000_000_000;
|
|
12
|
+
const clock = () => now;
|
|
13
|
+
|
|
14
|
+
it('admits up to the limit inside the window and refuses the next', () => {
|
|
15
|
+
const window = new SlidingWindow({ windowMs: 10 * MINUTE, limit: 3, now: clock });
|
|
16
|
+
|
|
17
|
+
expect([window.hit('a'), window.hit('a'), window.hit('a')]).toEqual([false, false, false]);
|
|
18
|
+
expect(window.hit('a')).toBe(true);
|
|
19
|
+
expect(window.isOver('a')).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('keeps each key in its own window', () => {
|
|
23
|
+
const window = new SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
|
|
24
|
+
window.hit('a');
|
|
25
|
+
window.hit('a');
|
|
26
|
+
|
|
27
|
+
expect(window.hit('a')).toBe(true);
|
|
28
|
+
expect(window.hit('b')).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('drains: an attempt older than the window no longer counts, and refused attempts never counted', () => {
|
|
32
|
+
const window = new SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
|
|
33
|
+
window.hit('a');
|
|
34
|
+
now += 4 * MINUTE;
|
|
35
|
+
window.hit('a');
|
|
36
|
+
now += 5 * MINUTE;
|
|
37
|
+
expect(window.hit('a')).toBe(true); // both counted attempts are still inside the window
|
|
38
|
+
|
|
39
|
+
now += 1 * MINUTE + 1; // the first counted attempt left the window; the refused one never counted
|
|
40
|
+
expect(window.hit('a')).toBe(false);
|
|
41
|
+
expect(window.hit('a')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('isOver reads without counting; record counts without answering; clear forgets the key', () => {
|
|
45
|
+
const window = new SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
|
|
46
|
+
|
|
47
|
+
expect(window.isOver('a')).toBe(false);
|
|
48
|
+
expect(window.isOver('a')).toBe(false);
|
|
49
|
+
window.record('a');
|
|
50
|
+
expect(window.isOver('a')).toBe(false);
|
|
51
|
+
window.record('a');
|
|
52
|
+
expect(window.isOver('a')).toBe(true);
|
|
53
|
+
|
|
54
|
+
window.clear('a');
|
|
55
|
+
expect(window.isOver('a')).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('forgive uncounts the latest attempt only', () => {
|
|
59
|
+
const window = new SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
|
|
60
|
+
window.hit('a');
|
|
61
|
+
window.hit('a');
|
|
62
|
+
expect(window.isOver('a')).toBe(true);
|
|
63
|
+
|
|
64
|
+
window.forgive('a');
|
|
65
|
+
|
|
66
|
+
expect(window.isOver('a')).toBe(false);
|
|
67
|
+
expect(window.hit('a')).toBe(false);
|
|
68
|
+
expect(window.hit('a')).toBe(true);
|
|
69
|
+
window.forgive('b'); // a key never seen: nothing to uncount, nothing thrown
|
|
70
|
+
expect(window.isOver('b')).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('bounds the keys it tracks: at the bound the oldest-touched key is forgotten first', () => {
|
|
74
|
+
const window = new SlidingWindow({ windowMs: 10 * MINUTE, limit: 1, maxKeys: 2, now: clock });
|
|
75
|
+
window.hit('a');
|
|
76
|
+
window.hit('b');
|
|
77
|
+
window.hit('a'); // refused, but 'a' is now the most recently touched
|
|
78
|
+
|
|
79
|
+
window.hit('c'); // at the bound: 'b' (oldest-touched) is forgotten
|
|
80
|
+
|
|
81
|
+
expect(window.isOver('a')).toBe(true);
|
|
82
|
+
expect(window.isOver('b')).toBe(false);
|
|
83
|
+
expect(window.isOver('c')).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { QueryBuilder } from '@proteinjs/db';
|
|
3
|
+
import { SourceRepository } from '@proteinjs/reflection';
|
|
4
|
+
import { SocketIOServerRepo } from '@proteinjs/server';
|
|
5
|
+
import { Session } from '@proteinjs/user';
|
|
6
|
+
import { SocketIOSessionWatcher } from '../src/authentication/SocketIOSessionWatcher';
|
|
7
|
+
|
|
8
|
+
type ObjectCache = { objectCache: Record<string, unknown[]> };
|
|
9
|
+
|
|
10
|
+
/** What the watcher asked the socket server to do: which rooms (session ids) it closed. */
|
|
11
|
+
type DisconnectCall = { rooms: string[]; close: boolean };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The socket server this file installs through the repo's own factory seam — a stand-in that
|
|
15
|
+
* records every `in(rooms).disconnectSockets(close)` it receives (no socket.io client is part of
|
|
16
|
+
* this package, so no real socket can connect to observe).
|
|
17
|
+
*/
|
|
18
|
+
class RecordingSocketIOServer {
|
|
19
|
+
readonly calls: DisconnectCall[] = [];
|
|
20
|
+
|
|
21
|
+
in(rooms: string | string[]) {
|
|
22
|
+
return {
|
|
23
|
+
disconnectSockets: (close: boolean) => {
|
|
24
|
+
this.calls.push({ rooms: ([] as string[]).concat(rooms), close });
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const session = (sessionId: string): Session =>
|
|
31
|
+
({ id: `row-${sessionId}`, sessionId, session: '{}', userEmail: 'someone@test.local' }) as unknown as Session;
|
|
32
|
+
|
|
33
|
+
const deleteSessions = async (sessions: Session[]) =>
|
|
34
|
+
await new SocketIOSessionWatcher().afterDelete(
|
|
35
|
+
sessions.length,
|
|
36
|
+
sessions,
|
|
37
|
+
new QueryBuilder<Session>('session'),
|
|
38
|
+
new QueryBuilder<Session>('session')
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deleting session rows closes their sockets — and a deletion is not always made by a running
|
|
43
|
+
* server: a boot's source-record sync (and a migration Job, which never starts one) deactivates a
|
|
44
|
+
* withdrawn machine account and deletes its sessions before any socket server exists.
|
|
45
|
+
*/
|
|
46
|
+
describe('SocketIOSessionWatcher', () => {
|
|
47
|
+
it('a deletion before the socket server exists resolves — no socket can be open yet', async () => {
|
|
48
|
+
expect(SocketIOServerRepo.getSocketIOServerIfExists()).toBeUndefined();
|
|
49
|
+
|
|
50
|
+
await expect(deleteSessions([session('before-boot')])).resolves.toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('with the socket server up', () => {
|
|
54
|
+
const server = new RecordingSocketIOServer();
|
|
55
|
+
|
|
56
|
+
beforeAll(async () => {
|
|
57
|
+
(SourceRepository.get() as unknown as ObjectCache).objectCache['@proteinjs/event/DefaultSocketIOServerFactory'] =
|
|
58
|
+
[{ createSocketIOServer: async () => server }];
|
|
59
|
+
await SocketIOServerRepo.createSocketIOServer(createServer());
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
delete (SourceRepository.get() as unknown as ObjectCache).objectCache[
|
|
64
|
+
'@proteinjs/event/DefaultSocketIOServerFactory'
|
|
65
|
+
];
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
server.calls.length = 0;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it(`closes exactly the deleted sessions' sockets`, async () => {
|
|
73
|
+
await deleteSessions([session('s-1'), session('s-2')]);
|
|
74
|
+
|
|
75
|
+
expect(server.calls).toEqual([{ rooms: ['s-1', 's-2'], close: true }]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('a deletion with no session ids closes nothing — an empty room list would address every socket', async () => {
|
|
79
|
+
// socket.io's in-memory adapter applies an operation with NO rooms to every socket on the
|
|
80
|
+
// namespace (`apply`: `if (rooms.size) … else for every sid`), so `in([])` must never be sent.
|
|
81
|
+
await deleteSessions([]);
|
|
82
|
+
|
|
83
|
+
expect(server.calls).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
package/test/setup.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
process.env.SPANNER_EMULATOR_HOST = process.env.SPANNER_EMULATOR_HOST || 'localhost:9010';
|
|
2
2
|
process.env.DB_LOG_LEVEL = 'error';
|
|
3
|
+
// The key the throttled doors' digests (RequestDigests) derive from; a process without one refuses to digest.
|
|
4
|
+
process.env.SESSION_SECRET = process.env.SESSION_SECRET || 'user-server-test-secret';
|