feat: add machine data encryption key and new-machine update
This commit is contained in:
parent
31bb2892f2
commit
d4570c6b8f
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Machine" ADD COLUMN "dataEncryptionKey" BYTEA;
|
@ -199,6 +199,7 @@ model Machine {
|
|||||||
metadataVersion Int @default(0)
|
metadataVersion Int @default(0)
|
||||||
daemonState String? // Encrypted - contains dynamic daemon state
|
daemonState String? // Encrypted - contains dynamic daemon state
|
||||||
daemonStateVersion Int @default(0)
|
daemonStateVersion Int @default(0)
|
||||||
|
dataEncryptionKey Bytes?
|
||||||
seq Int @default(0)
|
seq Int @default(0)
|
||||||
active Boolean @default(true)
|
active Boolean @default(true)
|
||||||
lastActiveAt DateTime @default(now())
|
lastActiveAt DateTime @default(now())
|
||||||
|
@ -5,7 +5,7 @@ import { db } from "@/storage/db";
|
|||||||
import { log } from "@/utils/log";
|
import { log } from "@/utils/log";
|
||||||
import { randomKeyNaked } from "@/utils/randomKeyNaked";
|
import { randomKeyNaked } from "@/utils/randomKeyNaked";
|
||||||
import { allocateUserSeq } from "@/storage/seq";
|
import { allocateUserSeq } from "@/storage/seq";
|
||||||
import { buildUpdateMachineUpdate } from "@/app/events/eventRouter";
|
import { buildNewMachineUpdate, buildUpdateMachineUpdate } from "@/app/events/eventRouter";
|
||||||
|
|
||||||
export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
||||||
app.post('/v1/machines', {
|
app.post('/v1/machines', {
|
||||||
@ -14,12 +14,13 @@ export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
metadata: z.string(), // Encrypted metadata
|
metadata: z.string(), // Encrypted metadata
|
||||||
daemonState: z.string().optional() // Encrypted daemon state
|
daemonState: z.string().optional(), // Encrypted daemon state
|
||||||
|
dataEncryptionKey: z.string().nullish()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.userId;
|
const userId = request.userId;
|
||||||
const { id, metadata, daemonState } = request.body;
|
const { id, metadata, daemonState, dataEncryptionKey } = request.body;
|
||||||
|
|
||||||
// Check if machine exists (like sessions do)
|
// Check if machine exists (like sessions do)
|
||||||
const machine = await db.machine.findFirst({
|
const machine = await db.machine.findFirst({
|
||||||
@ -39,6 +40,7 @@ export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
|||||||
metadataVersion: machine.metadataVersion,
|
metadataVersion: machine.metadataVersion,
|
||||||
daemonState: machine.daemonState,
|
daemonState: machine.daemonState,
|
||||||
daemonStateVersion: machine.daemonStateVersion,
|
daemonStateVersion: machine.daemonStateVersion,
|
||||||
|
dataEncryptionKey: machine.dataEncryptionKey ? Buffer.from(machine.dataEncryptionKey).toString('base64') : null,
|
||||||
active: machine.active,
|
active: machine.active,
|
||||||
activeAt: machine.lastActiveAt.getTime(), // Return as activeAt for API consistency
|
activeAt: machine.lastActiveAt.getTime(), // Return as activeAt for API consistency
|
||||||
createdAt: machine.createdAt.getTime(),
|
createdAt: machine.createdAt.getTime(),
|
||||||
@ -57,19 +59,30 @@ export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
|||||||
metadataVersion: 1,
|
metadataVersion: 1,
|
||||||
daemonState: daemonState || null,
|
daemonState: daemonState || null,
|
||||||
daemonStateVersion: daemonState ? 1 : 0,
|
daemonStateVersion: daemonState ? 1 : 0,
|
||||||
|
dataEncryptionKey: dataEncryptionKey ? Buffer.from(dataEncryptionKey, 'base64') : undefined,
|
||||||
// Default to offline - in case the user does not start daemon
|
// Default to offline - in case the user does not start daemon
|
||||||
active: false,
|
active: false,
|
||||||
// lastActiveAt and activeAt defaults to now() in schema
|
// lastActiveAt and activeAt defaults to now() in schema
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emit update for new machine
|
// Emit both new-machine and update-machine events for backward compatibility
|
||||||
const updSeq = await allocateUserSeq(userId);
|
const updSeq1 = await allocateUserSeq(userId);
|
||||||
|
const updSeq2 = await allocateUserSeq(userId);
|
||||||
|
|
||||||
|
// Emit new-machine event with all data including dataEncryptionKey
|
||||||
|
const newMachinePayload = buildNewMachineUpdate(newMachine, updSeq1, randomKeyNaked(12));
|
||||||
|
eventRouter.emitUpdate({
|
||||||
|
userId,
|
||||||
|
payload: newMachinePayload
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit update-machine event for backward compatibility (without dataEncryptionKey)
|
||||||
const machineMetadata = {
|
const machineMetadata = {
|
||||||
version: 1,
|
version: 1,
|
||||||
value: metadata
|
value: metadata
|
||||||
};
|
};
|
||||||
const updatePayload = buildUpdateMachineUpdate(newMachine.id, updSeq, randomKeyNaked(12), machineMetadata);
|
const updatePayload = buildUpdateMachineUpdate(newMachine.id, updSeq2, randomKeyNaked(12), machineMetadata);
|
||||||
eventRouter.emitUpdate({
|
eventRouter.emitUpdate({
|
||||||
userId,
|
userId,
|
||||||
payload: updatePayload
|
payload: updatePayload
|
||||||
@ -82,6 +95,7 @@ export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
|||||||
metadataVersion: newMachine.metadataVersion,
|
metadataVersion: newMachine.metadataVersion,
|
||||||
daemonState: newMachine.daemonState,
|
daemonState: newMachine.daemonState,
|
||||||
daemonStateVersion: newMachine.daemonStateVersion,
|
daemonStateVersion: newMachine.daemonStateVersion,
|
||||||
|
dataEncryptionKey: newMachine.dataEncryptionKey ? Buffer.from(newMachine.dataEncryptionKey).toString('base64') : null,
|
||||||
active: newMachine.active,
|
active: newMachine.active,
|
||||||
activeAt: newMachine.lastActiveAt.getTime(), // Return as activeAt for API consistency
|
activeAt: newMachine.lastActiveAt.getTime(), // Return as activeAt for API consistency
|
||||||
createdAt: newMachine.createdAt.getTime(),
|
createdAt: newMachine.createdAt.getTime(),
|
||||||
@ -109,6 +123,7 @@ export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
|||||||
metadataVersion: m.metadataVersion,
|
metadataVersion: m.metadataVersion,
|
||||||
daemonState: m.daemonState,
|
daemonState: m.daemonState,
|
||||||
daemonStateVersion: m.daemonStateVersion,
|
daemonStateVersion: m.daemonStateVersion,
|
||||||
|
dataEncryptionKey: m.dataEncryptionKey ? Buffer.from(m.dataEncryptionKey).toString('base64') : null,
|
||||||
seq: m.seq,
|
seq: m.seq,
|
||||||
active: m.active,
|
active: m.active,
|
||||||
activeAt: m.lastActiveAt.getTime(),
|
activeAt: m.lastActiveAt.getTime(),
|
||||||
@ -147,6 +162,7 @@ export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
|
|||||||
metadataVersion: machine.metadataVersion,
|
metadataVersion: machine.metadataVersion,
|
||||||
daemonState: machine.daemonState,
|
daemonState: machine.daemonState,
|
||||||
daemonStateVersion: machine.daemonStateVersion,
|
daemonStateVersion: machine.daemonStateVersion,
|
||||||
|
dataEncryptionKey: machine.dataEncryptionKey ? Buffer.from(machine.dataEncryptionKey).toString('base64') : null,
|
||||||
seq: machine.seq,
|
seq: machine.seq,
|
||||||
active: machine.active,
|
active: machine.active,
|
||||||
activeAt: machine.lastActiveAt.getTime(),
|
activeAt: machine.lastActiveAt.getTime(),
|
||||||
|
@ -80,6 +80,19 @@ export type UpdateEvent = {
|
|||||||
version: number;
|
version: number;
|
||||||
} | null | undefined;
|
} | null | undefined;
|
||||||
github?: GitHubProfile | null | undefined;
|
github?: GitHubProfile | null | undefined;
|
||||||
|
} | {
|
||||||
|
type: 'new-machine';
|
||||||
|
machineId: string;
|
||||||
|
seq: number;
|
||||||
|
metadata: string;
|
||||||
|
metadataVersion: number;
|
||||||
|
daemonState: string | null;
|
||||||
|
daemonStateVersion: number;
|
||||||
|
dataEncryptionKey: string | null;
|
||||||
|
active: boolean;
|
||||||
|
activeAt: number;
|
||||||
|
createdAt: number;
|
||||||
|
updatedAt: number;
|
||||||
} | {
|
} | {
|
||||||
type: 'update-machine';
|
type: 'update-machine';
|
||||||
machineId: string;
|
machineId: string;
|
||||||
@ -349,6 +362,40 @@ export function buildUpdateAccountUpdate(userId: string, profile: Partial<Accoun
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildNewMachineUpdate(machine: {
|
||||||
|
id: string;
|
||||||
|
seq: number;
|
||||||
|
metadata: string;
|
||||||
|
metadataVersion: number;
|
||||||
|
daemonState: string | null;
|
||||||
|
daemonStateVersion: number;
|
||||||
|
dataEncryptionKey: Uint8Array | null;
|
||||||
|
active: boolean;
|
||||||
|
lastActiveAt: Date;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}, updateSeq: number, updateId: string): UpdatePayload {
|
||||||
|
return {
|
||||||
|
id: updateId,
|
||||||
|
seq: updateSeq,
|
||||||
|
body: {
|
||||||
|
t: 'new-machine',
|
||||||
|
machineId: machine.id,
|
||||||
|
seq: machine.seq,
|
||||||
|
metadata: machine.metadata,
|
||||||
|
metadataVersion: machine.metadataVersion,
|
||||||
|
daemonState: machine.daemonState,
|
||||||
|
daemonStateVersion: machine.daemonStateVersion,
|
||||||
|
dataEncryptionKey: machine.dataEncryptionKey ? Buffer.from(machine.dataEncryptionKey).toString('base64') : null,
|
||||||
|
active: machine.active,
|
||||||
|
activeAt: machine.lastActiveAt.getTime(),
|
||||||
|
createdAt: machine.createdAt.getTime(),
|
||||||
|
updatedAt: machine.updatedAt.getTime()
|
||||||
|
},
|
||||||
|
createdAt: Date.now()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function buildUpdateMachineUpdate(machineId: string, updateSeq: number, updateId: string, metadata?: { value: string; version: number }, daemonState?: { value: string; version: number }): UpdatePayload {
|
export function buildUpdateMachineUpdate(machineId: string, updateSeq: number, updateId: string, metadata?: { value: string; version: number }, daemonState?: { value: string; version: number }): UpdatePayload {
|
||||||
return {
|
return {
|
||||||
id: updateId,
|
id: updateId,
|
||||||
|
@ -64,6 +64,19 @@ declare global {
|
|||||||
version: number;
|
version: number;
|
||||||
} | null | undefined;
|
} | null | undefined;
|
||||||
github?: GitHubProfileType | null | undefined;
|
github?: GitHubProfileType | null | undefined;
|
||||||
|
} | {
|
||||||
|
t: 'new-machine';
|
||||||
|
machineId: string;
|
||||||
|
seq: number;
|
||||||
|
metadata: string;
|
||||||
|
metadataVersion: number;
|
||||||
|
daemonState: string | null;
|
||||||
|
daemonStateVersion: number;
|
||||||
|
dataEncryptionKey: string | null;
|
||||||
|
active: boolean;
|
||||||
|
activeAt: number;
|
||||||
|
createdAt: number;
|
||||||
|
updatedAt: number;
|
||||||
} | {
|
} | {
|
||||||
t: 'update-machine';
|
t: 'update-machine';
|
||||||
machineId: string;
|
machineId: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user