feat(device-manager): trust Hub owner scopes

This commit is contained in:
Codex
2026-08-13 11:37:36 +03:00
parent 1c5246afe8
commit 117bfe0c3a
3 changed files with 171 additions and 18 deletions
@@ -15,6 +15,22 @@ test("Launcher handoff becomes an opaque Device Manager session and trusted acto
return jsonResponse(200, {
ok: true,
launcherSessionId,
access: {
allowed: true,
hubRole: "owner",
ownerScopes: [
{
scopeKind: "company",
ownerRef: "client:client_dctouch",
displayName: "DC Touch",
},
{
scopeKind: "personal",
ownerRef: "user:user_root",
displayName: "DC SUDO",
},
],
},
user: {
id: "user_root",
email: "root@example.test",
@@ -53,11 +69,18 @@ test("Launcher handoff becomes an opaque Device Manager session and trusted acto
const context = auth.currentContext(request);
assert.equal(context.actor.userRef, "user:user_root");
assert.equal(context.actor.hubRole, "owner");
assert.deepEqual(context.actor.ownerScopes, [{
scopeKind: "personal",
ownerRef: "user:user_root",
displayName: "DC SUDO",
}]);
assert.deepEqual(context.actor.ownerScopes, [
{
scopeKind: "company",
ownerRef: "client:client_dctouch",
displayName: "DC Touch",
},
{
scopeKind: "personal",
ownerRef: "user:user_root",
displayName: "DC SUDO",
},
]);
assert.deepEqual(context.actor.groupRefs, [
"group:nodedc:device-core:admin",
"group:nodedc:superadmin",
@@ -76,6 +99,11 @@ test("invalid identity and explicit Device Core block never produce an actor", a
fetchImpl: async () => jsonResponse(200, {
ok: true,
launcherSessionId,
access: {
allowed: true,
hubRole: "admin",
ownerScopes: [],
},
user,
}),
});
@@ -97,6 +125,65 @@ test("invalid identity and explicit Device Core block never produce an actor", a
}
});
test("production auth fails closed when Launcher omits trusted Device Core access", async () => {
const auth = createDeviceManagerAuth({
env: productionEnv(),
fetchImpl: async () => jsonResponse(200, {
ok: true,
launcherSessionId,
user: {
id: "user_root",
email: "root@example.test",
name: "DC SUDO",
groups: ["nodedc:superadmin"],
},
}),
});
const handoff = mockResponse();
await auth.handleHandoff(
{ method: "GET", headers: {} },
handoff,
new URL("https://device.example.test/auth/nodedc/handoff?token=handoff-secret"),
);
const cookie = String(handoff.getHeader("set-cookie")).split(";", 1)[0];
const response = mockResponse();
assert.equal(await auth.authorize(
{ method: "GET", headers: { cookie, accept: "application/json" } },
response,
new URL("https://device.example.test/api/device-manager/session"),
), true);
assert.equal(response.statusCode, 403);
assert.equal(JSON.parse(response.body).error, "device_manager_access_denied");
});
test("an injected file-backed token takes precedence over broad platform env tokens", async () => {
const calls = [];
const auth = createDeviceManagerAuth({
env: { ...productionEnv(), NODEDC_INTERNAL_ACCESS_TOKEN: "broad-platform-token" },
internalToken: "scoped-file-token",
fetchImpl: async (url, init) => {
calls.push({ url, init });
return jsonResponse(200, {
ok: true,
launcherSessionId,
access: { allowed: true, hubRole: "member", ownerScopes: [] },
user: {
id: "device-member",
email: "member@example.test",
name: "Device Member",
groups: ["nodedc:device-core:access"],
},
});
},
});
await auth.handleHandoff(
{ method: "GET", headers: {} },
mockResponse(),
new URL("https://device.example.test/auth/nodedc/handoff?token=handoff-secret"),
);
assert.equal(calls[0].init.headers.Authorization, "Bearer scoped-file-token");
});
function productionEnv() {
return {
NODE_ENV: "production",