@oneuptime/common 12.0.20 → 12.0.22
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/Models/AnalyticsModels/ChangeEvent.ts +331 -0
- package/Models/AnalyticsModels/Index.ts +2 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/UserTwoFactorBackupCode.ts +262 -0
- package/Server/API/UserTwoFactorBackupCodeAPI.ts +258 -0
- package/Server/EnvironmentConfig.ts +1 -8
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddUserTwoFactorBackupCode.ts +63 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -2
- package/Server/Services/ChangeEventService.ts +11 -0
- package/Server/Services/EnterpriseLicenseService.ts +3 -3
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/UserService.ts +42 -0
- package/Server/Services/UserTwoFactorBackupCodeService.ts +355 -0
- package/Server/Utils/Attribution.ts +11 -11
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +4 -5
- package/Server/Utils/Telemetry/ChangeEventRow.ts +228 -0
- package/Server/Utils/TwoFactorBackupCode.ts +266 -0
- package/Server/Views/Partials/AnalyticsConsent.ejs +3 -95
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +64 -0
- package/Tests/App/Dashboard/EventOverlayHook.test.tsx +249 -0
- package/Tests/App/Dashboard/InvestigationArtifacts.test.tsx +315 -0
- package/Tests/App/Dashboard/InvestigationDrawer.test.tsx +258 -0
- package/Tests/App/Dashboard/MetricChartsCompare.test.tsx +171 -0
- package/Tests/App/Dashboard/MetricViewCompare.test.tsx +233 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +16 -3
- package/Tests/Server/API/UserTwoFactorAuthAdminAPI.test.ts +18 -2
- package/Tests/Server/API/UserTwoFactorBackupCodeAPI.test.ts +1390 -0
- package/Tests/Server/Infrastructure/Postgres/AddUserSlackAndMicrosoftTeamsMigration.test.ts +236 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +23 -1
- package/Tests/Server/Services/UserTwoFactorAuthAdmin.test.ts +21 -0
- package/Tests/Server/Services/UserTwoFactorBackupCodeAdminSurface.test.ts +919 -0
- package/Tests/Server/Services/UserTwoFactorBackupCodeService.test.ts +862 -0
- package/Tests/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.test.ts +224 -0
- package/Tests/Server/Utils/Attribution.test.ts +4 -5
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +2 -2
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.test.ts +300 -0
- package/Tests/Server/Utils/TwoFactorBackupCode.test.ts +475 -0
- package/Tests/UI/Components/Charts/ChartGhostSeries.test.tsx +103 -0
- package/Tests/UI/Components/Charts/TooltipEntries.test.ts +44 -8
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +13 -2
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +7 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/Marketing/Attribution.ts +9 -15
- package/Types/Marketing/MarketingEvent.ts +10 -46
- package/Types/UserAuthenticationStatus.ts +16 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +15 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +16 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +189 -93
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +6 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +213 -114
- package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +42 -27
- package/UI/Components/Charts/Line/LineChart.tsx +15 -0
- package/Utils/Metrics/MetricExplorerUrl.ts +119 -5
- package/Utils/Telemetry/CrossSignalScope.ts +19 -10
- package/build/dist/Models/AnalyticsModels/ChangeEvent.js +298 -0
- package/build/dist/Models/AnalyticsModels/ChangeEvent.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/Index.js +2 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTwoFactorBackupCode.js +277 -0
- package/build/dist/Models/DatabaseModels/UserTwoFactorBackupCode.js.map +1 -0
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js +201 -0
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +1 -7
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddUserTwoFactorBackupCode.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddUserTwoFactorBackupCode.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/ChangeEventService.js +9 -0
- package/build/dist/Server/Services/ChangeEventService.js.map +1 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js +3 -3
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +39 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js +327 -0
- package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js.map +1 -0
- package/build/dist/Server/Utils/Attribution.js +6 -6
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +4 -5
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ChangeEventRow.js +150 -0
- package/build/dist/Server/Utils/Telemetry/ChangeEventRow.js.map +1 -0
- package/build/dist/Server/Utils/TwoFactorBackupCode.js +269 -0
- package/build/dist/Server/Utils/TwoFactorBackupCode.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +7 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Marketing/Attribution.js +9 -15
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +2 -33
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +9 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +92 -38
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +8 -8
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +95 -37
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +39 -24
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js +81 -0
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +14 -8
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +0 -117
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +0 -106
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +0 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { AddUserSlackAndMicrosoftTeams1789000000000 } from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams";
|
|
2
|
+
import SchemaMigrations from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
|
|
3
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { describe, expect, test } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* The guard that lets this migration run twice.
|
|
10
|
+
*
|
|
11
|
+
* It shipped as AddUserSlackAndMicrosoftTeams1788800000000 and was renumbered
|
|
12
|
+
* to 1789000000000 to clear a timestamp collision. TypeORM records a migration
|
|
13
|
+
* by class NAME, so the rename made it pending again on every database that
|
|
14
|
+
* had already run it, and `CREATE TABLE "UserSlack"` failed on its own table —
|
|
15
|
+
* which stops the runner, leaving this and every later migration pending on
|
|
16
|
+
* every boot until it is fixed.
|
|
17
|
+
*
|
|
18
|
+
* So there are two databases to satisfy, and both are asserted below: one that
|
|
19
|
+
* has never seen this migration must get the whole schema, and one that
|
|
20
|
+
* already carries it must get no DDL at all.
|
|
21
|
+
*
|
|
22
|
+
* The statements are inspected rather than executed — these tests run without
|
|
23
|
+
* a database, and what can go wrong here is which statements are issued, not
|
|
24
|
+
* whether Postgres understands them.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const MIGRATION_NAME: string = "AddUserSlackAndMicrosoftTeams1789000000000";
|
|
28
|
+
|
|
29
|
+
const SUPERSEDED_MIGRATION_NAME: string =
|
|
30
|
+
"AddUserSlackAndMicrosoftTeams1788800000000";
|
|
31
|
+
|
|
32
|
+
const MIGRATION_TIMESTAMP: string = "1789000000000";
|
|
33
|
+
|
|
34
|
+
const MIGRATION_DIRECTORY: string = path.join(
|
|
35
|
+
__dirname,
|
|
36
|
+
"../../../../Server/Infrastructure/Postgres/SchemaMigrations",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Every statement `up` issues after the probe. The count is asserted rather
|
|
41
|
+
* than sampled: the risk the guard introduces is an early return that swallows
|
|
42
|
+
* work a fresh install needs, and only a total catches a statement going
|
|
43
|
+
* missing from the middle.
|
|
44
|
+
*/
|
|
45
|
+
const STATEMENTS_IN_UP: number = 30;
|
|
46
|
+
|
|
47
|
+
type CapturedQueryRunner = {
|
|
48
|
+
runner: QueryRunner;
|
|
49
|
+
queries: Array<string>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type IsProbeFunction = (sql: string) => boolean;
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
* The one read `up` makes: does "UserSlack" already exist? Matched on shape
|
|
56
|
+
* rather than on the exact string so reformatting the SQL does not fail these.
|
|
57
|
+
*/
|
|
58
|
+
const isProbe: IsProbeFunction = (sql: string): boolean => {
|
|
59
|
+
return (
|
|
60
|
+
sql.includes("information_schema.tables") && sql.includes("'UserSlack'")
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type MockQueryRunnerFunction = (
|
|
65
|
+
userSlackTableExists: boolean,
|
|
66
|
+
) => CapturedQueryRunner;
|
|
67
|
+
|
|
68
|
+
const mockQueryRunner: MockQueryRunnerFunction = (
|
|
69
|
+
userSlackTableExists: boolean,
|
|
70
|
+
): CapturedQueryRunner => {
|
|
71
|
+
const queries: Array<string> = [];
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
queries: queries,
|
|
75
|
+
runner: {
|
|
76
|
+
query: async (sql: string): Promise<unknown> => {
|
|
77
|
+
queries.push(sql);
|
|
78
|
+
|
|
79
|
+
if (isProbe(sql)) {
|
|
80
|
+
return [{ exists: userSlackTableExists }];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return undefined;
|
|
84
|
+
},
|
|
85
|
+
} as unknown as QueryRunner,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const migration: AddUserSlackAndMicrosoftTeams1789000000000 =
|
|
90
|
+
new AddUserSlackAndMicrosoftTeams1789000000000();
|
|
91
|
+
|
|
92
|
+
describe("AddUserSlackAndMicrosoftTeams migration", () => {
|
|
93
|
+
/*
|
|
94
|
+
* TypeORM matches migrations by this name. A mismatch between the class name
|
|
95
|
+
* and the `name` property makes the migration run again on every boot.
|
|
96
|
+
*/
|
|
97
|
+
test("names itself consistently so it is recorded as run", () => {
|
|
98
|
+
expect(migration.name).toBe(MIGRATION_NAME);
|
|
99
|
+
expect(AddUserSlackAndMicrosoftTeams1789000000000.name).toBe(
|
|
100
|
+
MIGRATION_NAME,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("registration", () => {
|
|
105
|
+
test("it is registered exactly once", () => {
|
|
106
|
+
const occurrences: number = SchemaMigrations.filter(
|
|
107
|
+
(registered: new () => MigrationInterface): boolean => {
|
|
108
|
+
return registered.name === MIGRATION_NAME;
|
|
109
|
+
},
|
|
110
|
+
).length;
|
|
111
|
+
|
|
112
|
+
expect(occurrences).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* The old number belongs to DropMarketingConversionAddEnterpriseLicenseEmail
|
|
117
|
+
* now. Registering both names would run the same DDL twice under two
|
|
118
|
+
* records, which is the failure this migration already had once.
|
|
119
|
+
*/
|
|
120
|
+
test("no longer registers the timestamp it was renamed away from", () => {
|
|
121
|
+
const names: Array<string> = SchemaMigrations.map(
|
|
122
|
+
(registered: new () => MigrationInterface): string => {
|
|
123
|
+
return registered.name;
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(names).not.toContain(SUPERSEDED_MIGRATION_NAME);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("exactly one file on disk carries its timestamp, and it is this one", () => {
|
|
131
|
+
const matching: Array<string> = fs
|
|
132
|
+
.readdirSync(MIGRATION_DIRECTORY)
|
|
133
|
+
.filter((file: string): boolean => {
|
|
134
|
+
return file.startsWith(`${MIGRATION_TIMESTAMP}-`);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(matching).toEqual([
|
|
138
|
+
`${MIGRATION_TIMESTAMP}-AddUserSlackAndMicrosoftTeams.ts`,
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("on a database that has never run it", () => {
|
|
144
|
+
test("checks for the table before touching anything", async () => {
|
|
145
|
+
const up: CapturedQueryRunner = mockQueryRunner(false);
|
|
146
|
+
|
|
147
|
+
await migration.up(up.runner);
|
|
148
|
+
|
|
149
|
+
expect(isProbe(up.queries[0]!)).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("creates the whole schema, unchanged by the guard", async () => {
|
|
153
|
+
const up: CapturedQueryRunner = mockQueryRunner(false);
|
|
154
|
+
|
|
155
|
+
await migration.up(up.runner);
|
|
156
|
+
|
|
157
|
+
const statements: Array<string> = up.queries.filter(
|
|
158
|
+
(sql: string): boolean => {
|
|
159
|
+
return !isProbe(sql);
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(statements.length).toBe(STATEMENTS_IN_UP);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("creates both method tables and their notification wiring", async () => {
|
|
167
|
+
const up: CapturedQueryRunner = mockQueryRunner(false);
|
|
168
|
+
|
|
169
|
+
await migration.up(up.runner);
|
|
170
|
+
|
|
171
|
+
const sql: string = up.queries.join("\n");
|
|
172
|
+
|
|
173
|
+
expect(sql).toContain(`CREATE TABLE "UserSlack"`);
|
|
174
|
+
expect(sql).toContain(`CREATE TABLE "UserMicrosoftTeams"`);
|
|
175
|
+
expect(sql).toContain(
|
|
176
|
+
`ALTER TABLE "UserNotificationRule" ADD "userSlackId" uuid`,
|
|
177
|
+
);
|
|
178
|
+
expect(sql).toContain(
|
|
179
|
+
`ALTER TABLE "UserNotificationRule" ADD "userMicrosoftTeamsId" uuid`,
|
|
180
|
+
);
|
|
181
|
+
expect(sql).toContain(
|
|
182
|
+
`ALTER TABLE "UserNotificationSetting" ADD "alertBySlack" boolean NOT NULL DEFAULT false`,
|
|
183
|
+
);
|
|
184
|
+
expect(sql).toContain(
|
|
185
|
+
`ALTER TABLE "UserNotificationSetting" ADD "alertByMicrosoftTeams" boolean NOT NULL DEFAULT false`,
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("on a database that already ran it under its old name", () => {
|
|
191
|
+
/*
|
|
192
|
+
* The whole point. Migrations run one transaction each, so a recorded run
|
|
193
|
+
* committed all 30 statements or none of them — the table being there means
|
|
194
|
+
* the schema is there, and re-issuing any of it can only fail.
|
|
195
|
+
*/
|
|
196
|
+
test("issues nothing but the probe", async () => {
|
|
197
|
+
const up: CapturedQueryRunner = mockQueryRunner(true);
|
|
198
|
+
|
|
199
|
+
await migration.up(up.runner);
|
|
200
|
+
|
|
201
|
+
expect(up.queries.length).toBe(1);
|
|
202
|
+
expect(isProbe(up.queries[0]!)).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("never re-runs the CREATE TABLE that was failing", async () => {
|
|
206
|
+
const up: CapturedQueryRunner = mockQueryRunner(true);
|
|
207
|
+
|
|
208
|
+
await migration.up(up.runner);
|
|
209
|
+
|
|
210
|
+
const sql: string = up.queries.join("\n");
|
|
211
|
+
|
|
212
|
+
expect(sql).not.toContain(`CREATE TABLE "UserSlack"`);
|
|
213
|
+
expect(sql).not.toContain(`CREATE TABLE "UserMicrosoftTeams"`);
|
|
214
|
+
expect(sql).not.toContain("ADD CONSTRAINT");
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* `down` is deliberately NOT guarded. It reverses the schema whichever name
|
|
220
|
+
* recorded it, and a rollback that finds the tables missing has bigger
|
|
221
|
+
* problems than a failed DROP.
|
|
222
|
+
*/
|
|
223
|
+
describe("down", () => {
|
|
224
|
+
test("drops both method tables", async () => {
|
|
225
|
+
const down: CapturedQueryRunner = mockQueryRunner(true);
|
|
226
|
+
|
|
227
|
+
await migration.down(down.runner);
|
|
228
|
+
|
|
229
|
+
const sql: string = down.queries.join("\n");
|
|
230
|
+
|
|
231
|
+
expect(sql).toContain(`DROP TABLE "UserSlack"`);
|
|
232
|
+
expect(sql).toContain(`DROP TABLE "UserMicrosoftTeams"`);
|
|
233
|
+
expect(down.queries.length).toBe(STATEMENTS_IN_UP);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -5,6 +5,7 @@ import UserService from "../../../Server/Services/UserService";
|
|
|
5
5
|
import UserSessionService from "../../../Server/Services/UserSessionService";
|
|
6
6
|
import UserTotpAuthService from "../../../Server/Services/UserTotpAuthService";
|
|
7
7
|
import UserWebAuthnService from "../../../Server/Services/UserWebAuthnService";
|
|
8
|
+
import UserTwoFactorBackupCodeService from "../../../Server/Services/UserTwoFactorBackupCodeService";
|
|
8
9
|
import logger from "../../../Server/Utils/Logger";
|
|
9
10
|
import User from "../../../Models/DatabaseModels/User";
|
|
10
11
|
import Hostname from "../../../Types/API/Hostname";
|
|
@@ -191,6 +192,25 @@ describe("UserService -- master-admin authentication management", () => {
|
|
|
191
192
|
return new PositiveNumber(0);
|
|
192
193
|
});
|
|
193
194
|
|
|
195
|
+
/*
|
|
196
|
+
* getAuthenticationStatus also reads the user's remaining two factor
|
|
197
|
+
* BACKUP codes, which is a third table and therefore a third stub. Without
|
|
198
|
+
* it the real service reaches a repository that is not connected in this
|
|
199
|
+
* suite, and every test here fails with "Database not connected" rather
|
|
200
|
+
* than anything to do with what it was checking.
|
|
201
|
+
*
|
|
202
|
+
* Deliberately NOT folded into the two counts above -- backup codes are a
|
|
203
|
+
* way back in to an account whose factor is unreachable, not a factor.
|
|
204
|
+
* Common/Tests/Server/Services/UserTwoFactorBackupCodeAdminSurface.test.ts
|
|
205
|
+
* is what pins that distinction; here it only has to be stubbed.
|
|
206
|
+
*/
|
|
207
|
+
getJestSpyOn(
|
|
208
|
+
UserTwoFactorBackupCodeService,
|
|
209
|
+
"countUnusedForUser",
|
|
210
|
+
).mockImplementation(async (): Promise<number> => {
|
|
211
|
+
return 0;
|
|
212
|
+
});
|
|
213
|
+
|
|
194
214
|
getJestSpyOn(DatabaseConfig, "getHost").mockImplementation(
|
|
195
215
|
async (): Promise<Hostname> => {
|
|
196
216
|
return new Hostname(TEST_HOSTNAME);
|
|
@@ -402,7 +422,7 @@ describe("UserService -- master-admin authentication management", () => {
|
|
|
402
422
|
expect(status.hasPendingPasswordResetLink).toBe(true);
|
|
403
423
|
});
|
|
404
424
|
|
|
405
|
-
test("returns
|
|
425
|
+
test("returns exactly the vetted fields -- never the password digest itself", async () => {
|
|
406
426
|
/*
|
|
407
427
|
* The whole reason this is a DTO rather than a selected row: the caller
|
|
408
428
|
* is a master admin, whose props bypass column read permissions, so
|
|
@@ -433,6 +453,7 @@ describe("UserService -- master-admin authentication management", () => {
|
|
|
433
453
|
"isEmailVerified",
|
|
434
454
|
"isTwoFactorAuthEnabled",
|
|
435
455
|
"twoFactorAuthStatus",
|
|
456
|
+
"unusedTwoFactorBackupCodeCount",
|
|
436
457
|
"verifiedTwoFactorAuthMethodCount",
|
|
437
458
|
]);
|
|
438
459
|
|
|
@@ -452,6 +473,7 @@ describe("UserService -- master-admin authentication management", () => {
|
|
|
452
473
|
expect(typeof status.hasPendingPasswordResetLink).toBe("boolean");
|
|
453
474
|
expect(typeof status.twoFactorAuthStatus).toBe("string");
|
|
454
475
|
expect(typeof status.verifiedTwoFactorAuthMethodCount).toBe("number");
|
|
476
|
+
expect(typeof status.unusedTwoFactorBackupCodeCount).toBe("number");
|
|
455
477
|
});
|
|
456
478
|
|
|
457
479
|
test("selects exactly the columns the row-derived fields come from", async () => {
|
|
@@ -3,6 +3,7 @@ import UserService from "../../../Server/Services/UserService";
|
|
|
3
3
|
import UserSessionService from "../../../Server/Services/UserSessionService";
|
|
4
4
|
import UserTotpAuthService from "../../../Server/Services/UserTotpAuthService";
|
|
5
5
|
import UserWebAuthnService from "../../../Server/Services/UserWebAuthnService";
|
|
6
|
+
import UserTwoFactorBackupCodeService from "../../../Server/Services/UserTwoFactorBackupCodeService";
|
|
6
7
|
import logger from "../../../Server/Utils/Logger";
|
|
7
8
|
import User from "../../../Models/DatabaseModels/User";
|
|
8
9
|
import ColumnLength from "../../../Types/Database/ColumnLength";
|
|
@@ -201,6 +202,26 @@ describe("UserService -- admin-controlled two factor auth", () => {
|
|
|
201
202
|
return 0;
|
|
202
203
|
});
|
|
203
204
|
|
|
205
|
+
/*
|
|
206
|
+
* resetTwoFactorAuth clears the user's two factor BACKUP codes as well as
|
|
207
|
+
* their authenticators -- a reset that left a printed list of recovery
|
|
208
|
+
* codes alive would not have revoked the second-factor material it exists
|
|
209
|
+
* to revoke. Un-stubbed, the real service reaches a repository this suite
|
|
210
|
+
* does not connect and every resetTwoFactorAuth test below fails with
|
|
211
|
+
* "Database not connected" rather than anything it was checking.
|
|
212
|
+
*
|
|
213
|
+
* That the delete HAPPENS, for the right user, and before the session
|
|
214
|
+
* revocation, is asserted in
|
|
215
|
+
* Common/Tests/Server/Services/UserTwoFactorBackupCodeAdminSurface.test.ts
|
|
216
|
+
* -- this file only has to let the method run.
|
|
217
|
+
*/
|
|
218
|
+
getJestSpyOn(
|
|
219
|
+
UserTwoFactorBackupCodeService,
|
|
220
|
+
"deleteAllForUser",
|
|
221
|
+
).mockImplementation(async (): Promise<void> => {
|
|
222
|
+
return undefined;
|
|
223
|
+
});
|
|
224
|
+
|
|
204
225
|
/*
|
|
205
226
|
* The two probes the DELETED guards used to make. They are stubbed rather
|
|
206
227
|
* than left real so that "was this consulted at all?" is answerable, which
|