Skip to content

Commit 935b2f1

Browse files
vyctorbrzezowskisteipete
authored andcommitted
fix(devices): refresh paired device last seen
1 parent 94b1427 commit 935b2f1

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
@@ -700,7 +700,9 @@ export function registerControlUiAndPairingSuite(): void {
700700

701701
test("device token auth matrix", async () => {
702702
const { server, ws, port, prevToken } = await startControlUiServerWithClient("secret");
703-
const { deviceToken, deviceIdentityPath } = await ensurePairedDeviceTokenForCurrentIdentity(ws);
703+
const { identity, deviceToken, deviceIdentityPath } =
704+
await ensurePairedDeviceTokenForCurrentIdentity(ws);
705+
const { getPairedDevice } = await import("../infra/device-pairing.js");
704706
ws.close();
705707

706708
const scenarios: Array<{
@@ -783,6 +785,9 @@ export function registerControlUiAndPairingSuite(): void {
783785
ws2.close();
784786
}
785787
}
788+
const paired = await getPairedDevice(identity.deviceId);
789+
expect(paired?.lastSeenReason).toBe("connect");
790+
expect(typeof paired?.lastSeenAtMs).toBe("number");
786791
} finally {
787792
await server.close();
788793
restoreGatewayToken(prevToken);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,8 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
11141114
const clientAccessMetadata = {
11151115
displayName: connectParams.client.displayName,
11161116
remoteIp: reportedClientIp,
1117+
lastSeenAtMs: Date.now(),
1118+
lastSeenReason: "connect",
11171119
};
11181120
const requirePairing = async (
11191121
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)