Skip to content

Commit 447e069

Browse files
fix(devices): refresh paired device last seen
1 parent 068e026 commit 447e069

4 files changed

Lines changed: 32 additions & 2 deletions

File tree

src/gateway/server.auth.control-ui.suite.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,9 @@ export function registerControlUiAndPairingSuite(): void {
689689

690690
test("device token auth matrix", async () => {
691691
const { server, ws, port, prevToken } = await startControlUiServerWithClient("secret");
692-
const { deviceToken, deviceIdentityPath } = await ensurePairedDeviceTokenForCurrentIdentity(ws);
692+
const { identity, deviceToken, deviceIdentityPath } =
693+
await ensurePairedDeviceTokenForCurrentIdentity(ws);
694+
const { getPairedDevice } = await import("../infra/device-pairing.js");
693695
ws.close();
694696

695697
const scenarios: Array<{
@@ -772,6 +774,9 @@ export function registerControlUiAndPairingSuite(): void {
772774
ws2.close();
773775
}
774776
}
777+
const paired = await getPairedDevice(identity.deviceId);
778+
expect(paired?.lastSeenReason).toBe("connect");
779+
expect(typeof paired?.lastSeenAtMs).toBe("number");
775780
} finally {
776781
await server.close();
777782
restoreGatewayToken(prevToken);

src/gateway/server/ws-connection/message-handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,8 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
11131113
const clientAccessMetadata = {
11141114
displayName: connectParams.client.displayName,
11151115
remoteIp: reportedClientIp,
1116+
lastSeenAtMs: Date.now(),
1117+
lastSeenReason: "connect",
11161118
};
11171119
const requirePairing = async (
11181120
reason: ConnectPairingRequiredReason,

src/infra/device-pairing.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,26 @@ describe("device pairing tokens", () => {
789789
});
790790
});
791791

792+
test("device token verification refreshes paired device last-seen metadata", async () => {
793+
const { baseDir, token } = await setupOperatorToken(["operator.read"]);
794+
const beforeVerifyAtMs = Date.now();
795+
796+
await expect(
797+
verifyDeviceToken({
798+
deviceId: "device-1",
799+
token,
800+
role: "operator",
801+
scopes: ["operator.read"],
802+
baseDir,
803+
}),
804+
).resolves.toEqual({ ok: true });
805+
806+
const paired = await getPairedDevice("device-1", baseDir);
807+
expect(paired?.lastSeenReason).toBe("device-token-auth");
808+
expect(typeof paired?.lastSeenAtMs).toBe("number");
809+
expect(paired?.lastSeenAtMs ?? 0).toBeGreaterThanOrEqual(beforeVerifyAtMs);
810+
});
811+
792812
test("generates base64url device tokens with 256-bit entropy output length", async () => {
793813
const baseDir = await makeDevicePairingDir();
794814
await setupPairedOperatorDevice(baseDir, ["operator.admin"]);

src/infra/device-pairing.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,9 +976,12 @@ export async function verifyDeviceToken(params: {
976976
if (!roleScopesAllow({ role, requestedScopes, allowedScopes: entry.scopes })) {
977977
return { ok: false, reason: "scope-mismatch" };
978978
}
979-
entry.lastUsedAtMs = Date.now();
979+
const now = Date.now();
980+
entry.lastUsedAtMs = now;
980981
device.tokens ??= {};
981982
device.tokens[role] = entry;
983+
device.lastSeenAtMs = now;
984+
device.lastSeenReason = "device-token-auth";
982985
state.pairedByDeviceId[device.deviceId] = device;
983986
await persistState(state, params.baseDir, "paired");
984987
return entry.issuer ? { ok: true, issuer: entry.issuer } : { ok: true };

0 commit comments

Comments
 (0)