@proteinjs/user-server 1.17.0 → 1.18.0

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,175 @@
1
+ import { getDbAsSystem } from '@proteinjs/db';
2
+ import { tables } from '@proteinjs/user';
3
+ import { invokeDevLogin } from './devLoginHarness';
4
+ import { UserServerTestEnvironment } from './UserServerTestEnvironment';
5
+
6
+ const testEnv = new UserServerTestEnvironment();
7
+
8
+ /**
9
+ * `DEV_BOOTSTRAP_ADMIN_EMAIL` — the first-admin door INSIDE `/dev/login` (n3xa plans/DEV_ESTATES.md
10
+ * D3, founder-ruled 2026-09-04). A dev estate on a fresh real database has no privileged account
11
+ * to grant from, and the no-raw-DB rule forbids the emulator-era seed scripts there — so the ONE
12
+ * sanctioned account door mints break-glass, once:
13
+ * - behind the door's existing two gates (DEVELOPMENT AND DEV_AUTO_LOGIN_EMAIL): closed = 404 as
14
+ * before, and the variable changes nothing;
15
+ * - only while NO account carries 'admin' (the same membership test the app's admin checks make);
16
+ * - only for the request whose resolved address equals the variable exactly (case-normalized
17
+ * the way every account email is);
18
+ * - once, audited like any grant (a role_grant_event row; actor = the account itself — the door
19
+ * acts for nobody else); a later call finds an admin and grants nothing.
20
+ * Outcomes are asserted on the rows (roles, audit), never on calls.
21
+ */
22
+
23
+ const ENV_EMAIL = 'dev@test.local';
24
+ const BOOTSTRAP_EMAIL = 'owner@test.local';
25
+
26
+ const userRow = async (email: string) => await getDbAsSystem().get(tables.User, { email });
27
+ const auditRows = async () => await getDbAsSystem().query(tables.RoleGrantEvent, {});
28
+ const adminRows = async () =>
29
+ (await getDbAsSystem().query(tables.User, {})).filter((user) => (user.roles ?? []).includes('admin'));
30
+
31
+ describe('devLogin — the DEV_BOOTSTRAP_ADMIN_EMAIL first-admin door', () => {
32
+ const originalEnv = {
33
+ DEVELOPMENT: process.env.DEVELOPMENT,
34
+ DEV_AUTO_LOGIN_EMAIL: process.env.DEV_AUTO_LOGIN_EMAIL,
35
+ DEV_BOOTSTRAP_ADMIN_EMAIL: process.env.DEV_BOOTSTRAP_ADMIN_EMAIL,
36
+ };
37
+
38
+ beforeAll(async () => {
39
+ await testEnv.beforeAll();
40
+ });
41
+
42
+ afterAll(async () => {
43
+ await testEnv.afterAll();
44
+ });
45
+
46
+ beforeEach(async () => {
47
+ process.env.DEVELOPMENT = 'true';
48
+ process.env.DEV_AUTO_LOGIN_EMAIL = ENV_EMAIL;
49
+ process.env.DEV_BOOTSTRAP_ADMIN_EMAIL = BOOTSTRAP_EMAIL;
50
+ // Every case starts from a fresh database: no accounts, no audit trail.
51
+ const db = getDbAsSystem();
52
+ await db.delete(tables.RoleGrantEvent, {});
53
+ await db.delete(tables.User, {});
54
+ });
55
+
56
+ afterEach(() => {
57
+ for (const [key, value] of Object.entries(originalEnv)) {
58
+ if (value === undefined) {
59
+ delete process.env[key];
60
+ } else {
61
+ process.env[key] = value;
62
+ }
63
+ }
64
+ });
65
+
66
+ it('a fresh database + the matching address: the account is created carrying admin, audited once, and logged in', async () => {
67
+ expect(await userRow(BOOTSTRAP_EMAIL)).toBeUndefined();
68
+
69
+ const outcome = await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
70
+
71
+ expect(outcome.loggedInAs).toBe(BOOTSTRAP_EMAIL);
72
+ expect(outcome.sessionSaved).toBe(true);
73
+ expect(outcome.redirect).toBe('/');
74
+ const owner = await userRow(BOOTSTRAP_EMAIL);
75
+ expect(owner!.roles).toEqual(['admin']);
76
+ const events = await auditRows();
77
+ expect(events).toHaveLength(1);
78
+ expect(events[0]).toMatchObject({ actor: owner!.id, target: owner!.id, role: 'admin', action: 'grant' });
79
+ expect(await adminRows()).toHaveLength(1);
80
+ });
81
+
82
+ it('a second call for the same address changes nothing: still exactly one admin, one grant', async () => {
83
+ await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
84
+
85
+ const outcome = await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
86
+
87
+ expect(outcome.loggedInAs).toBe(BOOTSTRAP_EMAIL);
88
+ expect((await userRow(BOOTSTRAP_EMAIL))!.roles).toEqual(['admin']);
89
+ expect(await auditRows()).toHaveLength(1);
90
+ expect(await adminRows()).toHaveLength(1);
91
+ });
92
+
93
+ it('an existing role-less account at the matching address is granted admin on its next login — the door serves created and loaded accounts alike', async () => {
94
+ await testEnv.createUser({ name: 'Owner', email: BOOTSTRAP_EMAIL });
95
+
96
+ const outcome = await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
97
+
98
+ expect(outcome.loggedInAs).toBe(BOOTSTRAP_EMAIL);
99
+ expect((await userRow(BOOTSTRAP_EMAIL))!.roles).toEqual(['admin']);
100
+ expect(await auditRows()).toHaveLength(1);
101
+ });
102
+
103
+ it('the default path is a request for DEV_AUTO_LOGIN_EMAIL: when that IS the bootstrap address, the default account carries admin', async () => {
104
+ process.env.DEV_BOOTSTRAP_ADMIN_EMAIL = ENV_EMAIL;
105
+
106
+ const outcome = await invokeDevLogin();
107
+
108
+ expect(outcome.loggedInAs).toBe(ENV_EMAIL);
109
+ expect((await userRow(ENV_EMAIL))!.roles).toEqual(['admin']);
110
+ expect(await auditRows()).toHaveLength(1);
111
+ });
112
+
113
+ it('the match is case-normalized like every account email — never a second account for a differently-cased address', async () => {
114
+ process.env.DEV_BOOTSTRAP_ADMIN_EMAIL = 'Owner@Test.local';
115
+
116
+ await invokeDevLogin({ email: 'owner@test.local' });
117
+
118
+ expect((await userRow(BOOTSTRAP_EMAIL))!.roles).toEqual(['admin']);
119
+ expect(await adminRows()).toHaveLength(1);
120
+ });
121
+
122
+ it('an admin already exists: the matching address gets an ordinary account — no grant, no audit row', async () => {
123
+ await testEnv.createUser({ name: 'Standing admin', email: 'standing-admin@test.local', roles: ['admin'] });
124
+
125
+ const outcome = await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
126
+
127
+ expect(outcome.loggedInAs).toBe(BOOTSTRAP_EMAIL);
128
+ expect((await userRow(BOOTSTRAP_EMAIL))!.roles).toEqual([]);
129
+ expect(await auditRows()).toHaveLength(0);
130
+ expect((await adminRows()).map((user) => user.email)).toEqual(['standing-admin@test.local']);
131
+ });
132
+
133
+ it('a different same-domain address: an ordinary account, no grant — and the bootstrap address itself stays uncreated', async () => {
134
+ const outcome = await invokeDevLogin({ email: 'agent@test.local' });
135
+
136
+ expect(outcome.loggedInAs).toBe('agent@test.local');
137
+ expect((await userRow('agent@test.local'))!.roles).toEqual([]);
138
+ expect(await auditRows()).toHaveLength(0);
139
+ expect(await userRow(BOOTSTRAP_EMAIL)).toBeUndefined();
140
+ expect(await adminRows()).toHaveLength(0);
141
+ });
142
+
143
+ it('the address must match exactly — a plus-suffixed variant is a different account and gets nothing', async () => {
144
+ const outcome = await invokeDevLogin({ email: 'owner+lane@test.local' });
145
+
146
+ expect(outcome.loggedInAs).toBe('owner+lane@test.local');
147
+ expect((await userRow('owner+lane@test.local'))!.roles).toEqual([]);
148
+ expect(await auditRows()).toHaveLength(0);
149
+ });
150
+
151
+ it('with the variable unset the matching address is an ordinary account — the omission is the safety (test/prod never set it)', async () => {
152
+ delete process.env.DEV_BOOTSTRAP_ADMIN_EMAIL;
153
+
154
+ await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
155
+
156
+ expect((await userRow(BOOTSTRAP_EMAIL))!.roles).toEqual([]);
157
+ expect(await auditRows()).toHaveLength(0);
158
+ });
159
+
160
+ it.each([['DEVELOPMENT'], ['DEV_AUTO_LOGIN_EMAIL']])(
161
+ 'gate closed (%s unset): 404 exactly as before — no session, no account, no admin; the variable changes nothing',
162
+ async (gate) => {
163
+ delete process.env[gate];
164
+
165
+ const outcome = await invokeDevLogin({ email: BOOTSTRAP_EMAIL });
166
+
167
+ expect(outcome.status).toBe(404);
168
+ expect(outcome.loggedInAs).toBeUndefined();
169
+ expect(outcome.sessionSaved).toBe(false);
170
+ expect(await userRow(BOOTSTRAP_EMAIL)).toBeUndefined();
171
+ expect(await auditRows()).toHaveLength(0);
172
+ expect(await adminRows()).toHaveLength(0);
173
+ }
174
+ );
175
+ });
@@ -0,0 +1,39 @@
1
+ import { devLogin } from '../src/routes/devLogin';
2
+ import { createPassportRequest } from './passportSessionHarness';
3
+
4
+ /** What one `GET /dev/login` did, seen from outside: the session it minted (or not) and the response it sent. */
5
+ export type DevLoginOutcome = {
6
+ loggedInAs?: string;
7
+ sessionRegenerated: boolean;
8
+ sessionSaved: boolean;
9
+ status?: number;
10
+ body?: unknown;
11
+ redirect?: string;
12
+ };
13
+
14
+ /**
15
+ * Drives the devLogin route once over the REAL passport login machinery (see
16
+ * passportSessionHarness): the full session-establishment contract (regenerate → login → save)
17
+ * is pinned by SignupRoute.test.ts — the dev-door suites assert the door INHERITS it.
18
+ */
19
+ export const invokeDevLogin = async (query?: Record<string, unknown>): Promise<DevLoginOutcome> => {
20
+ const { request, events } = await createPassportRequest({ query });
21
+ const outcome: DevLoginOutcome = { sessionRegenerated: false, sessionSaved: false };
22
+ const response = {
23
+ status(code: number) {
24
+ outcome.status = code;
25
+ return this;
26
+ },
27
+ send(body?: unknown) {
28
+ outcome.body = body;
29
+ },
30
+ redirect(path: string) {
31
+ outcome.redirect = path;
32
+ },
33
+ };
34
+ await devLogin.onRequest(request as never, response as never);
35
+ outcome.loggedInAs = request.session.passport?.user;
36
+ outcome.sessionRegenerated = events.includes('regenerate');
37
+ outcome.sessionSaved = events.includes('save');
38
+ return outcome;
39
+ };