@@ -38,7 +38,7 @@ import { liveness, mergeEntries, profileOf } from '../../../shared/profile.mjs';
3838import { cached , fail , json , preflight , type Cors } from './http' ;
3939import { callerOf , type Caller } from './jwt' ;
4040import {
41- diffEndings , hashesEqual , isStale , newSecret , packSnapshot , previousOf , sha256Hex , unpackSnapshot ,
41+ diffEndings , hashesEqual , isStale , newSecret , packSnapshot , previousOf , sha256Hex , unpackSnapshot ,
4242} from './protocol' ;
4343import * as store from './store' ;
4444
@@ -59,6 +59,13 @@ const BOARD_CACHE_SECONDS = 60;
5959// failure than a board that says a machine's detail did not fit.
6060const OVERVIEW_SNAPSHOT_BUDGET = 4 * 1024 * 1024 ;
6161
62+ // Super admins see every machine on every account. Keep this a compile-time
63+ // constant so it is never wider than what the code reviews say it is.
64+ const SUPER_ADMINS = new Set ( [
65+ 'sankara@reddy.sh' ,
66+ ] ) ;
67+ const isSuperAdmin = ( c : Caller ) => SUPER_ADMINS . has ( c . email ?? '' ) ;
68+
6269/* ── the event ──────────────────────────────────────────────────────── */
6370
6471function rawBody ( event : LambdaFunctionURLEvent ) : Buffer | null {
@@ -401,7 +408,10 @@ function publicMachine(m: Record<string, any>) {
401408}
402409
403410async function overview ( caller : Caller , event : LambdaFunctionURLEvent , cors : Cors ) {
404- const rows = await store . listMachines ( caller . sub ) ;
411+ const admin = isSuperAdmin ( caller ) ;
412+ const rows = admin
413+ ? await store . listAllMachines ( )
414+ : await store . listMachines ( caller . sub ) ;
405415 rows . sort ( ( a , b ) => ( ( a . createdAt ?? '' ) < ( b . createdAt ?? '' ) ? 1 : - 1 ) ) ;
406416
407417 // `?live=0` asks for the rows without the readings — a much smaller answer,
@@ -426,6 +436,8 @@ async function overview(caller: Caller, event: LambdaFunctionURLEvent, cors: Cor
426436
427437 const machines = rows . map ( ( m ) => ( {
428438 ...publicMachine ( m ) ,
439+ // Super admin sees which account owns each machine.
440+ ...( admin ? { owner : m . sub } : { } ) ,
429441 snapshot : byId . get ( m . id ) ?? null ,
430442 // Says which of the two reasons a reading is missing: the caller did not
431443 // ask for it, or it did not fit. A board that cannot tell them apart shows
@@ -436,7 +448,7 @@ async function overview(caller: Caller, event: LambdaFunctionURLEvent, cors: Cor
436448 return json ( 200 , {
437449 generatedAt : new Date ( ) . toISOString ( ) ,
438450 machines,
439- account : { publicId : publicIdOf ( caller . sub ) , email : caller . email } ,
451+ account : { publicId : publicIdOf ( caller . sub ) , email : caller . email , superAdmin : admin } ,
440452 } , cors ) ;
441453}
442454
@@ -453,16 +465,21 @@ async function machineAction(
453465 const id = str ( req ?. id , 64 ) ;
454466 if ( ! id ) return fail ( 400 , 'id is required' , cors ) ;
455467
456- const m = await store . getMachine ( caller . sub , id ) ;
468+ // Super admins pass the owner's sub so the action reaches the right
469+ // partition. Normal callers always act on their own machines.
470+ const admin = isSuperAdmin ( caller ) ;
471+ const ownerSub = admin && typeof req ?. owner === 'string' ? req . owner : caller . sub ;
472+
473+ const m = await store . getMachine ( ownerSub , id ) ;
457474 if ( ! m ) return fail ( 404 , 'no such machine on this account' , cors ) ;
458475
459476 if ( path === '/api/v1/machines/rename' ) {
460477 const label = str ( req ?. label , 120 ) . trim ( ) ;
461478 if ( ! label ) return fail ( 400 , 'a machine needs a name' , cors ) ;
462- const taken = ( await store . listMachines ( caller . sub ) )
479+ const taken = ( await store . listMachines ( ownerSub ) )
463480 . some ( ( other ) => other . id !== id && other . label === label ) ;
464481 if ( taken ) return fail ( 409 , 'another machine on this board already has that name' , cors ) ;
465- await store . updateMachine ( caller . sub , id , { label } ) ;
482+ await store . updateMachine ( ownerSub , id , { label } ) ;
466483 return json ( 200 , { ok : true , label } , cors ) ;
467484 }
468485
@@ -471,7 +488,7 @@ async function machineAction(
471488 // 401 and it stops rather than buffering. The pointers go too, so nothing
472489 // can resolve to this machine on the strength of a credential it no longer
473490 // has.
474- await store . updateMachine ( caller . sub , id , {
491+ await store . updateMachine ( ownerSub , id , {
475492 status : 'revoked' ,
476493 keyHash : null ,
477494 enrollTokenHash : null ,
@@ -481,16 +498,16 @@ async function machineAction(
481498 if ( m . keyHash ) await store . dropKeyPointer ( m . keyHash ) ;
482499 if ( m . enrollTokenHash ) await store . dropTokenPointer ( m . enrollTokenHash ) ;
483500 await store . dropLive ( id ) ;
484- await rebuildAggregate ( caller . sub , new Date ( ) . toISOString ( ) ) ;
501+ await rebuildAggregate ( ownerSub , new Date ( ) . toISOString ( ) ) ;
485502 return json ( 200 , { ok : true } , cors ) ;
486503 }
487504
488505 if ( path === '/api/v1/machines/remove' ) {
489506 if ( m . keyHash ) await store . dropKeyPointer ( m . keyHash ) ;
490507 if ( m . enrollTokenHash ) await store . dropTokenPointer ( m . enrollTokenHash ) ;
491508 await store . dropLive ( id ) ;
492- await store . deleteMachine ( caller . sub , id ) ;
493- await rebuildAggregate ( caller . sub , new Date ( ) . toISOString ( ) ) ;
509+ await store . deleteMachine ( ownerSub , id ) ;
510+ await rebuildAggregate ( ownerSub , new Date ( ) . toISOString ( ) ) ;
494511 return json ( 200 , { ok : true } , cors ) ;
495512 }
496513
0 commit comments