@oneuptime/common 12.0.3 → 12.0.4
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/DatabaseModels/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/DashboardAPI.ts +419 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +7 -3
- package/Server/Infrastructure/GlobalCache.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +185 -2
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/PasswordHash.ts +306 -0
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
- package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
- package/Types/Database/ColumnLength.ts +9 -1
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/HashedString.ts +139 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +304 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +7 -3
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +160 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- package/build/dist/Server/Utils/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- package/build/dist/Types/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- package/build/dist/Utils/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import ProjectService from "../../../Services/ProjectService";
|
|
11
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
12
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
13
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
14
|
+
/*
|
|
15
|
+
* A GitHub App installation ID is a small, guessable integer, and the single
|
|
16
|
+
* GitHub App JWT this instance holds will mint a `ghs_` token for ANY of them
|
|
17
|
+
* (GitHubUtil.getInstallationAccessToken does not — and cannot — know which
|
|
18
|
+
* tenant asked). So the installation ID alone is not a capability: on its own
|
|
19
|
+
* it is just an attacker-supplied number.
|
|
20
|
+
*
|
|
21
|
+
* Project.gitHubAppInstallationId is the ONE authoritative record of which
|
|
22
|
+
* project an installation belongs to. It is written only by the GitHub App
|
|
23
|
+
* install callback, and only after GitHub itself has confirmed — via the
|
|
24
|
+
* OAuth user-to-server round trip — that the person completing the install
|
|
25
|
+
* controls that installation. Both `create` and `update` on that column are
|
|
26
|
+
* closed to API callers, so nothing outside that callback can assert a
|
|
27
|
+
* binding.
|
|
28
|
+
*
|
|
29
|
+
* Every path that turns an installation ID into GitHub access must therefore
|
|
30
|
+
* re-derive the binding from here rather than trusting the ID it was handed.
|
|
31
|
+
* Checking that a CodeRepository row is in the caller's project is NOT enough
|
|
32
|
+
* on its own: the attacker owns their own project, so they control both sides
|
|
33
|
+
* of that comparison.
|
|
34
|
+
*/
|
|
35
|
+
export default class GitHubInstallationBinding {
|
|
36
|
+
/*
|
|
37
|
+
* The installation this project owns, or null if it has none. Callers
|
|
38
|
+
* filtering a whole project's repositories should read it once and compare
|
|
39
|
+
* in memory rather than asking per row — every row in such a list shares
|
|
40
|
+
* the same project, so the answer cannot differ between them.
|
|
41
|
+
*/
|
|
42
|
+
static async getBoundInstallationId(projectId) {
|
|
43
|
+
const project = await ProjectService.findOneBy({
|
|
44
|
+
query: {
|
|
45
|
+
_id: projectId.toString(),
|
|
46
|
+
},
|
|
47
|
+
select: {
|
|
48
|
+
_id: true,
|
|
49
|
+
gitHubAppInstallationId: true,
|
|
50
|
+
},
|
|
51
|
+
props: {
|
|
52
|
+
isRoot: true,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return (project === null || project === void 0 ? void 0 : project.gitHubAppInstallationId) || null;
|
|
56
|
+
}
|
|
57
|
+
// True only if `installationId` is the installation bound to `projectId`.
|
|
58
|
+
static async isInstallationBoundToProject(data) {
|
|
59
|
+
if (!data.installationId) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const project = await ProjectService.findOneBy({
|
|
63
|
+
query: {
|
|
64
|
+
_id: data.projectId.toString(),
|
|
65
|
+
gitHubAppInstallationId: data.installationId.toString(),
|
|
66
|
+
},
|
|
67
|
+
select: {
|
|
68
|
+
_id: true,
|
|
69
|
+
},
|
|
70
|
+
props: {
|
|
71
|
+
isRoot: true,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
return Boolean(project);
|
|
75
|
+
}
|
|
76
|
+
/*
|
|
77
|
+
* Convenience for the many callers that hold a CodeRepository row and are
|
|
78
|
+
* about to use its installation ID against GitHub. Returns false for a row
|
|
79
|
+
* that carries no project or no installation, so a half-populated row can
|
|
80
|
+
* never be mistaken for a permitted one.
|
|
81
|
+
*
|
|
82
|
+
* Row-level checks like this stay necessary even with the write boundary
|
|
83
|
+
* closed, because a database that was exploited before the fix can still
|
|
84
|
+
* hold rows whose installation ID never belonged to their project.
|
|
85
|
+
*/
|
|
86
|
+
static async isRepositoryInstallationBound(repository) {
|
|
87
|
+
if (!repository.projectId || !repository.gitHubAppInstallationId) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return GitHubInstallationBinding.isInstallationBoundToProject({
|
|
91
|
+
projectId: repository.projectId,
|
|
92
|
+
installationId: repository.gitHubAppInstallationId,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/*
|
|
96
|
+
* Fail-closed variant for the places where continuing without a binding
|
|
97
|
+
* would hand out cross-tenant access.
|
|
98
|
+
*/
|
|
99
|
+
static async assertInstallationBoundToProject(data) {
|
|
100
|
+
const isBound = await GitHubInstallationBinding.isInstallationBoundToProject(data);
|
|
101
|
+
if (!isBound) {
|
|
102
|
+
/*
|
|
103
|
+
* Deliberately vague: a probing caller must not be able to tell
|
|
104
|
+
* "this installation belongs to someone else" from "no such
|
|
105
|
+
* installation", or the error itself becomes the enumeration oracle
|
|
106
|
+
* this check exists to close.
|
|
107
|
+
*/
|
|
108
|
+
throw new BadDataException("This GitHub App installation is not connected to this project. Please connect the repository from Project Settings using the GitHub App.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
__decorate([
|
|
113
|
+
CaptureSpan(),
|
|
114
|
+
__metadata("design:type", Function),
|
|
115
|
+
__metadata("design:paramtypes", [ObjectID]),
|
|
116
|
+
__metadata("design:returntype", Promise)
|
|
117
|
+
], GitHubInstallationBinding, "getBoundInstallationId", null);
|
|
118
|
+
__decorate([
|
|
119
|
+
CaptureSpan(),
|
|
120
|
+
__metadata("design:type", Function),
|
|
121
|
+
__metadata("design:paramtypes", [Object]),
|
|
122
|
+
__metadata("design:returntype", Promise)
|
|
123
|
+
], GitHubInstallationBinding, "isInstallationBoundToProject", null);
|
|
124
|
+
__decorate([
|
|
125
|
+
CaptureSpan(),
|
|
126
|
+
__metadata("design:type", Function),
|
|
127
|
+
__metadata("design:paramtypes", [Object]),
|
|
128
|
+
__metadata("design:returntype", Promise)
|
|
129
|
+
], GitHubInstallationBinding, "isRepositoryInstallationBound", null);
|
|
130
|
+
__decorate([
|
|
131
|
+
CaptureSpan(),
|
|
132
|
+
__metadata("design:type", Function),
|
|
133
|
+
__metadata("design:paramtypes", [Object]),
|
|
134
|
+
__metadata("design:returntype", Promise)
|
|
135
|
+
], GitHubInstallationBinding, "assertInstallationBoundToProject", null);
|
|
136
|
+
//# sourceMappingURL=GitHubInstallationBinding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHubInstallationBinding.js","sourceRoot":"","sources":["../../../../../../Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,cAAc,MAAM,kCAAkC,CAAC;AAE9D,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AAClD,OAAO,gBAAgB,MAAM,8CAA8C,CAAC;AAC5E,OAAO,WAAW,MAAM,6BAA6B,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,OAAO,OAAO,yBAAyB;IAC5C;;;;;OAKG;IAEiB,AAAb,MAAM,CAAC,KAAK,CAAC,sBAAsB,CACxC,SAAmB;QAEnB,MAAM,OAAO,GAAmB,MAAM,cAAc,CAAC,SAAS,CAAC;YAC7D,KAAK,EAAE;gBACL,GAAG,EAAE,SAAS,CAAC,QAAQ,EAAE;aAC1B;YACD,MAAM,EAAE;gBACN,GAAG,EAAE,IAAI;gBACT,uBAAuB,EAAE,IAAI;aAC9B;YACD,KAAK,EAAE;gBACL,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CAAC;QAEH,OAAO,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,uBAAuB,KAAI,IAAI,CAAC;IAClD,CAAC;IAED,0EAA0E;IAEtD,AAAb,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,IAGhD;QACC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAmB,MAAM,cAAc,CAAC,SAAS,CAAC;YAC7D,KAAK,EAAE;gBACL,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE;gBAC9B,uBAAuB,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE;aACxD;YACD,MAAM,EAAE;gBACN,GAAG,EAAE,IAAI;aACV;YACD,KAAK,EAAE;gBACL,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED;;;;;;;;;OASG;IAEiB,AAAb,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,UAGjD;QACC,IAAI,CAAC,UAAU,CAAC,SAAS,IAAI,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC;YACjE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,yBAAyB,CAAC,4BAA4B,CAAC;YAC5D,SAAS,EAAE,UAAU,CAAC,SAAS;YAC/B,cAAc,EAAE,UAAU,CAAC,uBAAuB;SACnD,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IAEiB,AAAb,MAAM,CAAC,KAAK,CAAC,gCAAgC,CAAC,IAGpD;QACC,MAAM,OAAO,GACX,MAAM,yBAAyB,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC;QAErE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb;;;;;eAKG;YACH,MAAM,IAAI,gBAAgB,CACxB,0IAA0I,CAC3I,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA9FqB;IADnB,WAAW,EAAE;;qCAED,QAAQ;;6DAgBpB;AAImB;IADnB,WAAW,EAAE;;;;mEAuBb;AAamB;IADnB,WAAW,EAAE;;;;oEAab;AAOmB;IADnB,WAAW,EAAE;;;;uEAmBb"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { EncryptionSecret } from "../EnvironmentConfig";
|
|
2
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
3
|
+
import HashedString from "../../Types/HashedString";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
/*
|
|
6
|
+
* Password hashing for user credentials.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS IS NOT A PLAIN HASH
|
|
9
|
+
*
|
|
10
|
+
* SHA-256 is designed to be fast, and that is exactly wrong for a password.
|
|
11
|
+
* A commodity GPU computes billions of SHA-256 digests per second, so a
|
|
12
|
+
* leaked User table is a dictionary attack that finishes over lunch — a
|
|
13
|
+
* per-user salt stops the attacker amortising that work across accounts, but
|
|
14
|
+
* it does nothing to slow down any single guess.
|
|
15
|
+
*
|
|
16
|
+
* scrypt is deliberately expensive in BOTH time and memory. The memory cost
|
|
17
|
+
* is the important half: it is what stops an attacker buying a thousandfold
|
|
18
|
+
* speedup with GPUs or custom silicon, because memory bandwidth is the one
|
|
19
|
+
* thing that hardware cannot cheaply parallelise.
|
|
20
|
+
*
|
|
21
|
+
* THE CONSTRUCTION
|
|
22
|
+
*
|
|
23
|
+
* input = HMAC-SHA256(key = EncryptionSecret, message = password)
|
|
24
|
+
* derived = scrypt(input, salt, 32 bytes, { N, r, p })
|
|
25
|
+
*
|
|
26
|
+
* The HMAC step carries the instance-wide EncryptionSecret as a "pepper".
|
|
27
|
+
* The pepper lives in configuration, not in the database, so a dump of
|
|
28
|
+
* Postgres alone is not enough to start guessing — the attacker also needs
|
|
29
|
+
* the deployment's secret. It also normalises the password to a fixed 32
|
|
30
|
+
* bytes before scrypt sees it, which sidesteps every question about very
|
|
31
|
+
* long or oddly-encoded inputs.
|
|
32
|
+
*
|
|
33
|
+
* THE STORED FORMAT
|
|
34
|
+
*
|
|
35
|
+
* scrypt$N=16384,r=8,p=1$<64 hex characters>
|
|
36
|
+
*
|
|
37
|
+
* The hash carries the algorithm and the cost parameters it was produced
|
|
38
|
+
* with. That is what makes raising the cost later a non-event: old hashes
|
|
39
|
+
* keep verifying against the parameters recorded in them, and each user's
|
|
40
|
+
* hash is re-derived at their next successful login. Nothing about a cost
|
|
41
|
+
* bump requires a migration or a forced password reset.
|
|
42
|
+
*
|
|
43
|
+
* The salt is NOT in this string — it lives in the row's own salt column
|
|
44
|
+
* (see TableColumn.hashSaltColumn), which is where the previous scheme put
|
|
45
|
+
* it and where the write path keeps it in step with the hash.
|
|
46
|
+
*/
|
|
47
|
+
/*
|
|
48
|
+
* N is the CPU/memory cost. Memory used is 128 * N * r bytes, so the values
|
|
49
|
+
* below cost 16 MiB and roughly 50ms per hash on current server hardware.
|
|
50
|
+
*
|
|
51
|
+
* That is a deliberate middle: enough to make offline guessing expensive,
|
|
52
|
+
* cheap enough that a burst of logins does not exhaust the libuv thread pool
|
|
53
|
+
* or the box's memory. Raising N is a one-line change — bump it here, and
|
|
54
|
+
* every user is migrated to the new cost as they log in.
|
|
55
|
+
*/
|
|
56
|
+
const CURRENT_PARAMS = {
|
|
57
|
+
N: 16384,
|
|
58
|
+
r: 8,
|
|
59
|
+
p: 1,
|
|
60
|
+
};
|
|
61
|
+
/** Bytes of derived key. 32 bytes = the 64 hex characters stored. */
|
|
62
|
+
const KEY_LENGTH_IN_BYTES = 32;
|
|
63
|
+
const SCHEME_NAME = "scrypt";
|
|
64
|
+
/*
|
|
65
|
+
* Refuse absurd parameters read back out of the database. Stored values are
|
|
66
|
+
* ours, not an attacker's, but a corrupted row must fail the login rather
|
|
67
|
+
* than ask Node to allocate gigabytes.
|
|
68
|
+
*/
|
|
69
|
+
const MAX_N = 1048576; // 2^20
|
|
70
|
+
const MAX_R = 32;
|
|
71
|
+
const MAX_P = 16;
|
|
72
|
+
export default class PasswordHash {
|
|
73
|
+
/**
|
|
74
|
+
* A fresh, cryptographically random salt: 32 bytes as 64 hex characters.
|
|
75
|
+
*
|
|
76
|
+
* A salt is not a secret — its one job is to be UNIQUE per user, so that no
|
|
77
|
+
* precomputed table and no cracked password is ever worth anything against
|
|
78
|
+
* a second account.
|
|
79
|
+
*/
|
|
80
|
+
static generateSalt() {
|
|
81
|
+
return crypto.randomBytes(32).toString("hex");
|
|
82
|
+
}
|
|
83
|
+
/** The `algorithm$params$` prefix that hashes written today carry. */
|
|
84
|
+
static getCurrentPrefix() {
|
|
85
|
+
return `${SCHEME_NAME}$${PasswordHash.encodeParams(CURRENT_PARAMS)}$`;
|
|
86
|
+
}
|
|
87
|
+
static getCurrentParams() {
|
|
88
|
+
return Object.assign({}, CURRENT_PARAMS);
|
|
89
|
+
}
|
|
90
|
+
static encodeParams(params) {
|
|
91
|
+
return `N=${params.N},r=${params.r},p=${params.p}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Pull the parameters and derived key back out of a stored value.
|
|
95
|
+
*
|
|
96
|
+
* Returns null for anything that is not one of our scrypt hashes — which
|
|
97
|
+
* includes every password written before this scheme existed. Callers use
|
|
98
|
+
* null to mean "fall back to the legacy comparison", so this must never
|
|
99
|
+
* throw on unrecognised input.
|
|
100
|
+
*/
|
|
101
|
+
static parse(storedValue) {
|
|
102
|
+
const parts = storedValue.split("$");
|
|
103
|
+
if (parts.length !== 3 || parts[0] !== SCHEME_NAME) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const match = parts[1].match(/^N=(\d+),r=(\d+),p=(\d+)$/);
|
|
107
|
+
if (!match) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const params = {
|
|
111
|
+
N: Number(match[1]),
|
|
112
|
+
r: Number(match[2]),
|
|
113
|
+
p: Number(match[3]),
|
|
114
|
+
};
|
|
115
|
+
const isSane = params.N > 1 &&
|
|
116
|
+
params.N <= MAX_N &&
|
|
117
|
+
(params.N & (params.N - 1)) === 0 && // scrypt requires a power of two
|
|
118
|
+
params.r > 0 &&
|
|
119
|
+
params.r <= MAX_R &&
|
|
120
|
+
params.p > 0 &&
|
|
121
|
+
params.p <= MAX_P;
|
|
122
|
+
if (!isSane || !parts[2]) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return { params: params, derivedKey: parts[2] };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The pepper step. Keyed with the instance's EncryptionSecret, so the
|
|
129
|
+
* database alone cannot be attacked offline.
|
|
130
|
+
*/
|
|
131
|
+
static applyPepper(plainValue) {
|
|
132
|
+
return crypto
|
|
133
|
+
.createHmac("sha256", EncryptionSecret.toString())
|
|
134
|
+
.update(plainValue, "utf8")
|
|
135
|
+
.digest();
|
|
136
|
+
}
|
|
137
|
+
static derive(data) {
|
|
138
|
+
const { N, r, p } = data.params;
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
crypto.scrypt(PasswordHash.applyPepper(data.plainValue), data.salt, KEY_LENGTH_IN_BYTES, {
|
|
141
|
+
N: N,
|
|
142
|
+
r: r,
|
|
143
|
+
p: p,
|
|
144
|
+
/*
|
|
145
|
+
* Node rejects the call outright when 128 * N * r exceeds
|
|
146
|
+
* maxmem, and its default is a flat 32 MiB. Scale the ceiling
|
|
147
|
+
* with the parameters instead, so raising N does not silently
|
|
148
|
+
* become "Invalid scrypt params" at runtime.
|
|
149
|
+
*/
|
|
150
|
+
maxmem: 128 * N * r * 2,
|
|
151
|
+
}, (error, derivedKey) => {
|
|
152
|
+
if (error) {
|
|
153
|
+
return reject(error);
|
|
154
|
+
}
|
|
155
|
+
return resolve(derivedKey.toString("hex"));
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Hash a password for storage, at today's cost parameters.
|
|
161
|
+
*
|
|
162
|
+
* The returned string records the algorithm and the parameters used, so it
|
|
163
|
+
* stays verifiable after those parameters change.
|
|
164
|
+
*/
|
|
165
|
+
static async hash(data) {
|
|
166
|
+
if (!data.plainValue) {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
if (!data.salt) {
|
|
170
|
+
throw new BadDataException("A salt is required to hash a password.");
|
|
171
|
+
}
|
|
172
|
+
const derivedKey = await PasswordHash.derive({
|
|
173
|
+
plainValue: data.plainValue,
|
|
174
|
+
salt: data.salt,
|
|
175
|
+
params: CURRENT_PARAMS,
|
|
176
|
+
});
|
|
177
|
+
return `${PasswordHash.getCurrentPrefix()}${derivedKey}`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check a password against a stored value, whichever scheme wrote it.
|
|
181
|
+
*
|
|
182
|
+
* Three schemes are in circulation and all three have to keep working, or
|
|
183
|
+
* the change that introduced the next one locks people out:
|
|
184
|
+
*
|
|
185
|
+
* scrypt$... this one. Verified at the parameters recorded in the hash.
|
|
186
|
+
* salted SHA `SHA256("v2:" + salt + ":" + secret + ":" + password)`.
|
|
187
|
+
* bare SHA `SHA256(secret + password)`, from before salts existed.
|
|
188
|
+
*
|
|
189
|
+
* The last two are told apart by whether the row has a salt, which is the
|
|
190
|
+
* same rule they were written under. Anything not carrying a recognised
|
|
191
|
+
* scheme prefix is one of them.
|
|
192
|
+
*/
|
|
193
|
+
static async verify(data) {
|
|
194
|
+
if (!data.plainValue || !data.storedValue) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const parsed = PasswordHash.parse(data.storedValue);
|
|
198
|
+
if (!parsed) {
|
|
199
|
+
return await HashedString.verifyValue({
|
|
200
|
+
plainValue: data.plainValue,
|
|
201
|
+
hashedValue: data.storedValue,
|
|
202
|
+
encryptionSecret: EncryptionSecret,
|
|
203
|
+
salt: data.salt || null,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/*
|
|
207
|
+
* A scrypt hash is meaningless without the salt it was derived with.
|
|
208
|
+
* Reaching here without one means the caller did not select the salt
|
|
209
|
+
* column, which would otherwise look exactly like a wrong password.
|
|
210
|
+
*/
|
|
211
|
+
if (!data.salt) {
|
|
212
|
+
throw new BadDataException("Cannot verify a salted password hash without its salt. Select the salt column alongside the hash.");
|
|
213
|
+
}
|
|
214
|
+
const derivedKey = await PasswordHash.derive({
|
|
215
|
+
plainValue: data.plainValue,
|
|
216
|
+
salt: data.salt,
|
|
217
|
+
params: parsed.params,
|
|
218
|
+
});
|
|
219
|
+
return HashedString.isEqual(derivedKey, parsed.derivedKey);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Should this stored value be re-hashed the next time the password is
|
|
223
|
+
* known to be correct?
|
|
224
|
+
*
|
|
225
|
+
* True for every hash that is not exactly what `hash()` writes today: the
|
|
226
|
+
* two SHA-256 schemes that came before, and any scrypt hash carrying
|
|
227
|
+
* parameters that are no longer current. This is the whole upgrade
|
|
228
|
+
* mechanism — raising the cost above is enough to start migrating users.
|
|
229
|
+
*/
|
|
230
|
+
static needsUpgrade(storedValue) {
|
|
231
|
+
if (!storedValue) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
return !storedValue.startsWith(PasswordHash.getCurrentPrefix());
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=PasswordHash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordHash.js","sourceRoot":"","sources":["../../../../Server/Utils/PasswordHash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,gBAAgB,MAAM,wCAAwC,CAAC;AACtE,OAAO,YAAY,MAAM,0BAA0B,CAAC;AACpD,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAiB;IACnC,CAAC,EAAE,KAAK;IACR,CAAC,EAAE,CAAC;IACJ,CAAC,EAAE,CAAC;CACL,CAAC;AAEF,qEAAqE;AACrE,MAAM,mBAAmB,GAAW,EAAE,CAAC;AAEvC,MAAM,WAAW,GAAW,QAAQ,CAAC;AAErC;;;;GAIG;AACH,MAAM,KAAK,GAAW,OAAO,CAAC,CAAC,OAAO;AACtC,MAAM,KAAK,GAAW,EAAE,CAAC;AACzB,MAAM,KAAK,GAAW,EAAE,CAAC;AAazB,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B;;;;;;OAMG;IACI,MAAM,CAAC,YAAY;QACxB,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,sEAAsE;IAC/D,MAAM,CAAC,gBAAgB;QAC5B,OAAO,GAAG,WAAW,IAAI,YAAY,CAAC,YAAY,CAAC,cAAc,CAAC,GAAG,CAAC;IACxE,CAAC;IAEM,MAAM,CAAC,gBAAgB;QAC5B,yBAAY,cAAc,EAAG;IAC/B,CAAC;IAEO,MAAM,CAAC,YAAY,CAAC,MAAoB;QAC9C,OAAO,KAAK,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC;IACrD,CAAC;IAED;;;;;;;OAOG;IACK,MAAM,CAAC,KAAK,CAAC,WAAmB;QACtC,MAAM,KAAK,GAAkB,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEpD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAA6B,KAAK,CAAC,CAAC,CAAY,CAAC,KAAK,CAC/D,2BAA2B,CAC5B,CAAC;QAEF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAiB;YAC3B,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnB,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnB,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACpB,CAAC;QAEF,MAAM,MAAM,GACV,MAAM,CAAC,CAAC,GAAG,CAAC;YACZ,MAAM,CAAC,CAAC,IAAI,KAAK;YACjB,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,iCAAiC;YACtE,MAAM,CAAC,CAAC,GAAG,CAAC;YACZ,MAAM,CAAC,CAAC,IAAI,KAAK;YACjB,MAAM,CAAC,CAAC,GAAG,CAAC;YACZ,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC;QAEpB,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,WAAW,CAAC,UAAkB;QAC3C,OAAO,MAAM;aACV,UAAU,CAAC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC;aACjD,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC;aAC1B,MAAM,EAAE,CAAC;IACd,CAAC;IAEO,MAAM,CAAC,MAAM,CAAC,IAIrB;QACC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAEhC,OAAO,IAAI,OAAO,CAChB,CAAC,OAAgC,EAAE,MAA8B,EAAE,EAAE;YACnE,MAAM,CAAC,MAAM,CACX,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EACzC,IAAI,CAAC,IAAI,EACT,mBAAmB,EACnB;gBACE,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC;gBACJ;;;;;mBAKG;gBACH,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;aACxB,EACD,CAAC,KAAmB,EAAE,UAAkB,EAAQ,EAAE;gBAChD,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvB,CAAC;gBAED,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7C,CAAC,CACF,CAAC;QACJ,CAAC,CACF,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAGxB;QACC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,IAAI,gBAAgB,CAAC,wCAAwC,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,UAAU,GAAW,MAAM,YAAY,CAAC,MAAM,CAAC;YACnD,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QAEH,OAAO,GAAG,YAAY,CAAC,gBAAgB,EAAE,GAAG,UAAU,EAAE,CAAC;IAC3D,CAAC;IAED;;;;;;;;;;;;;OAaG;IACI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAI1B;QACC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,MAAM,GAA8B,YAAY,CAAC,KAAK,CAC1D,IAAI,CAAC,WAAW,CACjB,CAAC;QAEF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,MAAM,YAAY,CAAC,WAAW,CAAC;gBACpC,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,gBAAgB,EAAE,gBAAgB;gBAClC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;aACxB,CAAC,CAAC;QACL,CAAC;QAED;;;;WAIG;QACH,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,IAAI,gBAAgB,CACxB,mGAAmG,CACpG,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAW,MAAM,YAAY,CAAC,MAAM,CAAC;YACnD,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;QAEH,OAAO,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7D,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,YAAY,CAAC,WAAmB;QAC5C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAClE,CAAC;CACF"}
|
|
@@ -12,7 +12,15 @@ var ColumnLength;
|
|
|
12
12
|
ColumnLength[ColumnLength["Password"] = 500] = "Password";
|
|
13
13
|
ColumnLength[ColumnLength["ShortURL"] = 100] = "ShortURL";
|
|
14
14
|
ColumnLength[ColumnLength["ShortText"] = 100] = "ShortText";
|
|
15
|
-
|
|
15
|
+
/*
|
|
16
|
+
* Wide enough for a self-describing password hash, not just a bare digest.
|
|
17
|
+
* User passwords are stored as `scrypt$N=...,r=...,p=...$<64 hex>` (~90
|
|
18
|
+
* characters) so the cost parameters travel with the hash and can be
|
|
19
|
+
* raised later without a migration. The columns that still store a plain
|
|
20
|
+
* SHA-256 digest — session refresh tokens, master passwords — simply do
|
|
21
|
+
* not use the extra room.
|
|
22
|
+
*/
|
|
23
|
+
ColumnLength[ColumnLength["HashedString"] = 255] = "HashedString";
|
|
16
24
|
ColumnLength[ColumnLength["Phone"] = 30] = "Phone";
|
|
17
25
|
ColumnLength[ColumnLength["OTP"] = 8] = "OTP";
|
|
18
26
|
})(ColumnLength || (ColumnLength = {}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ColumnLength.js","sourceRoot":"","sources":["../../../../Types/Database/ColumnLength.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAEhD,IAAK,
|
|
1
|
+
{"version":3,"file":"ColumnLength.js","sourceRoot":"","sources":["../../../../Types/Database/ColumnLength.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAEhD,IAAK,YAuBJ;AAvBD,WAAK,YAAY;IACf,sDAAY,CAAA;IACZ,iDAAU,CAAA;IACV,mDAAW,CAAA;IACX,qDAAY,CAAA;IACZ,kDAAU,CAAA;IACV,gDAAS,CAAA;IACT,+DAAiB,CAAA;IACjB,yDAAc,CAAA;IACd,yDAAc,CAAA;IACd,yDAAc,CAAA;IACd,2DAAe,CAAA;IACf;;;;;;;OAOG;IACH,iEAAkB,CAAA;IAClB,kDAAU,CAAA;IACV,6CAAO,CAAA;AACT,CAAC,EAvBI,YAAY,KAAZ,YAAY,QAuBhB;AAMD,MAAM,CAAC,MAAM,+BAA+B,GAC1C,CAAC,IAAqB,EAAsB,EAAE;IAC5C,IAAI,IAAI,KAAK,eAAe,CAAC,OAAO,EAAE,CAAC;QACrC,OAAO,YAAY,CAAC,OAAO,CAAC;IAC9B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,IAAI,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,IAAI,CAAC;IAC3B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,YAAY,CAAC,KAAK,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,MAAM,EAAE,CAAC;QACpC,OAAO,YAAY,CAAC,MAAM,CAAC;IAC7B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,YAAY,CAAC,KAAK,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,IAAI,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,IAAI,CAAC;IAC3B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,WAAW,EAAE,CAAC;QACzC,OAAO,YAAY,CAAC,WAAW,CAAC;IAClC,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC,QAAQ,CAAC;IAC/B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC,QAAQ,CAAC;IAC/B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC,QAAQ,CAAC;IAC/B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,SAAS,EAAE,CAAC;QACvC,OAAO,YAAY,CAAC,SAAS,CAAC;IAChC,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,YAAY,EAAE,CAAC;QAC1C,OAAO,YAAY,CAAC,YAAY,CAAC;IACnC,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,YAAY,CAAC,KAAK,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,CAAC,GAAG,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,GAAG,CAAC;IAC1B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEJ,eAAe,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TableColumn.js","sourceRoot":"","sources":["../../../../Types/Database/TableColumn.ts"],"names":[],"mappings":"AAKA,OAAO,kBAAkB,CAAC;AAE1B,MAAM,WAAW,GAAW,MAAM,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"TableColumn.js","sourceRoot":"","sources":["../../../../Types/Database/TableColumn.ts"],"names":[],"mappings":"AAKA,OAAO,kBAAkB,CAAC;AAE1B,MAAM,WAAW,GAAW,MAAM,CAAC,aAAa,CAAC,CAAC;AAkClD,eAAe,CAAC,KAA0B,EAA0B,EAAE;IACpE,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,cAAc,GAA2B,CACpD,MAAS,EACT,WAAmB,EACE,EAAE;IACvB,OAAO,OAAO,CAAC,WAAW,CACxB,WAAW,EACX,MAAM,EACN,WAAW,CACW,CAAC;AAC3B,CAAC,CAAC;AAMF,MAAM,CAAC,MAAM,eAAe,GAA4B,CACtD,MAAS,EACwB,EAAE;IACnC,MAAM,SAAS,GAAoC,EAAE,CAAC;IACtD,MAAM,IAAI,GAAkB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YAClD,SAAS,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,WAAW,CAClC,WAAW,EACX,MAAM,EACN,GAAG,CACmB,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Options for an index whose definition TypeORM cannot express, and which must
|
|
3
|
+
* therefore be owned by a migration rather than by the schema builder.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
*
|
|
7
|
+
* TypeORM's schema builder matches database indexes to entity metadata BY NAME
|
|
8
|
+
* (RdbmsSchemaBuilder.dropOldIndices: `metadata.indices.find((i) => i.name ===
|
|
9
|
+
* tableIndex.name)`) and DROPS every index it cannot find a match for. An index
|
|
10
|
+
* created only in a migration is invisible to entity metadata, so the next
|
|
11
|
+
* `npm run generate-postgres-migration` emits a DROP for it — silently, mixed
|
|
12
|
+
* in with whatever change was actually being made.
|
|
13
|
+
*
|
|
14
|
+
* That is not hypothetical. An audit of every hand-named index ever created by
|
|
15
|
+
* a migration against a fully-migrated database found ten that had been deleted
|
|
16
|
+
* this way and never restored, including the unique guards on six `slug`
|
|
17
|
+
* columns and the LOWER(name) index serving the telemetry ingest hot path.
|
|
18
|
+
* Migration 1775735059360 is a representative example: two unrelated column
|
|
19
|
+
* default changes, plus a DROP of an expression index, plus a down() that
|
|
20
|
+
* "restores" it without the expression.
|
|
21
|
+
*
|
|
22
|
+
* HOW THIS FIXES IT
|
|
23
|
+
*
|
|
24
|
+
* Declaring the index on the entity BY NAME with these options makes the schema
|
|
25
|
+
* builder skip it in both directions: `dropOldIndices` returns early on
|
|
26
|
+
* `synchronize === false`, and `createNewIndices` only considers indexes with
|
|
27
|
+
* `synchronize === true`. The migration stays the single source of truth for
|
|
28
|
+
* the real definition, and the declaration exists purely so TypeORM leaves it
|
|
29
|
+
* alone.
|
|
30
|
+
*
|
|
31
|
+
* The columns passed alongside these options are documentation — the schema
|
|
32
|
+
* builder never reads them for an unsynchronized index. Where the real
|
|
33
|
+
* definition is partial or an expression, say so in a comment at the call site.
|
|
34
|
+
*
|
|
35
|
+
* THE CAST
|
|
36
|
+
*
|
|
37
|
+
* `synchronize` is a real, supported option: the @Index decorator forwards it
|
|
38
|
+
* into IndexMetadataArgs (typeorm/decorator/Index.js), IndexMetadataArgs
|
|
39
|
+
* declares it, and RdbmsSchemaBuilder reads it. Only the public IndexOptions
|
|
40
|
+
* type omits it — a gap in TypeORM's own typings rather than a misuse.
|
|
41
|
+
*/
|
|
42
|
+
const UNSYNCHRONIZED_INDEX = {
|
|
43
|
+
synchronize: false,
|
|
44
|
+
};
|
|
45
|
+
export default UNSYNCHRONIZED_INDEX;
|
|
46
|
+
//# sourceMappingURL=UnsynchronizedIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnsynchronizedIndex.js","sourceRoot":"","sources":["../../../../Types/Database/UnsynchronizedIndex.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,oBAAoB,GAAiB;IACzC,WAAW,EAAE,KAAK;CACH,CAAC;AAElB,eAAe,oBAAoB,CAAC"}
|
|
@@ -4,6 +4,28 @@ import BadDataException from "./Exception/BadDataException";
|
|
|
4
4
|
import BadOperationException from "./Exception/BadOperationException";
|
|
5
5
|
import { ObjectType } from "./JSON";
|
|
6
6
|
import CryptoJS from "crypto-js";
|
|
7
|
+
/*
|
|
8
|
+
* SHA-256 hashing for values that are ALREADY high-entropy: session refresh
|
|
9
|
+
* tokens, email verification tokens, reset tokens, permission fingerprints.
|
|
10
|
+
* Those are random values the server minted, so there is nothing to guess and
|
|
11
|
+
* a fast hash is the right tool — it also keeps them searchable by hash,
|
|
12
|
+
* which is how they are looked up.
|
|
13
|
+
*
|
|
14
|
+
* User passwords are NOT hashed here any more. A password is low-entropy and
|
|
15
|
+
* guessable, so it goes through scrypt in Common/Server/Utils/PasswordHash,
|
|
16
|
+
* which is deliberately slow and memory-hard. What remains in this class is
|
|
17
|
+
* the two SHA-256 schemes passwords used to be stored under, kept so those
|
|
18
|
+
* rows still verify (and are then upgraded) at their owner's next login:
|
|
19
|
+
*
|
|
20
|
+
* no salt `SHA256(secret + value)`, from before per-user salts existed.
|
|
21
|
+
* salted `SHA256("v2:" + salt + ":" + secret + ":" + value)`.
|
|
22
|
+
*
|
|
23
|
+
* Neither records its scheme in the digest — it is a bare hex SHA-256 either
|
|
24
|
+
* way — so they are told apart by whether the row carries a salt. The "v2"
|
|
25
|
+
* prefix and the separators exist for domain separation: without them a
|
|
26
|
+
* crafted salt could make a salted input collide with an unsalted one.
|
|
27
|
+
*/
|
|
28
|
+
const SALTED_HASH_SCHEME_VERSION = "v2";
|
|
7
29
|
export default class HashedString extends DatabaseProperty {
|
|
8
30
|
get value() {
|
|
9
31
|
return this._value;
|
|
@@ -48,21 +70,89 @@ export default class HashedString extends DatabaseProperty {
|
|
|
48
70
|
isValueHashed() {
|
|
49
71
|
return this.isHashed;
|
|
50
72
|
}
|
|
51
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Adopt an already-computed digest.
|
|
75
|
+
*
|
|
76
|
+
* For hashes this class does not compute itself — user passwords go
|
|
77
|
+
* through scrypt in Common/Server/Utils/PasswordHash, which is server-only
|
|
78
|
+
* because this type is also bundled into the browser. The guard is the
|
|
79
|
+
* same one hashValue() carries: a value is hashed once and never re-hashed
|
|
80
|
+
* over the top of itself.
|
|
81
|
+
*/
|
|
82
|
+
setHashedValue(hashedValue) {
|
|
83
|
+
if (this.isHashed) {
|
|
84
|
+
throw new BadOperationException("Value is already hashed");
|
|
85
|
+
}
|
|
86
|
+
this.isHashed = true;
|
|
87
|
+
this.value = hashedValue;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The exact byte string fed to SHA-256.
|
|
91
|
+
*
|
|
92
|
+
* The unsalted branch MUST stay byte-for-byte identical forever: every
|
|
93
|
+
* password hashed before per-user salts existed is verified through it.
|
|
94
|
+
*/
|
|
95
|
+
static buildValueToHash(value, encryptionSecret, salt) {
|
|
96
|
+
if (!salt) {
|
|
97
|
+
return (encryptionSecret || "") + value;
|
|
98
|
+
}
|
|
99
|
+
return `${SALTED_HASH_SCHEME_VERSION}:${salt}:${encryptionSecret || ""}:${value}`;
|
|
100
|
+
}
|
|
101
|
+
static computeHash(value, encryptionSecret, salt) {
|
|
102
|
+
if (!value) {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
return CryptoJS.SHA256(HashedString.buildValueToHash(value, encryptionSecret, salt || null)).toString();
|
|
106
|
+
}
|
|
107
|
+
async hashValue(encryptionSecret, salt) {
|
|
52
108
|
if (!this.value) {
|
|
53
109
|
return "";
|
|
54
110
|
}
|
|
55
111
|
if (this.isHashed) {
|
|
56
112
|
throw new BadOperationException("Value is already hashed");
|
|
57
113
|
}
|
|
58
|
-
const valueToHash = (encryptionSecret || "") + this.value;
|
|
59
114
|
this.isHashed = true;
|
|
60
|
-
this.value =
|
|
115
|
+
this.value = HashedString.computeHash(this.value, encryptionSecret, salt || null);
|
|
61
116
|
return this.value;
|
|
62
117
|
}
|
|
63
|
-
static async hashValue(value, encryptionSecret) {
|
|
118
|
+
static async hashValue(value, encryptionSecret, salt) {
|
|
64
119
|
const hashstring = new HashedString(value, false);
|
|
65
|
-
return await hashstring.hashValue(encryptionSecret);
|
|
120
|
+
return await hashstring.hashValue(encryptionSecret, salt || null);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Compare two already-hashed values without leaking, through timing, how
|
|
124
|
+
* many leading characters matched. Comparing digests with `===` lets an
|
|
125
|
+
* attacker who can measure response time recover a stored hash one
|
|
126
|
+
* character at a time; recovering the hash is what makes offline cracking
|
|
127
|
+
* possible in the first place.
|
|
128
|
+
*
|
|
129
|
+
* Lengths are compared up front. Digests here are always the same length,
|
|
130
|
+
* so an early exit only happens on malformed input, where the length is
|
|
131
|
+
* not the secret.
|
|
132
|
+
*/
|
|
133
|
+
static isEqual(a, b) {
|
|
134
|
+
if (a.length !== b.length) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
let difference = 0;
|
|
138
|
+
for (let i = 0; i < a.length; i++) {
|
|
139
|
+
difference |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
140
|
+
}
|
|
141
|
+
return difference === 0;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Verify a plaintext value against a stored hash.
|
|
145
|
+
*
|
|
146
|
+
* Pass the salt stored alongside the hash. Pass null/undefined for records
|
|
147
|
+
* written before per-user salts existed — those verify against the legacy
|
|
148
|
+
* unsalted scheme, and the caller should re-hash them with a fresh salt
|
|
149
|
+
* once the value is known to be correct.
|
|
150
|
+
*/
|
|
151
|
+
static async verifyValue(data) {
|
|
152
|
+
if (!data.plainValue || !data.hashedValue) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return HashedString.isEqual(HashedString.computeHash(data.plainValue, data.encryptionSecret, data.salt || null), data.hashedValue);
|
|
66
156
|
}
|
|
67
157
|
static fromDatabase(_value) {
|
|
68
158
|
if (_value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HashedString.js","sourceRoot":"","sources":["../../../Types/HashedString.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,eAAe,CAAC;AACjC,OAAO,gBAAgB,MAAM,6BAA6B,CAAC;AAC3D,OAAO,gBAAgB,MAAM,8BAA8B,CAAC;AAC5D,OAAO,qBAAqB,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAc,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,QAAQ,MAAM,WAAW,CAAC;AAGjC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,gBAAgB;IAIxD,IAAW,KAAK;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IACD,IAAW,KAAK,CAAC,CAAS;QACxB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,CAAC;IAED,YAAmB,KAAa,EAAE,gBAAyB,KAAK;QAC9D,KAAK,EAAE,CAAC;QAXF,aAAQ,GAAY,KAAK,CAAC;QAE1B,WAAM,GAAW,EAAE,CAAC;QAU1B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,aAAa,CAAC;IAChC,CAAC;IAEe,MAAM;QACpB,OAAO;YACL,KAAK,EAAE,UAAU,CAAC,YAAY;YAC9B,KAAK,EAAG,IAAqB,CAAC,QAAQ,EAAE;SACzC,CAAC;IACJ,CAAC;IAEM,MAAM,CAAU,QAAQ,CAAC,IAAgB;QAC9C,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,YAAY,EAAE,CAAC;YAC9C,OAAO,IAAI,YAAY,CAAE,IAAI,CAAC,OAAO,CAAY,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,IAAI,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,CAAC;IAEe,QAAQ;QACtB,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAEM,MAAM,CAAC,QAAQ;QACpB,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnC,CAAC;IAES,MAAM,CAAU,UAAU,CAClC,KAAgD;QAEhD,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;YAED,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,aAAa;QAClB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"HashedString.js","sourceRoot":"","sources":["../../../Types/HashedString.ts"],"names":[],"mappings":"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"}
|