@camstack/server 1.2.537 → 1.2.539

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.
@@ -1036,6 +1036,46 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
1036
1036
  });
1037
1037
  return { id: record.id, token, expiresAt: record.expiresAt };
1038
1038
  }),
1039
+ /**
1040
+ * Slide expiry on a still-valid share token (same `csv_*`). Used by the
1041
+ * Home Assistant embed cache so a long-lived dashboard does not mint a
1042
+ * new row every hour. Expired / missing → NOT_FOUND (caller must create).
1043
+ */
1044
+ extendShareToken: trpc_middleware_js_1.protectedProcedure
1045
+ .input(zod_1.z.object({
1046
+ id: zod_1.z.string().min(1),
1047
+ ttlSec: zod_1.z.number().int().min(share_token_service_js_1.SHARE_TOKEN_TTL_MIN_SEC).max(share_token_service_js_1.SHARE_TOKEN_TTL_MAX_SEC),
1048
+ }))
1049
+ .output(zod_1.z.object({ id: zod_1.z.string(), expiresAt: zod_1.z.number() }))
1050
+ .mutation(async ({ input, ctx }) => {
1051
+ assertRealUserSession(ctx.user);
1052
+ const service = requireShareTokens();
1053
+ try {
1054
+ const record = await service.extend({
1055
+ id: input.id,
1056
+ ttlSec: input.ttlSec,
1057
+ callerUserId: ctx.user.id,
1058
+ callerIsAdmin: ctx.user.isAdmin,
1059
+ });
1060
+ if (record.expiresAt === null) {
1061
+ throw new server_1.TRPCError({
1062
+ code: 'BAD_REQUEST',
1063
+ message: 'A never-expiring share token cannot be extended',
1064
+ });
1065
+ }
1066
+ return { id: record.id, expiresAt: record.expiresAt };
1067
+ }
1068
+ catch (error) {
1069
+ const message = error instanceof Error ? error.message : 'Share token extend failed';
1070
+ if (/not found|expired/i.test(message)) {
1071
+ throw new server_1.TRPCError({ code: 'NOT_FOUND', message });
1072
+ }
1073
+ if (/owner or an admin|never-expiring/i.test(message)) {
1074
+ throw new server_1.TRPCError({ code: 'FORBIDDEN', message });
1075
+ }
1076
+ throw new server_1.TRPCError({ code: 'BAD_REQUEST', message });
1077
+ }
1078
+ }),
1039
1079
  revokeShareToken: trpc_middleware_js_1.protectedProcedure
1040
1080
  .input(zod_1.z.object({ id: zod_1.z.string().min(1) }))
1041
1081
  .output(zod_1.z.object({ success: zod_1.z.boolean() }))
@@ -262,6 +262,59 @@ class ShareTokenService {
262
262
  });
263
263
  return record;
264
264
  }
265
+ /**
266
+ * Slide `expiresAt` forward from NOW for a still-valid token. Same raw
267
+ * credential, same id — Home Assistant remints without flooding
268
+ * `share_view_tokens`. Throws when missing, expired, never-expiring,
269
+ * or the caller is not the owner/admin; refuses `'never'` as ttlSec.
270
+ */
271
+ async extend(input) {
272
+ if (!Number.isInteger(input.ttlSec) ||
273
+ input.ttlSec < exports.SHARE_TOKEN_TTL_MIN_SEC ||
274
+ input.ttlSec > exports.SHARE_TOKEN_TTL_MAX_SEC) {
275
+ throw new Error(`Share token TTL out of bounds — got ${input.ttlSec}s, allowed ` +
276
+ `[${exports.SHARE_TOKEN_TTL_MIN_SEC}s, ${exports.SHARE_TOKEN_TTL_MAX_SEC}s]`);
277
+ }
278
+ const results = await (await this.store()).query({
279
+ collection: SHARE_TOKENS_COLLECTION,
280
+ filter: { where: { id: input.id } },
281
+ });
282
+ const first = results[0];
283
+ if (!first) {
284
+ throw new Error(`Share token not found: ${input.id}`);
285
+ }
286
+ const record = parseShareToken(first.data);
287
+ if (!record) {
288
+ throw new Error(`Share token not found: ${input.id}`);
289
+ }
290
+ if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
291
+ throw new Error('Only the token owner or an admin can extend a share token');
292
+ }
293
+ if (record.expiresAt === null) {
294
+ throw new Error('A never-expiring share token cannot be extended');
295
+ }
296
+ const now = Date.now();
297
+ if (now > record.expiresAt) {
298
+ throw new Error(`Share token expired: ${input.id}`);
299
+ }
300
+ const updated = {
301
+ ...record,
302
+ expiresAt: now + input.ttlSec * 1000,
303
+ };
304
+ await (await this.store()).update({
305
+ collection: SHARE_TOKENS_COLLECTION,
306
+ id: record.id,
307
+ data: { ...updated },
308
+ });
309
+ this.logger?.info('Share token extended', {
310
+ meta: {
311
+ id: updated.id,
312
+ byUserId: input.callerUserId,
313
+ expiresAt: updated.expiresAt,
314
+ },
315
+ });
316
+ return updated;
317
+ }
265
318
  /**
266
319
  * Revoke (delete) a share token. Only the minting user or an admin may
267
320
  * revoke. Returns `false` when the token doesn't exist; throws on a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.537",
3
+ "version": "1.2.539",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -37,17 +37,17 @@
37
37
  ]
38
38
  },
39
39
  "dependencies": {
40
- "@camstack/addon-admin-ui": "1.2.404",
40
+ "@camstack/addon-admin-ui": "1.2.405",
41
41
  "@camstack/addon-agent-ui": "1.2.223",
42
42
  "@camstack/addon-auth": "1.2.141",
43
43
  "@camstack/addon-decoder-nodeav": "1.2.123",
44
44
  "@camstack/addon-notifiers": "1.2.134",
45
- "@camstack/addon-pipeline": "1.2.357",
46
- "@camstack/addon-pipeline-orchestrator": "1.2.308",
47
- "@camstack/addon-post-analysis": "1.2.356",
45
+ "@camstack/addon-pipeline": "1.2.358",
46
+ "@camstack/addon-pipeline-orchestrator": "1.2.309",
47
+ "@camstack/addon-post-analysis": "1.2.357",
48
48
  "@camstack/sdk": "1.2.131",
49
- "@camstack/system": "1.2.414",
50
- "@camstack/types": "1.2.352",
49
+ "@camstack/system": "1.2.415",
50
+ "@camstack/types": "1.2.353",
51
51
  "@camstack/ui-library": "1.2.281",
52
52
  "@fastify/compress": "^9.0.0",
53
53
  "@fastify/cookie": "^11.0.2",