




















11// Gateway RPC handlers for device pairing and device-token lifecycle operations.
2-import { createHash } from "node:crypto";
32import {
43ErrorCodes,
54errorShape,
@@ -26,26 +25,23 @@ import {
2625rotateDeviceToken,
2726summarizeDeviceTokens,
2827} from "../../infra/device-pairing.js";
28+import type { DiagnosticSecurityEventInput } from "../../infra/diagnostic-events.js";
2929import {
30-emitTrustedSecurityEvent,
31-type DiagnosticSecurityEventInput,
32-} from "../../infra/diagnostic-events.js";
33-import type { GatewayClient, GatewayRequestHandlers } from "./types.js";
30+deniesCrossDeviceManagement,
31+deniesDeviceTokenRoleManagement,
32+pairedDeviceHasNonOperatorRole,
33+requestsNonOperatorDeviceRole,
34+resolveDeviceManagementAuthz,
35+resolveDeviceSessionAuthz,
36+} from "./device-management-authz.js";
37+import type { DeviceManagementAuthz } from "./device-management-authz.js";
38+import { emitDeviceManagementSecurityEvent } from "./device-management-security.js";
39+import type { GatewayRequestHandlers } from "./types.js";
34403541const DEVICE_TOKEN_ROTATION_DENIED_MESSAGE = "device token rotation denied";
3642const DEVICE_TOKEN_REVOCATION_DENIED_MESSAGE = "device token revocation denied";
374338-type DeviceSessionAuthz = {
39-callerDeviceId: string | null;
40-callerScopes: string[];
41-isAdminCaller: boolean;
42-};
43-44-type DeviceManagementAuthz = DeviceSessionAuthz & {
45-normalizedTargetDeviceId: string;
46-};
47-48-type DeviceSecurityDecision = NonNullable<DiagnosticSecurityEventInput["policy"]>["decision"];
44+type DeviceSessionAuthz = ReturnType<typeof resolveDeviceSessionAuthz>;
49455046const DEVICE_PAIR_APPROVAL_DENIED_MESSAGE = "device pairing approval denied";
5147const DEVICE_PAIR_REJECTION_DENIED_MESSAGE = "device pairing rejection denied";
@@ -95,144 +91,25 @@ function logDeviceTokenRevocationDenied(params: {
9591);
9692}
979398-function resolveDeviceManagementAuthz(
99-client: GatewayClient | null,
100-targetDeviceId: string,
101-): DeviceManagementAuthz {
102-return {
103- ...resolveDeviceSessionAuthz(client),
104-normalizedTargetDeviceId: targetDeviceId.trim(),
105-};
106-}
107-108-function resolveDeviceSessionAuthz(client: GatewayClient | null): DeviceSessionAuthz {
109-const callerScopes = Array.isArray(client?.connect?.scopes) ? client.connect.scopes : [];
110-const rawCallerDeviceId = client?.connect?.device?.id;
111-const callerDeviceId =
112-// Plain shared-auth connections may report device metadata, but only
113-// device-token auth proves ownership for self-service pairing actions.
114-client?.isDeviceTokenAuth && typeof rawCallerDeviceId === "string" && rawCallerDeviceId.trim()
115- ? rawCallerDeviceId.trim()
116- : null;
117-return {
118- callerDeviceId,
119- callerScopes,
120-isAdminCaller: callerScopes.includes("operator.admin"),
121-};
122-}
123-124-function deniesCrossDeviceManagement(authz: DeviceManagementAuthz): boolean {
125-return Boolean(
126-authz.callerDeviceId &&
127-authz.callerDeviceId !== authz.normalizedTargetDeviceId &&
128-!authz.isAdminCaller,
129-);
130-}
131-13294function shouldReturnRotatedDeviceToken(authz: DeviceManagementAuthz): boolean {
13395// Admins can rotate any token, but only a device rotating itself receives
13496// the new token in-band; other rotations are notification/invalidations.
13597return Boolean(authz.callerDeviceId && authz.callerDeviceId === authz.normalizedTargetDeviceId);
13698}
13799138-function deniesDeviceTokenRoleManagement(
139-authz: DeviceManagementAuthz,
140-targetRole: string,
141-): boolean {
142-const normalizedTargetRole = targetRole.trim();
143-if (!normalizedTargetRole || authz.isAdminCaller) {
144-return false;
145-}
146-return normalizedTargetRole !== "operator";
147-}
148-149-function hasNonOperatorDeviceRole(input: { role?: string; roles?: string[] }): boolean {
150-const roles = new Set<string>();
151-const role = input.role?.trim();
152-if (role) {
153-roles.add(role);
154-}
155-for (const entry of input.roles ?? []) {
156-const normalized = entry.trim();
157-if (normalized) {
158-roles.add(normalized);
159-}
160-}
161-return [...roles].some((entry) => entry !== "operator");
162-}
163-164-function hasNonOperatorDeviceTokenRole(
165-tokens: Record<string, DeviceAuthToken> | undefined,
166-): boolean {
167-for (const token of Object.values(tokens ?? {})) {
168-const normalized = token.role.trim();
169-if (normalized && normalized !== "operator") {
170-return true;
171-}
172-}
173-return false;
174-}
175-176-function requestsNonOperatorDeviceRole(pending: { role?: string; roles?: string[] }): boolean {
177-return hasNonOperatorDeviceRole(pending);
178-}
179-180-function pairedDeviceHasNonOperatorRole(device: {
181-role?: string;
182-roles?: string[];
183-tokens?: Record<string, DeviceAuthToken>;
184-}): boolean {
185-return hasNonOperatorDeviceRole(device) || hasNonOperatorDeviceTokenRole(device.tokens);
186-}
187-188-function hashDeviceSecurityId(value: string | undefined): string | undefined {
189-const normalized = value?.trim();
190-if (!normalized) {
191-return undefined;
192-}
193-return `sha256:${createHash("sha256").update(normalized).digest("hex").slice(0, 12)}`;
194-}
195-196100function emitDeviceSecurityEvent(params: {
197101action: string;
198102outcome: DiagnosticSecurityEventInput["outcome"];
199103severity: DiagnosticSecurityEventInput["severity"];
200104authz: DeviceSessionAuthz;
201105targetDeviceId?: string;
202106policyId: string;
203-decision: DeviceSecurityDecision;
107+decision: NonNullable<DiagnosticSecurityEventInput["policy"]>["decision"];
204108controlId: string;
205109reason?: string;
206110attributes?: Record<string, string | number | boolean>;
207111}) {
208-emitTrustedSecurityEvent({
209-category: "auth",
210-action: params.action,
211-outcome: params.outcome,
212-severity: params.severity,
213-actor: {
214-kind: "operator",
215- ...(params.authz.callerDeviceId
216- ? { deviceIdHash: hashDeviceSecurityId(params.authz.callerDeviceId) }
217- : {}),
218-role: params.authz.isAdminCaller ? "admin" : "operator",
219-},
220-target: {
221-kind: "device",
222- ...(params.targetDeviceId ? { idHash: hashDeviceSecurityId(params.targetDeviceId) } : {}),
223-},
224-policy: {
225-id: params.policyId,
226-decision: params.decision,
227- ...(params.reason ? { reason: params.reason } : {}),
228-},
229-control: {
230-id: params.controlId,
231-family: "auth",
232-},
233- ...(params.reason ? { reason: params.reason } : {}),
234- ...(params.attributes ? { attributes: params.attributes } : {}),
235-});
112+emitDeviceManagementSecurityEvent(params);
236113}
237114238115function emitDevicePairingDeniedSecurityEvent(params: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。