@camstack/server 1.2.602 → 1.2.603

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.
@@ -6,6 +6,7 @@ exports.validateAuthorizeQuery = validateAuthorizeQuery;
6
6
  exports.isRedirectUriAllowed = isRedirectUriAllowed;
7
7
  exports.summariseScopes = summariseScopes;
8
8
  exports.registerOauth2Routes = registerOauth2Routes;
9
+ const types_1 = require("@camstack/types");
9
10
  const session_cookie_js_1 = require("../../auth/session-cookie.js");
10
11
  const app_page_strings_js_1 = require("../../auth/app-page-strings.js");
11
12
  const consent_page_js_1 = require("./consent-page.js");
@@ -232,7 +233,7 @@ function summariseScopes(scopes) {
232
233
  * weakened. The RESPONSE carries only the caller's own echoed input; it tells
233
234
  * a client nothing about what else this hub has installed.
234
235
  */
235
- function refuseAuthorize(reply, logger, method, call, refusal) {
236
+ function refuseAuthorize(request, reply, logger, method, call, refusal) {
236
237
  logger.warn(`oauth2 authorize refused: ${refusal.error}`, {
237
238
  meta: {
238
239
  method,
@@ -245,7 +246,28 @@ function refuseAuthorize(reply, logger, method, call, refusal) {
245
246
  if (refusal.retryAfterSec !== undefined) {
246
247
  void reply.header('Retry-After', String(refusal.retryAfterSec));
247
248
  }
248
- return reply.status(refusal.status).send({ error: refusal.error, ...refusal.detail });
249
+ return answerRefusal(request, reply, refusal.status, { error: refusal.error, ...refusal.detail }, refusal.status === 503 ? 'unavailable' : 'invalid_request', typeof refusal.detail?.received === 'string' ? refusal.detail.received : undefined);
250
+ }
251
+ /**
252
+ * Answer a refused authorize. A BROWSER (it asked for HTML: a person who
253
+ * followed a link) is sent to the CamStack completion page with a reason CODE
254
+ * — a bare JSON body is a dead end for them. A machine caller keeps the status
255
+ * and JSON it always got. Relative: the browser is already on the hub's origin,
256
+ * and nothing the request carried (the redirect target, the state, any code)
257
+ * goes into the URL.
258
+ */
259
+ function answerRefusal(request, reply, status, body, reason, integration) {
260
+ const accept = request.headers.accept;
261
+ if (typeof accept === 'string' && accept.includes('text/html')) {
262
+ return reply.redirect((0, types_1.oauthLandingUrl)(null, {
263
+ status: 'error',
264
+ reason,
265
+ ...(integration !== undefined && /^[A-Za-z0-9._-]{1,64}$/.test(integration)
266
+ ? { integration }
267
+ : {}),
268
+ }));
269
+ }
270
+ return reply.status(status).send(body);
249
271
  }
250
272
  /**
251
273
  * An authorize call turned away for want of a usable session.
@@ -317,13 +339,13 @@ function registerOauth2Routes(fastify, deps) {
317
339
  }
318
340
  const registry = deps.getRegistry();
319
341
  if (!registry) {
320
- return reply.status(503).send({ error: 'service_unavailable' });
342
+ return answerRefusal(request, reply, 503, { error: 'service_unavailable' }, 'unavailable');
321
343
  }
322
344
  const { descriptors, pendingAddons } = await readIntegrationRegistry(registry);
323
345
  const query = request.query;
324
346
  const v = validateAuthorizeQuery(query, descriptors, pendingAddons);
325
347
  if (!v.ok) {
326
- return refuseAuthorize(reply, deps.logger, 'GET', call, v);
348
+ return refuseAuthorize(request, reply, deps.logger, 'GET', call, v);
327
349
  }
328
350
  const descriptor = descriptors.get(v.integration);
329
351
  if (!isRedirectUriAllowed(v.redirectUri, descriptor.allowedRedirectPrefixes, descriptor.allowedPrivateHostPaths ?? [])) {
@@ -337,9 +359,7 @@ function registerOauth2Routes(fastify, deps) {
337
359
  allowedPrivateHostPaths: descriptor.allowedPrivateHostPaths ?? [],
338
360
  },
339
361
  });
340
- return reply
341
- .status(400)
342
- .send({ error: 'invalid_request — redirect_uri not allowed for this integration' });
362
+ return answerRefusal(request, reply, 400, { error: 'invalid_request — redirect_uri not allowed for this integration' }, 'invalid_request', v.integration);
343
363
  }
344
364
  const html = (0, consent_page_js_1.renderConsentPage)({
345
365
  lang: (0, app_page_strings_js_1.resolveAppPageLang)({
@@ -417,7 +437,7 @@ function registerOauth2Routes(fastify, deps) {
417
437
  }
418
438
  const registry = deps.getRegistry();
419
439
  if (!registry) {
420
- return reply.status(503).send({ error: 'service_unavailable' });
440
+ return answerRefusal(request, reply, 503, { error: 'service_unavailable' }, 'unavailable');
421
441
  }
422
442
  const { descriptors, pendingAddons } = await readIntegrationRegistry(registry);
423
443
  const body = request.body;
@@ -433,7 +453,7 @@ function registerOauth2Routes(fastify, deps) {
433
453
  // and every one of them is attacker-editable.
434
454
  const v = validateAuthorizeQuery(formQuery, descriptors, pendingAddons);
435
455
  if (!v.ok) {
436
- return refuseAuthorize(reply, deps.logger, 'POST', call, v);
456
+ return refuseAuthorize(request, reply, deps.logger, 'POST', call, v);
437
457
  }
438
458
  const descriptor = descriptors.get(v.integration);
439
459
  if (!isRedirectUriAllowed(v.redirectUri, descriptor.allowedRedirectPrefixes, descriptor.allowedPrivateHostPaths ?? [])) {
@@ -447,9 +467,7 @@ function registerOauth2Routes(fastify, deps) {
447
467
  allowedPrivateHostPaths: descriptor.allowedPrivateHostPaths ?? [],
448
468
  },
449
469
  });
450
- return reply
451
- .status(400)
452
- .send({ error: 'invalid_request — redirect_uri not allowed for this integration' });
470
+ return answerRefusal(request, reply, 400, { error: 'invalid_request — redirect_uri not allowed for this integration' }, 'invalid_request', v.integration);
453
471
  }
454
472
  if (body.consent !== 'allow') {
455
473
  return reply.redirect(`${v.redirectUri}?error=access_denied&state=${encodeURIComponent(v.state)}`);
@@ -4269,6 +4269,15 @@ function createCapRouter_dishwasher(getProvider, createRemoteProxy) {
4269
4269
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4270
4270
  return p.pause(methodInput);
4271
4271
  }),
4272
+ resume: trpc_middleware_js_1.protectedProcedure
4273
+ .input(types_56.dishwasherCapability.methods.resume.input.loose())
4274
+ .output(types_56.dishwasherCapability.methods.resume.output)
4275
+ .mutation(async ({ input, ctx }) => {
4276
+ const { nodeId, ...methodInput } = input;
4277
+ const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
4278
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4279
+ return p.resume(methodInput);
4280
+ }),
4272
4281
  stop: trpc_middleware_js_1.protectedProcedure
4273
4282
  .input(types_56.dishwasherCapability.methods.stop.input.loose())
4274
4283
  .output(types_56.dishwasherCapability.methods.stop.output)
@@ -4359,6 +4368,15 @@ function createCapRouter_dishwasher(getProvider, createRemoteProxy) {
4359
4368
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4360
4369
  return p.setOption(methodInput);
4361
4370
  }),
4371
+ getOptions: trpc_middleware_js_1.protectedProcedure
4372
+ .input(types_56.dishwasherCapability.methods.getOptions.input.loose())
4373
+ .output(types_56.dishwasherCapability.methods.getOptions.output)
4374
+ .query(async ({ input, ctx }) => {
4375
+ const { nodeId, ...methodInput } = input;
4376
+ const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
4377
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4378
+ return p.getOptions(methodInput);
4379
+ }),
4362
4380
  });
4363
4381
  }
4364
4382
  function createCapRouter_doorbell(getProvider, createRemoteProxy) {
@@ -11128,6 +11146,15 @@ function createCapRouter_storageMigration(getProvider, createRemoteProxy) {
11128
11146
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11129
11147
  return p.movers(methodInput);
11130
11148
  }),
11149
+ moverClearance: trpc_middleware_js_1.adminProcedure
11150
+ .input(types_127.storageMigrationCapability.methods.moverClearance.input.loose())
11151
+ .output(types_127.storageMigrationCapability.methods.moverClearance.output)
11152
+ .query(async ({ input, ctx }) => {
11153
+ const { nodeId, ...methodInput } = input;
11154
+ const p = resolveProvider('storage-migration', nodeId, () => getProvider(ctx), createRemoteProxy);
11155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11156
+ return p.moverClearance(methodInput);
11157
+ }),
11131
11158
  residue: trpc_middleware_js_1.adminProcedure
11132
11159
  .input(types_127.storageMigrationCapability.methods.residue.input.loose())
11133
11160
  .output(types_127.storageMigrationCapability.methods.residue.output)
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.alexaLambdaPageStyleSource = alexaLambdaPageStyleSource;
4
+ /**
5
+ * The source text of the skill Lambda's `page-style.js`: the hub's standalone
6
+ * page stylesheet plus the brand mark, as constants. Kept here, beside
7
+ * `app-page-shell.ts`, so there is ONE place the look lives and a spec holds the
8
+ * Lambda's copy to it (`scripts/gen-alexa-lambda-page-style.ts` writes it).
9
+ */
10
+ const app_page_shell_js_1 = require("./app-page-shell.js");
11
+ function alexaLambdaPageStyleSource() {
12
+ return `// biome-ignore-all format: generated file
13
+ // biome-ignore-all lint: generated file
14
+ /**
15
+ * GENERATED by scripts/gen-alexa-lambda-page-style.ts from the hub's page shell
16
+ * (server/backend/src/auth/app-page-shell.ts). Do not edit: change the shell and
17
+ * regenerate. The Lambda ships plain files, so the stylesheet travels as a constant.
18
+ */
19
+ 'use strict'
20
+
21
+ const STYLE = ${JSON.stringify(app_page_shell_js_1.APP_PAGE_STYLE)}
22
+ const LOGO_DARK = ${JSON.stringify(app_page_shell_js_1.APP_PAGE_LOGOS.dark)}
23
+ const LOGO_LIGHT = ${JSON.stringify(app_page_shell_js_1.APP_PAGE_LOGOS.light)}
24
+
25
+ module.exports = { STYLE, LOGO_DARK, LOGO_LIGHT }
26
+ `;
27
+ }
@@ -64,6 +64,10 @@ const COMPONENT_CSS = [
64
64
  `.error:empty{display:none}`,
65
65
  `.btn:focus-visible,.link:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}`,
66
66
  `code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow-wrap:anywhere}`,
67
+ `.field{display:flex;flex-direction:column;gap:var(--spacing-md)}`,
68
+ `.field label{font-size:12px;line-height:16px;font-weight:500;color:var(--color-foreground-muted)}`,
69
+ `input[type=url],input[type=text]{width:100%;min-height:44px;padding:10px 12px;border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-background);color:var(--color-foreground);font:inherit}`,
70
+ `input[type=url]:focus-visible,input[type=text]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}`,
67
71
  `form{display:contents}`,
68
72
  `.actions{display:flex;flex-direction:column;gap:var(--spacing-md)}`,
69
73
  ].join('');
@@ -267,7 +267,7 @@ async function bootManual(opts) {
267
267
  throw new Error('the process service did not answer');
268
268
  return rows;
269
269
  },
270
- // The verdict reaches the alert centre (one alert + the direct critical push).
270
+ // The verdict reaches the alert centre (one critical alert).
271
271
  onResult: (check) => {
272
272
  eventBusService.emit({
273
273
  id: (0, node_crypto_1.randomUUID)(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.602",
3
+ "version": "1.2.603",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -38,19 +38,19 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@camstack/addon-admin-ui": "1.2.439",
42
- "@camstack/addon-agent-ui": "1.2.245",
43
- "@camstack/addon-auth": "1.2.150",
44
- "@camstack/addon-decoder-nodeav": "1.2.124",
45
- "@camstack/addon-notifiers": "1.2.602",
46
- "@camstack/addon-pipeline": "1.2.602",
47
- "@camstack/addon-pipeline-orchestrator": "1.2.603",
48
- "@camstack/addon-post-analysis": "1.2.607",
49
- "@camstack/develop": "0.1.14",
50
- "@camstack/sdk": "1.2.134",
51
- "@camstack/system": "1.2.453",
52
- "@camstack/types": "1.2.384",
53
- "@camstack/ui-library": "1.2.301",
41
+ "@camstack/addon-admin-ui": "1.2.440",
42
+ "@camstack/addon-agent-ui": "1.2.246",
43
+ "@camstack/addon-auth": "1.2.151",
44
+ "@camstack/addon-decoder-nodeav": "1.2.125",
45
+ "@camstack/addon-notifiers": "1.2.603",
46
+ "@camstack/addon-pipeline": "1.2.603",
47
+ "@camstack/addon-pipeline-orchestrator": "1.2.604",
48
+ "@camstack/addon-post-analysis": "1.2.608",
49
+ "@camstack/develop": "0.1.15",
50
+ "@camstack/sdk": "1.2.135",
51
+ "@camstack/system": "1.2.454",
52
+ "@camstack/types": "1.2.385",
53
+ "@camstack/ui-library": "1.2.302",
54
54
  "@fastify/compress": "^9.0.0",
55
55
  "@fastify/cookie": "^11.0.2",
56
56
  "@fastify/cors": "^11.2.0",