feat: unify device auth + pairing
This commit is contained in:
74
src/gateway/exec-approval-manager.ts
Normal file
74
src/gateway/exec-approval-manager.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import type { ExecApprovalDecision } from "../infra/exec-approvals.js";
|
||||
|
||||
export type ExecApprovalRequestPayload = {
|
||||
command: string;
|
||||
cwd?: string | null;
|
||||
host?: string | null;
|
||||
security?: string | null;
|
||||
ask?: string | null;
|
||||
agentId?: string | null;
|
||||
resolvedPath?: string | null;
|
||||
sessionKey?: string | null;
|
||||
};
|
||||
|
||||
export type ExecApprovalRecord = {
|
||||
id: string;
|
||||
request: ExecApprovalRequestPayload;
|
||||
createdAtMs: number;
|
||||
expiresAtMs: number;
|
||||
resolvedAtMs?: number;
|
||||
decision?: ExecApprovalDecision;
|
||||
resolvedBy?: string | null;
|
||||
};
|
||||
|
||||
type PendingEntry = {
|
||||
record: ExecApprovalRecord;
|
||||
resolve: (decision: ExecApprovalDecision) => void;
|
||||
reject: (err: Error) => void;
|
||||
timer: ReturnType<typeof setTimeout>;
|
||||
};
|
||||
|
||||
export class ExecApprovalManager {
|
||||
private pending = new Map<string, PendingEntry>();
|
||||
|
||||
create(request: ExecApprovalRequestPayload, timeoutMs: number): ExecApprovalRecord {
|
||||
const now = Date.now();
|
||||
const id = randomUUID();
|
||||
const record: ExecApprovalRecord = {
|
||||
id,
|
||||
request,
|
||||
createdAtMs: now,
|
||||
expiresAtMs: now + timeoutMs,
|
||||
};
|
||||
return record;
|
||||
}
|
||||
|
||||
async waitForDecision(record: ExecApprovalRecord, timeoutMs: number): Promise<ExecApprovalDecision> {
|
||||
return await new Promise<ExecApprovalDecision>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.pending.delete(record.id);
|
||||
resolve("deny");
|
||||
}, timeoutMs);
|
||||
this.pending.set(record.id, { record, resolve, reject, timer });
|
||||
});
|
||||
}
|
||||
|
||||
resolve(recordId: string, decision: ExecApprovalDecision, resolvedBy?: string | null): boolean {
|
||||
const pending = this.pending.get(recordId);
|
||||
if (!pending) return false;
|
||||
clearTimeout(pending.timer);
|
||||
pending.record.resolvedAtMs = Date.now();
|
||||
pending.record.decision = decision;
|
||||
pending.record.resolvedBy = resolvedBy ?? null;
|
||||
this.pending.delete(recordId);
|
||||
pending.resolve(decision);
|
||||
return true;
|
||||
}
|
||||
|
||||
getSnapshot(recordId: string): ExecApprovalRecord | null {
|
||||
const entry = this.pending.get(recordId);
|
||||
return entry?.record ?? null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user