@oneuptime/common 11.0.3 → 11.0.5
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/GlobalConfig.ts +19 -0
- package/Models/DatabaseModels/GlobalOidc.ts +351 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +265 -0
- package/Models/DatabaseModels/GlobalSso.ts +312 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +268 -0
- package/Models/DatabaseModels/Index.ts +8 -0
- package/Models/DatabaseModels/Project.ts +31 -0
- package/Models/DatabaseModels/StatusPage.ts +82 -0
- package/Server/API/StatusPageAPI.ts +2 -0
- package/Server/Infrastructure/GlobalCache.ts +11 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.ts → 1781750000000-MigrationName.ts} +2 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.ts +176 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.ts +299 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -2
- package/Server/Middleware/UserAuthorization.ts +113 -42
- package/Server/Services/CephClusterService.ts +22 -10
- package/Server/Services/CloudResourceService.ts +22 -10
- package/Server/Services/DockerHostService.ts +22 -10
- package/Server/Services/DockerSwarmClusterService.ts +22 -10
- package/Server/Services/GlobalConfigService.ts +50 -0
- package/Server/Services/GlobalOidcProjectService.ts +85 -0
- package/Server/Services/GlobalOidcService.ts +10 -0
- package/Server/Services/GlobalSsoProjectService.ts +85 -0
- package/Server/Services/GlobalSsoService.ts +10 -0
- package/Server/Services/HostService.ts +22 -10
- package/Server/Services/Index.ts +8 -0
- package/Server/Services/KubernetesClusterService.ts +22 -10
- package/Server/Services/PodmanHostService.ts +22 -10
- package/Server/Services/ProjectService.ts +44 -1
- package/Server/Services/ProxmoxClusterService.ts +22 -10
- package/Server/Services/RumApplicationService.ts +22 -10
- package/Server/Services/ServerlessFunctionService.ts +22 -10
- package/Server/Utils/Cookie.ts +39 -5
- package/Server/Utils/JsonWebToken.ts +7 -0
- package/Server/Utils/ValidateGlobalProviderProjectTeams.ts +119 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +43 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +51 -13
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +163 -0
- package/Tests/Server/Utils/CookieSSOToken.test.ts +130 -0
- package/Types/JsonWebTokenData.ts +3 -0
- package/Types/SSO/SsoProviderType.ts +8 -0
- package/UI/Components/Accordion/Accordion.tsx +5 -1
- package/UI/Components/CardSelect/CardSelect.tsx +6 -1
- package/UI/Components/CategoryCheckbox/Index.tsx +2 -1
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -8
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -8
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +4 -8
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +1 -1
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +1 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
- package/UI/Components/Charts/Line/LineChart.tsx +4 -8
- package/UI/Components/CodeEditor/CodeEditor.tsx +2 -0
- package/UI/Components/CollapsibleSection/CollapsibleSection.tsx +8 -1
- package/UI/Components/Dropdown/Dropdown.tsx +2 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +68 -16
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +3 -0
- package/UI/Components/FilePicker/FilePicker.tsx +2 -0
- package/UI/Components/Forms/Fields/ColorPicker.tsx +2 -0
- package/UI/Components/Forms/Fields/FieldLabel.tsx +4 -0
- package/UI/Components/Forms/Fields/FormField.tsx +72 -15
- package/UI/Components/Forms/Fields/IconPicker.tsx +2 -0
- package/UI/Components/Forms/Validation.ts +107 -23
- package/UI/Components/Input/Input.tsx +4 -0
- package/UI/Components/Link/Link.tsx +23 -0
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +3 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +63 -2
- package/UI/Components/Modal/Modal.tsx +4 -1
- package/UI/Components/Radio/Radio.tsx +2 -0
- package/UI/Components/RadioButtons/GroupRadioButtons.tsx +6 -1
- package/UI/Components/Tabs/Tabs.tsx +63 -0
- package/UI/Components/TextArea/TextArea.tsx +2 -0
- package/UI/Components/TimePicker/TimePicker.tsx +2 -0
- package/UI/Components/Toggle/Toggle.tsx +2 -1
- package/UI/Components/Tooltip/Tooltip.tsx +6 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +20 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +379 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +276 -0
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js +341 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +279 -0
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +8 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +32 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +84 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +2 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +13 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.js → 1781750000000-MigrationName.js} +3 -3
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.js.map → 1781750000000-MigrationName.js.map} +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.js +73 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +77 -34
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +19 -2
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +19 -2
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -2
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +19 -2
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +55 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +80 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -0
- package/build/dist/Server/Services/GlobalOidcService.js +9 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js +80 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -0
- package/build/dist/Server/Services/GlobalSsoService.js +9 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -0
- package/build/dist/Server/Services/HostService.js +19 -2
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +8 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +19 -2
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -2
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +36 -1
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +19 -2
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -2
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +19 -2
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Utils/Cookie.js +32 -3
- package/build/dist/Server/Utils/Cookie.js.map +1 -1
- package/build/dist/Server/Utils/JsonWebToken.js +6 -0
- package/build/dist/Server/Utils/JsonWebToken.js.map +1 -1
- package/build/dist/Server/Utils/ValidateGlobalProviderProjectTeams.js +66 -0
- package/build/dist/Server/Utils/ValidateGlobalProviderProjectTeams.js.map +1 -0
- package/build/dist/Types/SSO/SsoProviderType.js +9 -0
- package/build/dist/Types/SSO/SsoProviderType.js.map +1 -0
- package/build/dist/UI/Components/Accordion/Accordion.js +5 -3
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js +1 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/CategoryCheckbox/Index.js +1 -1
- package/build/dist/UI/Components/CategoryCheckbox/Index.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -6
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -6
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +8 -8
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -6
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CollapsibleSection/CollapsibleSection.js +4 -2
- package/build/dist/UI/Components/CollapsibleSection/CollapsibleSection.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +31 -5
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +2 -2
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/FilePicker/FilePicker.js +1 -1
- package/build/dist/UI/Components/FilePicker/FilePicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +58 -22
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +64 -15
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/Link/Link.js +22 -1
- package/build/dist/UI/Components/Link/Link.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +2 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +46 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +1 -1
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Radio/Radio.js +1 -1
- package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/GroupRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/GroupRadioButtons.js.map +1 -1
- package/build/dist/UI/Components/Tabs/Tabs.js +50 -1
- package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/TimePicker/TimePicker.js +1 -1
- package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +6 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/package.json +1 -1
|
@@ -192,21 +192,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
192
192
|
)
|
|
193
193
|
.digest("hex");
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
cacheKey
|
|
198
|
-
|
|
195
|
+
let cached: string | null = null;
|
|
196
|
+
try {
|
|
197
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
198
|
+
} catch {
|
|
199
|
+
/*
|
|
200
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
201
|
+
* cache error must never skip the DB write below, otherwise the
|
|
202
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
203
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
204
|
+
*/
|
|
205
|
+
cached = null;
|
|
206
|
+
}
|
|
199
207
|
|
|
200
208
|
if (cached === extrasFingerprint) {
|
|
201
209
|
return; // same data was written recently
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
try {
|
|
213
|
+
await GlobalCache.setString(
|
|
214
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
215
|
+
cacheKey,
|
|
216
|
+
extrasFingerprint,
|
|
217
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
218
|
+
);
|
|
219
|
+
} catch {
|
|
220
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
221
|
+
}
|
|
210
222
|
|
|
211
223
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
212
224
|
const data: any = {
|
|
@@ -202,21 +202,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
202
202
|
)
|
|
203
203
|
.digest("hex");
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
cacheKey
|
|
208
|
-
|
|
205
|
+
let cached: string | null = null;
|
|
206
|
+
try {
|
|
207
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
208
|
+
} catch {
|
|
209
|
+
/*
|
|
210
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
211
|
+
* cache error must never skip the DB write below, otherwise the
|
|
212
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
213
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
214
|
+
*/
|
|
215
|
+
cached = null;
|
|
216
|
+
}
|
|
209
217
|
|
|
210
218
|
if (cached === extrasFingerprint) {
|
|
211
219
|
return; // same data was written recently
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
try {
|
|
223
|
+
await GlobalCache.setString(
|
|
224
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
225
|
+
cacheKey,
|
|
226
|
+
extrasFingerprint,
|
|
227
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
228
|
+
);
|
|
229
|
+
} catch {
|
|
230
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
231
|
+
}
|
|
220
232
|
|
|
221
233
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
234
|
const data: any = {
|
|
@@ -1,10 +1,60 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import Model from "../../Models/DatabaseModels/GlobalConfig";
|
|
3
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
4
|
+
import ObjectID from "../../Types/ObjectID";
|
|
5
|
+
import { OnUpdate } from "../Types/Database/Hooks";
|
|
6
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
3
7
|
|
|
4
8
|
export class Service extends DatabaseService<Model> {
|
|
9
|
+
/*
|
|
10
|
+
* Caches the instance-wide "Require SSO for Login" flag. This is read on the
|
|
11
|
+
* authenticated request path (per project), so it must not hit Postgres every
|
|
12
|
+
* time. Refreshed at most once per 60s and invalidated on update below.
|
|
13
|
+
*/
|
|
14
|
+
private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
|
|
15
|
+
new InMemoryTTLCache(10_000);
|
|
16
|
+
|
|
5
17
|
public constructor() {
|
|
6
18
|
super(Model);
|
|
7
19
|
}
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Instance-wide: must every user sign in with SSO to access projects?
|
|
23
|
+
* (Master admins are exempted by the enforcement layer, not here.)
|
|
24
|
+
*/
|
|
25
|
+
@CaptureSpan()
|
|
26
|
+
public async getRequireSsoForLogin(): Promise<boolean> {
|
|
27
|
+
const key: string = "global";
|
|
28
|
+
const cached: boolean | undefined = this.requireSsoForLoginCache.get(key);
|
|
29
|
+
if (cached !== undefined) {
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const config: Model | null = await this.findOneBy({
|
|
34
|
+
query: {},
|
|
35
|
+
select: { requireSsoForLogin: true },
|
|
36
|
+
props: { isRoot: true },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const value: boolean = Boolean(config?.requireSsoForLogin);
|
|
40
|
+
this.requireSsoForLoginCache.set(key, value, 60_000);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@CaptureSpan()
|
|
45
|
+
protected override async onUpdateSuccess(
|
|
46
|
+
onUpdate: OnUpdate<Model>,
|
|
47
|
+
_updatedItemIds: Array<ObjectID>,
|
|
48
|
+
): Promise<OnUpdate<Model>> {
|
|
49
|
+
if (
|
|
50
|
+
(onUpdate.updateBy.data as { requireSsoForLogin?: unknown })
|
|
51
|
+
.requireSsoForLogin !== undefined
|
|
52
|
+
) {
|
|
53
|
+
this.requireSsoForLoginCache.clear();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return onUpdate;
|
|
57
|
+
}
|
|
8
58
|
}
|
|
9
59
|
|
|
10
60
|
export default new Service();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/GlobalOidcProject";
|
|
3
|
+
import Team from "../../Models/DatabaseModels/Team";
|
|
4
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
5
|
+
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
7
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
8
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
9
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
10
|
+
import validateGlobalProviderProjectTeams, {
|
|
11
|
+
resolveAttachmentProjectId,
|
|
12
|
+
} from "../Utils/ValidateGlobalProviderProjectTeams";
|
|
13
|
+
|
|
14
|
+
export class Service extends DatabaseService<Model> {
|
|
15
|
+
public constructor() {
|
|
16
|
+
super(Model);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@CaptureSpan()
|
|
20
|
+
protected override async onBeforeCreate(
|
|
21
|
+
createBy: CreateBy<Model>,
|
|
22
|
+
): Promise<OnCreate<Model>> {
|
|
23
|
+
/*
|
|
24
|
+
* The attach form submits the project via the `project` relation, so the
|
|
25
|
+
* `projectId` FK is not set yet. Resolve it and persist it (the column is
|
|
26
|
+
* required / NOT NULL) before validating the default teams against it.
|
|
27
|
+
*/
|
|
28
|
+
const projectId: ObjectID | undefined = resolveAttachmentProjectId(
|
|
29
|
+
createBy.data,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (projectId) {
|
|
33
|
+
createBy.data.projectId = projectId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await validateGlobalProviderProjectTeams({
|
|
37
|
+
teams: createBy.data.teams,
|
|
38
|
+
projectId,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { createBy, carryForward: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@CaptureSpan()
|
|
45
|
+
protected override async onBeforeUpdate(
|
|
46
|
+
updateBy: UpdateBy<Model>,
|
|
47
|
+
): Promise<OnUpdate<Model>> {
|
|
48
|
+
// `updateBy.data` is a partial-entity shape; narrow the relation/id here.
|
|
49
|
+
const teams: Array<Team> | undefined = updateBy.data.teams as unknown as
|
|
50
|
+
| Array<Team>
|
|
51
|
+
| undefined;
|
|
52
|
+
|
|
53
|
+
if (teams && teams.length > 0) {
|
|
54
|
+
const explicitProjectId: ObjectID | undefined = updateBy.data
|
|
55
|
+
.projectId as unknown as ObjectID | undefined;
|
|
56
|
+
|
|
57
|
+
if (explicitProjectId) {
|
|
58
|
+
await validateGlobalProviderProjectTeams({
|
|
59
|
+
teams,
|
|
60
|
+
projectId: explicitProjectId,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// projectId is immutable here; resolve it from the row(s) being updated.
|
|
64
|
+
const rows: Array<Model> = await this.findBy({
|
|
65
|
+
query: updateBy.query,
|
|
66
|
+
select: { _id: true, projectId: true },
|
|
67
|
+
limit: LIMIT_PER_PROJECT,
|
|
68
|
+
skip: 0,
|
|
69
|
+
props: { isRoot: true },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
await validateGlobalProviderProjectTeams({
|
|
74
|
+
teams,
|
|
75
|
+
projectId: row.projectId,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { updateBy, carryForward: null };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default new Service();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/GlobalSsoProject";
|
|
3
|
+
import Team from "../../Models/DatabaseModels/Team";
|
|
4
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
5
|
+
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
7
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
8
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
9
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
10
|
+
import validateGlobalProviderProjectTeams, {
|
|
11
|
+
resolveAttachmentProjectId,
|
|
12
|
+
} from "../Utils/ValidateGlobalProviderProjectTeams";
|
|
13
|
+
|
|
14
|
+
export class Service extends DatabaseService<Model> {
|
|
15
|
+
public constructor() {
|
|
16
|
+
super(Model);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@CaptureSpan()
|
|
20
|
+
protected override async onBeforeCreate(
|
|
21
|
+
createBy: CreateBy<Model>,
|
|
22
|
+
): Promise<OnCreate<Model>> {
|
|
23
|
+
/*
|
|
24
|
+
* The attach form submits the project via the `project` relation, so the
|
|
25
|
+
* `projectId` FK is not set yet. Resolve it and persist it (the column is
|
|
26
|
+
* required / NOT NULL) before validating the default teams against it.
|
|
27
|
+
*/
|
|
28
|
+
const projectId: ObjectID | undefined = resolveAttachmentProjectId(
|
|
29
|
+
createBy.data,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (projectId) {
|
|
33
|
+
createBy.data.projectId = projectId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await validateGlobalProviderProjectTeams({
|
|
37
|
+
teams: createBy.data.teams,
|
|
38
|
+
projectId,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { createBy, carryForward: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@CaptureSpan()
|
|
45
|
+
protected override async onBeforeUpdate(
|
|
46
|
+
updateBy: UpdateBy<Model>,
|
|
47
|
+
): Promise<OnUpdate<Model>> {
|
|
48
|
+
// `updateBy.data` is a partial-entity shape; narrow the relation/id here.
|
|
49
|
+
const teams: Array<Team> | undefined = updateBy.data.teams as unknown as
|
|
50
|
+
| Array<Team>
|
|
51
|
+
| undefined;
|
|
52
|
+
|
|
53
|
+
if (teams && teams.length > 0) {
|
|
54
|
+
const explicitProjectId: ObjectID | undefined = updateBy.data
|
|
55
|
+
.projectId as unknown as ObjectID | undefined;
|
|
56
|
+
|
|
57
|
+
if (explicitProjectId) {
|
|
58
|
+
await validateGlobalProviderProjectTeams({
|
|
59
|
+
teams,
|
|
60
|
+
projectId: explicitProjectId,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// projectId is immutable here; resolve it from the row(s) being updated.
|
|
64
|
+
const rows: Array<Model> = await this.findBy({
|
|
65
|
+
query: updateBy.query,
|
|
66
|
+
select: { _id: true, projectId: true },
|
|
67
|
+
limit: LIMIT_PER_PROJECT,
|
|
68
|
+
skip: 0,
|
|
69
|
+
props: { isRoot: true },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
await validateGlobalProviderProjectTeams({
|
|
74
|
+
teams,
|
|
75
|
+
projectId: row.projectId,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { updateBy, carryForward: null };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default new Service();
|
|
@@ -214,21 +214,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
214
214
|
*/
|
|
215
215
|
const cacheKey: string = hostId.toString();
|
|
216
216
|
const extrasFingerprint: string = this.fingerprintExtras(extra);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
cacheKey
|
|
220
|
-
|
|
217
|
+
let cached: string | null = null;
|
|
218
|
+
try {
|
|
219
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
220
|
+
} catch {
|
|
221
|
+
/*
|
|
222
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
223
|
+
* cache error must never skip the DB write below, otherwise the
|
|
224
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
225
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
226
|
+
*/
|
|
227
|
+
cached = null;
|
|
228
|
+
}
|
|
221
229
|
|
|
222
230
|
if (cached === extrasFingerprint) {
|
|
223
231
|
return; // same data was written recently
|
|
224
232
|
}
|
|
225
233
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
234
|
+
try {
|
|
235
|
+
await GlobalCache.setString(
|
|
236
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
237
|
+
cacheKey,
|
|
238
|
+
extrasFingerprint,
|
|
239
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
240
|
+
);
|
|
241
|
+
} catch {
|
|
242
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
243
|
+
}
|
|
232
244
|
|
|
233
245
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
246
|
const data: any = {
|
package/Server/Services/Index.ts
CHANGED
|
@@ -106,6 +106,10 @@ import ProfileSampleService from "./ProfileSampleService";
|
|
|
106
106
|
import ProjectSmtpConfigService from "./ProjectSmtpConfigService";
|
|
107
107
|
import ProjectSsoService from "./ProjectSsoService";
|
|
108
108
|
import ProjectOidcService from "./ProjectOidcService";
|
|
109
|
+
import GlobalSsoService from "./GlobalSsoService";
|
|
110
|
+
import GlobalOidcService from "./GlobalOidcService";
|
|
111
|
+
import GlobalSsoProjectService from "./GlobalSsoProjectService";
|
|
112
|
+
import GlobalOidcProjectService from "./GlobalOidcProjectService";
|
|
109
113
|
import PromoCodeService from "./PromoCodeService";
|
|
110
114
|
import EnterpriseLicenseService from "./EnterpriseLicenseService";
|
|
111
115
|
import OpenSourceDeploymentService from "./OpenSourceDeploymentService";
|
|
@@ -343,6 +347,10 @@ const services: Array<BaseService> = [
|
|
|
343
347
|
AIAgentTaskPullRequestService,
|
|
344
348
|
ProjectSsoService,
|
|
345
349
|
ProjectOidcService,
|
|
350
|
+
GlobalSsoService,
|
|
351
|
+
GlobalOidcService,
|
|
352
|
+
GlobalSsoProjectService,
|
|
353
|
+
GlobalOidcProjectService,
|
|
346
354
|
|
|
347
355
|
ScheduledMaintenanceCustomFieldService,
|
|
348
356
|
ScheduledMaintenanceInternalNoteService,
|
|
@@ -155,21 +155,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
155
155
|
)
|
|
156
156
|
.digest("hex");
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
cacheKey
|
|
161
|
-
|
|
158
|
+
let cached: string | null = null;
|
|
159
|
+
try {
|
|
160
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
161
|
+
} catch {
|
|
162
|
+
/*
|
|
163
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
164
|
+
* cache error must never skip the DB write below, otherwise the
|
|
165
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
166
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
167
|
+
*/
|
|
168
|
+
cached = null;
|
|
169
|
+
}
|
|
162
170
|
|
|
163
171
|
if (cached === extrasFingerprint) {
|
|
164
172
|
return; // same data was written recently
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
try {
|
|
176
|
+
await GlobalCache.setString(
|
|
177
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
178
|
+
cacheKey,
|
|
179
|
+
extrasFingerprint,
|
|
180
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
181
|
+
);
|
|
182
|
+
} catch {
|
|
183
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
184
|
+
}
|
|
173
185
|
|
|
174
186
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
187
|
const data: any = {
|
|
@@ -192,21 +192,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
192
192
|
)
|
|
193
193
|
.digest("hex");
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
cacheKey
|
|
198
|
-
|
|
195
|
+
let cached: string | null = null;
|
|
196
|
+
try {
|
|
197
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
198
|
+
} catch {
|
|
199
|
+
/*
|
|
200
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
201
|
+
* cache error must never skip the DB write below, otherwise the
|
|
202
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
203
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
204
|
+
*/
|
|
205
|
+
cached = null;
|
|
206
|
+
}
|
|
199
207
|
|
|
200
208
|
if (cached === extrasFingerprint) {
|
|
201
209
|
return; // same data was written recently
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
try {
|
|
213
|
+
await GlobalCache.setString(
|
|
214
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
215
|
+
cacheKey,
|
|
216
|
+
extrasFingerprint,
|
|
217
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
218
|
+
);
|
|
219
|
+
} catch {
|
|
220
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
221
|
+
}
|
|
210
222
|
|
|
211
223
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
212
224
|
const data: any = {
|
|
@@ -103,6 +103,14 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
103
103
|
*/
|
|
104
104
|
private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
|
|
105
105
|
new InMemoryTTLCache(10_000);
|
|
106
|
+
/*
|
|
107
|
+
* Caches the `requireSsoWithSsoProviderId` discriminator per project so the
|
|
108
|
+
* enforce-SSO middleware can require that the SSO token was issued by a
|
|
109
|
+
* specific provider. Stored as a string id (or null when unset). Populated
|
|
110
|
+
* alongside `getRequireSsoForLogin` so the common path stays a single query.
|
|
111
|
+
*/
|
|
112
|
+
private requireSsoWithSsoProviderIdCache: InMemoryTTLCache<string | null> =
|
|
113
|
+
new InMemoryTTLCache(10_000);
|
|
106
114
|
/*
|
|
107
115
|
* Caches the current billing plan per project. `getCurrentPlan` is hit
|
|
108
116
|
* by `CommonAPI.getDatabaseCommonInteractionProps` on every
|
|
@@ -351,6 +359,10 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
351
359
|
this.requireSsoForLoginCache.clear();
|
|
352
360
|
}
|
|
353
361
|
|
|
362
|
+
if (updateBy.data.requireSsoWithSsoProviderId !== undefined) {
|
|
363
|
+
this.requireSsoWithSsoProviderIdCache.clear();
|
|
364
|
+
}
|
|
365
|
+
|
|
354
366
|
if (IsBillingEnabled) {
|
|
355
367
|
if (
|
|
356
368
|
updateBy.data.businessDetails ||
|
|
@@ -1338,7 +1350,7 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1338
1350
|
|
|
1339
1351
|
const project: Model | null = await this.findOneById({
|
|
1340
1352
|
id: projectId,
|
|
1341
|
-
select: { requireSsoForLogin: true },
|
|
1353
|
+
select: { requireSsoForLogin: true, requireSsoWithSsoProviderId: true },
|
|
1342
1354
|
props: { isRoot: true },
|
|
1343
1355
|
});
|
|
1344
1356
|
|
|
@@ -1349,9 +1361,40 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1349
1361
|
|
|
1350
1362
|
const value: boolean = Boolean(project.requireSsoForLogin);
|
|
1351
1363
|
this.requireSsoForLoginCache.set(key, value, 60_000);
|
|
1364
|
+
this.requireSsoWithSsoProviderIdCache.set(
|
|
1365
|
+
key,
|
|
1366
|
+
project.requireSsoWithSsoProviderId
|
|
1367
|
+
? project.requireSsoWithSsoProviderId.toString()
|
|
1368
|
+
: null,
|
|
1369
|
+
60_000,
|
|
1370
|
+
);
|
|
1352
1371
|
return value;
|
|
1353
1372
|
}
|
|
1354
1373
|
|
|
1374
|
+
/**
|
|
1375
|
+
* Returns the specific SSO provider id a project requires for SSO-enforced
|
|
1376
|
+
* login, or null when any trusted provider is acceptable. Cached for 60s and
|
|
1377
|
+
* populated by `getRequireSsoForLogin`, so the enforce path stays one query.
|
|
1378
|
+
*/
|
|
1379
|
+
@CaptureSpan()
|
|
1380
|
+
public async getRequireSsoWithSsoProviderId(
|
|
1381
|
+
projectId: ObjectID,
|
|
1382
|
+
): Promise<ObjectID | null> {
|
|
1383
|
+
const key: string = projectId.toString();
|
|
1384
|
+
const cached: string | null | undefined =
|
|
1385
|
+
this.requireSsoWithSsoProviderIdCache.get(key);
|
|
1386
|
+
if (cached !== undefined) {
|
|
1387
|
+
return cached ? new ObjectID(cached) : null;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Populate both caches via the existing single-query path.
|
|
1391
|
+
await this.getRequireSsoForLogin(projectId);
|
|
1392
|
+
|
|
1393
|
+
const populated: string | null | undefined =
|
|
1394
|
+
this.requireSsoWithSsoProviderIdCache.get(key);
|
|
1395
|
+
return populated ? new ObjectID(populated) : null;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1355
1398
|
@CaptureSpan()
|
|
1356
1399
|
public async getOwners(projectId: ObjectID): Promise<Array<User>> {
|
|
1357
1400
|
if (!projectId) {
|
|
@@ -193,21 +193,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
193
193
|
)
|
|
194
194
|
.digest("hex");
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
cacheKey
|
|
199
|
-
|
|
196
|
+
let cached: string | null = null;
|
|
197
|
+
try {
|
|
198
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
199
|
+
} catch {
|
|
200
|
+
/*
|
|
201
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
202
|
+
* cache error must never skip the DB write below, otherwise the
|
|
203
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
204
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
205
|
+
*/
|
|
206
|
+
cached = null;
|
|
207
|
+
}
|
|
200
208
|
|
|
201
209
|
if (cached === extrasFingerprint) {
|
|
202
210
|
return; // same data was written recently
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
try {
|
|
214
|
+
await GlobalCache.setString(
|
|
215
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
216
|
+
cacheKey,
|
|
217
|
+
extrasFingerprint,
|
|
218
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
219
|
+
);
|
|
220
|
+
} catch {
|
|
221
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
222
|
+
}
|
|
211
223
|
|
|
212
224
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
225
|
const data: any = {
|
|
@@ -141,21 +141,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
141
141
|
)
|
|
142
142
|
.digest("hex");
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
cacheKey
|
|
147
|
-
|
|
144
|
+
let cached: string | null = null;
|
|
145
|
+
try {
|
|
146
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
147
|
+
} catch {
|
|
148
|
+
/*
|
|
149
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
150
|
+
* cache error must never skip the DB write below, otherwise the
|
|
151
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
152
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
153
|
+
*/
|
|
154
|
+
cached = null;
|
|
155
|
+
}
|
|
148
156
|
|
|
149
157
|
if (cached === extrasFingerprint) {
|
|
150
158
|
return; // same data was written recently
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
try {
|
|
162
|
+
await GlobalCache.setString(
|
|
163
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
164
|
+
cacheKey,
|
|
165
|
+
extrasFingerprint,
|
|
166
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
167
|
+
);
|
|
168
|
+
} catch {
|
|
169
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
170
|
+
}
|
|
159
171
|
|
|
160
172
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
173
|
const data: any = {
|