@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.
Files changed (173) hide show
  1. package/Models/DatabaseModels/CephCluster.ts +14 -0
  2. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  3. package/Models/DatabaseModels/DockerHost.ts +14 -0
  4. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  5. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  6. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  7. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  8. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  9. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  10. package/Models/DatabaseModels/Project.ts +16 -2
  11. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  12. package/Models/DatabaseModels/Service.ts +30 -0
  13. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  14. package/Models/DatabaseModels/User.ts +30 -0
  15. package/Server/API/AIAgentDataAPI.ts +31 -0
  16. package/Server/API/DashboardAPI.ts +419 -12
  17. package/Server/API/GitHubAPI.ts +119 -283
  18. package/Server/API/UserAPI.ts +263 -1
  19. package/Server/EnvironmentConfig.ts +7 -3
  20. package/Server/Infrastructure/GlobalCache.ts +37 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  27. package/Server/Services/AnalyticsDatabaseService.ts +72 -1
  28. package/Server/Services/CodeRepositoryService.ts +105 -2
  29. package/Server/Services/DatabaseService.ts +185 -2
  30. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  31. package/Server/Services/UserService.ts +184 -0
  32. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  33. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  34. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  35. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  36. package/Server/Utils/PasswordHash.ts +306 -0
  37. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  38. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  39. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  40. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  41. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  42. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  43. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  44. package/Tests/Server/API/Helpers.ts +6 -1
  45. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  46. package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
  47. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  48. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  49. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  50. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
  51. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  52. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  53. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
  54. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  55. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  56. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  57. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  58. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  59. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  60. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  61. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  62. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  63. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  64. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  65. package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
  66. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  67. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  68. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  69. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  70. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  71. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  72. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  73. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  74. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  75. package/Types/Database/ColumnLength.ts +9 -1
  76. package/Types/Database/TableColumn.ts +11 -0
  77. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  78. package/Types/HashedString.ts +139 -4
  79. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  80. package/UI/Components/Navbar/NavBar.tsx +12 -2
  81. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  82. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  83. package/Utils/TeamMembersByProject.ts +237 -0
  84. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  85. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  86. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  87. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  88. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  89. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  91. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  93. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  95. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  97. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  99. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  101. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Project.js +16 -2
  103. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  105. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  107. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  109. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/User.js +32 -0
  111. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  112. package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
  113. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  114. package/build/dist/Server/API/DashboardAPI.js +304 -10
  115. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  116. package/build/dist/Server/API/GitHubAPI.js +94 -167
  117. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  118. package/build/dist/Server/API/UserAPI.js +187 -2
  119. package/build/dist/Server/API/UserAPI.js.map +1 -1
  120. package/build/dist/Server/EnvironmentConfig.js +7 -3
  121. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  122. package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
  123. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  131. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  132. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  133. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  134. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  135. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  136. package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
  137. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  138. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  139. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  140. package/build/dist/Server/Services/DatabaseService.js +160 -2
  141. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  142. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  143. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  144. package/build/dist/Server/Services/UserService.js +165 -0
  145. package/build/dist/Server/Services/UserService.js.map +1 -1
  146. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  147. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  148. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  149. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  150. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  151. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  152. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  153. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  154. package/build/dist/Server/Utils/PasswordHash.js +237 -0
  155. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  156. package/build/dist/Types/Database/ColumnLength.js +9 -1
  157. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  158. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  159. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  160. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  161. package/build/dist/Types/HashedString.js +95 -5
  162. package/build/dist/Types/HashedString.js.map +1 -1
  163. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  164. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  165. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  166. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  167. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  168. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  169. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  170. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  171. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  172. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  173. 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":"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"}
@@ -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
- ColumnLength[ColumnLength["HashedString"] = 64] = "HashedString";
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,YAeJ;AAfD,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,gEAAiB,CAAA;IACjB,kDAAU,CAAA;IACV,6CAAO,CAAA;AACT,CAAC,EAfI,YAAY,KAAZ,YAAY,QAehB;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
+ {"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;AAuBlD,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"}
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
- async hashValue(encryptionSecret) {
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 = CryptoJS.SHA256(valueToHash).toString();
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":"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"}
1
+ {"version":3,"file":"HashedString.js","sourceRoot":"","sources":["../../../Types/HashedString.ts"],"names":[],"mappings":"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"}