@oneuptime/common 11.0.4 → 11.0.6
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/Server/Infrastructure/GlobalCache.ts +11 -0
- package/Server/Middleware/UserAuthorization.ts +94 -20
- 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/HostService.ts +22 -10
- package/Server/Services/KubernetesClusterService.ts +22 -10
- package/Server/Services/PodmanHostService.ts +22 -10
- 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 +61 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +43 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +151 -0
- package/Types/CookieName.ts +1 -0
- 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/EditionLabel/EditionLabel.tsx +68 -16
- package/UI/Components/Modal/Modal.tsx +4 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +13 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +72 -16
- 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/HostService.js +19 -2
- package/build/dist/Server/Services/HostService.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/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 +61 -0
- package/build/dist/Server/Utils/Cookie.js.map +1 -1
- package/build/dist/Types/CookieName.js +1 -0
- package/build/dist/Types/CookieName.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/EditionLabel/EditionLabel.js +31 -5
- package/build/dist/UI/Components/EditionLabel/EditionLabel.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/package.json +1 -1
|
@@ -175,4 +175,15 @@ export default abstract class GlobalCache {
|
|
|
175
175
|
*/
|
|
176
176
|
await client.set(`${namespace}-${key}`, value, "EX", expiresInSeconds);
|
|
177
177
|
}
|
|
178
|
+
|
|
179
|
+
@CaptureSpan()
|
|
180
|
+
public static async deleteKey(namespace: string, key: string): Promise<void> {
|
|
181
|
+
const client: ClientType | null = Redis.getClient();
|
|
182
|
+
|
|
183
|
+
if (!client || !Redis.isConnected()) {
|
|
184
|
+
throw new DatabaseNotConnectedException("Cache is not connected");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await client.del(`${namespace}-${key}`);
|
|
188
|
+
}
|
|
178
189
|
}
|
|
@@ -26,6 +26,7 @@ import { JSONObject } from "../../Types/JSON";
|
|
|
26
26
|
import JSONFunctions from "../../Types/JSONFunctions";
|
|
27
27
|
import JSONWebTokenData from "../../Types/JsonWebTokenData";
|
|
28
28
|
import ObjectID from "../../Types/ObjectID";
|
|
29
|
+
import SsoProviderType from "../../Types/SSO/SsoProviderType";
|
|
29
30
|
import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
|
|
30
31
|
import Permission, {
|
|
31
32
|
PermissionHelper,
|
|
@@ -163,6 +164,76 @@ export default class UserMiddleware {
|
|
|
163
164
|
return ssoTokens;
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
/*
|
|
168
|
+
* Specific-IdP enforcement: when the project requires a specific SSO provider,
|
|
169
|
+
* the token must carry a matching `ssoProviderId` discriminator. Tokens issued
|
|
170
|
+
* before this field existed (no discriminator) do not satisfy a
|
|
171
|
+
* specific-provider requirement. When no specific provider is required, any
|
|
172
|
+
* trusted SSO token satisfies enforcement.
|
|
173
|
+
*/
|
|
174
|
+
private static isSsoProviderSatisfied(
|
|
175
|
+
decodedData: JSONWebTokenData,
|
|
176
|
+
requiredSsoProviderId?: ObjectID | undefined,
|
|
177
|
+
): boolean {
|
|
178
|
+
if (!requiredSsoProviderId) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const tokenProviderId: string | undefined = decodedData.ssoProviderId
|
|
183
|
+
? decodedData.ssoProviderId.toString()
|
|
184
|
+
: undefined;
|
|
185
|
+
|
|
186
|
+
return Boolean(
|
|
187
|
+
tokenProviderId && tokenProviderId === requiredSsoProviderId.toString(),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/*
|
|
192
|
+
* Reads the single Global SSO token, if present. It is minted by a Global
|
|
193
|
+
* SSO/OIDC login and is NOT bound to a project, so it satisfies SSO
|
|
194
|
+
* enforcement for any project the user belongs to. Sourced from the
|
|
195
|
+
* `global-sso-token` cookie (web) or the `x-global-sso-token` header (mobile).
|
|
196
|
+
* Only tokens carrying a Global provider type are accepted here.
|
|
197
|
+
*/
|
|
198
|
+
@CaptureSpan()
|
|
199
|
+
public static getGlobalSsoTokenData(
|
|
200
|
+
req: ExpressRequest,
|
|
201
|
+
): JSONWebTokenData | null {
|
|
202
|
+
let rawToken: string | undefined = CookieUtil.getCookieFromExpressRequest(
|
|
203
|
+
req,
|
|
204
|
+
CookieUtil.getGlobalSSOKey(),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (!rawToken) {
|
|
208
|
+
const headerToken: string | undefined = req.headers[
|
|
209
|
+
"x-global-sso-token"
|
|
210
|
+
] as string | undefined;
|
|
211
|
+
if (headerToken && typeof headerToken === "string") {
|
|
212
|
+
rawToken = headerToken;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!rawToken) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const decodedData: JSONWebTokenData = JSONWebToken.decode(rawToken);
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
decodedData.ssoProviderType === SsoProviderType.GlobalSSO ||
|
|
225
|
+
decodedData.ssoProviderType === SsoProviderType.GlobalOIDC
|
|
226
|
+
) {
|
|
227
|
+
return decodedData;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return null;
|
|
231
|
+
} catch (err) {
|
|
232
|
+
logger.error(err, getLogAttributesFromRequest(req as OneUptimeRequest));
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
166
237
|
@CaptureSpan()
|
|
167
238
|
public static doesSsoTokenForProjectExist(
|
|
168
239
|
req: ExpressRequest,
|
|
@@ -172,37 +243,40 @@ export default class UserMiddleware {
|
|
|
172
243
|
): boolean {
|
|
173
244
|
const ssoTokens: Dictionary<string> = this.getSsoTokens(req);
|
|
174
245
|
|
|
246
|
+
/*
|
|
247
|
+
* 1) Per-project SSO token (Project SSO/OIDC login). Bound to one project.
|
|
248
|
+
*/
|
|
175
249
|
if (ssoTokens && ssoTokens[projectId.toString()]) {
|
|
176
250
|
const decodedData: JSONWebTokenData = JSONWebToken.decode(
|
|
177
251
|
ssoTokens[projectId.toString()] as string,
|
|
178
252
|
);
|
|
179
253
|
if (
|
|
180
254
|
decodedData.projectId?.toString() === projectId.toString() &&
|
|
181
|
-
decodedData.userId.toString() === userId.toString()
|
|
255
|
+
decodedData.userId.toString() === userId.toString() &&
|
|
256
|
+
this.isSsoProviderSatisfied(decodedData, requiredSsoProviderId)
|
|
182
257
|
) {
|
|
183
|
-
/*
|
|
184
|
-
* Specific-IdP enforcement: when the project requires a specific SSO
|
|
185
|
-
* provider, the token must carry a matching `ssoProviderId`
|
|
186
|
-
* discriminator. Tokens issued before this field existed (no
|
|
187
|
-
* discriminator) do not satisfy a specific-provider requirement.
|
|
188
|
-
*/
|
|
189
|
-
if (requiredSsoProviderId) {
|
|
190
|
-
const tokenProviderId: string | undefined = decodedData.ssoProviderId
|
|
191
|
-
? decodedData.ssoProviderId.toString()
|
|
192
|
-
: undefined;
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
!tokenProviderId ||
|
|
196
|
-
tokenProviderId !== requiredSsoProviderId.toString()
|
|
197
|
-
) {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
258
|
return true;
|
|
203
259
|
}
|
|
204
260
|
}
|
|
205
261
|
|
|
262
|
+
/*
|
|
263
|
+
* 2) Global SSO token (Global SSO/OIDC login). Not bound to a project, so a
|
|
264
|
+
* single token satisfies enforcement for every project this user belongs to
|
|
265
|
+
* — including projects created after the login. The specific-provider
|
|
266
|
+
* discriminator still applies (a project pinned to a different provider is
|
|
267
|
+
* not satisfied by this token).
|
|
268
|
+
*/
|
|
269
|
+
const globalSsoTokenData: JSONWebTokenData | null =
|
|
270
|
+
this.getGlobalSsoTokenData(req);
|
|
271
|
+
|
|
272
|
+
if (
|
|
273
|
+
globalSsoTokenData &&
|
|
274
|
+
globalSsoTokenData.userId.toString() === userId.toString() &&
|
|
275
|
+
this.isSsoProviderSatisfied(globalSsoTokenData, requiredSsoProviderId)
|
|
276
|
+
) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
206
280
|
return false;
|
|
207
281
|
}
|
|
208
282
|
|
|
@@ -200,21 +200,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
200
200
|
)
|
|
201
201
|
.digest("hex");
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
cacheKey
|
|
206
|
-
|
|
203
|
+
let cached: string | null = null;
|
|
204
|
+
try {
|
|
205
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
206
|
+
} catch {
|
|
207
|
+
/*
|
|
208
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
209
|
+
* cache error must never skip the DB write below, otherwise the
|
|
210
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
211
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
212
|
+
*/
|
|
213
|
+
cached = null;
|
|
214
|
+
}
|
|
207
215
|
|
|
208
216
|
if (cached === extrasFingerprint) {
|
|
209
217
|
return; // same data was written recently
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
try {
|
|
221
|
+
await GlobalCache.setString(
|
|
222
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
223
|
+
cacheKey,
|
|
224
|
+
extrasFingerprint,
|
|
225
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
226
|
+
);
|
|
227
|
+
} catch {
|
|
228
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
229
|
+
}
|
|
218
230
|
|
|
219
231
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
232
|
const data: any = {
|
|
@@ -170,21 +170,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
170
170
|
)
|
|
171
171
|
.digest("hex");
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
cacheKey
|
|
176
|
-
|
|
173
|
+
let cached: string | null = null;
|
|
174
|
+
try {
|
|
175
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
176
|
+
} catch {
|
|
177
|
+
/*
|
|
178
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
179
|
+
* cache error must never skip the DB write below, otherwise the
|
|
180
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
181
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
182
|
+
*/
|
|
183
|
+
cached = null;
|
|
184
|
+
}
|
|
177
185
|
|
|
178
186
|
if (cached === extrasFingerprint) {
|
|
179
187
|
return; // same data was written recently
|
|
180
188
|
}
|
|
181
189
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
try {
|
|
191
|
+
await GlobalCache.setString(
|
|
192
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
193
|
+
cacheKey,
|
|
194
|
+
extrasFingerprint,
|
|
195
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
196
|
+
);
|
|
197
|
+
} catch {
|
|
198
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
199
|
+
}
|
|
188
200
|
|
|
189
201
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
202
|
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 = {
|
|
@@ -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 = {
|
|
@@ -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 = {
|
|
@@ -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 = {
|
|
@@ -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 = {
|
|
@@ -170,21 +170,33 @@ export class Service extends DatabaseService<Model> {
|
|
|
170
170
|
)
|
|
171
171
|
.digest("hex");
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
cacheKey
|
|
176
|
-
|
|
173
|
+
let cached: string | null = null;
|
|
174
|
+
try {
|
|
175
|
+
cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
176
|
+
} catch {
|
|
177
|
+
/*
|
|
178
|
+
* Cache unavailable — fail open and refresh lastSeenAt anyway. A
|
|
179
|
+
* cache error must never skip the DB write below, otherwise the
|
|
180
|
+
* resource is wrongly marked "disconnected" while telemetry is
|
|
181
|
+
* still flowing. Mirrors shouldRunMaintenance's fail-open stance.
|
|
182
|
+
*/
|
|
183
|
+
cached = null;
|
|
184
|
+
}
|
|
177
185
|
|
|
178
186
|
if (cached === extrasFingerprint) {
|
|
179
187
|
return; // same data was written recently
|
|
180
188
|
}
|
|
181
189
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
try {
|
|
191
|
+
await GlobalCache.setString(
|
|
192
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
193
|
+
cacheKey,
|
|
194
|
+
extrasFingerprint,
|
|
195
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
196
|
+
);
|
|
197
|
+
} catch {
|
|
198
|
+
// Best-effort throttle write; proceed with the DB update regardless.
|
|
199
|
+
}
|
|
188
200
|
|
|
189
201
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
202
|
const data: any = {
|