mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors). All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke. Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding).
210 lines
6.3 KiB
TypeScript
210 lines
6.3 KiB
TypeScript
import { createHash, randomUUID } from "crypto";
|
|
|
|
import { getDbInstance, rowToCamel } from "./core";
|
|
import { decrypt, encrypt } from "./encryption";
|
|
|
|
export type CommandCodeAuthStatus = "pending" | "received" | "applied" | "expired";
|
|
|
|
export interface CommandCodeAuthMetadata {
|
|
userId?: string;
|
|
userName?: string;
|
|
keyName?: string;
|
|
receivedAt?: string;
|
|
}
|
|
|
|
export interface CommandCodeAuthSafeStatus {
|
|
id: string;
|
|
stateHash: string;
|
|
status: CommandCodeAuthStatus;
|
|
metadata: CommandCodeAuthMetadata | null;
|
|
createdAt: string;
|
|
expiresAt: string;
|
|
receivedAt: string | null;
|
|
appliedAt: string | null;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface ConsumedCommandCodeAuthSecret extends CommandCodeAuthSafeStatus {
|
|
apiKey: string;
|
|
}
|
|
|
|
type DbRunResult = { changes?: number };
|
|
type DbStatement<TRow = unknown> = {
|
|
get: (...params: unknown[]) => TRow | undefined;
|
|
all: (...params: unknown[]) => TRow[];
|
|
run: (...params: unknown[]) => DbRunResult;
|
|
};
|
|
type DbLike = {
|
|
prepare: <TRow = unknown>(sql: string) => DbStatement<TRow>;
|
|
transaction: <T extends (...args: unknown[]) => unknown>(fn: T) => T;
|
|
};
|
|
|
|
type AuthSessionRow = {
|
|
id: string;
|
|
state_hash: string;
|
|
status: CommandCodeAuthStatus;
|
|
encrypted_api_key?: string | null;
|
|
metadata_json?: string | null;
|
|
created_at: string;
|
|
expires_at: string;
|
|
received_at?: string | null;
|
|
applied_at?: string | null;
|
|
updated_at: string;
|
|
};
|
|
|
|
function db(): DbLike {
|
|
return getDbInstance() as unknown as DbLike;
|
|
}
|
|
|
|
export function hashCommandCodeAuthState(state: string): string {
|
|
return createHash("sha256").update(state, "utf8").digest("hex");
|
|
}
|
|
|
|
function nowIso(): string {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function parseMetadata(value: unknown): CommandCodeAuthMetadata | null {
|
|
if (!value) return null;
|
|
// rowToCamel auto-parses the `metadata_json` column and exposes the object under
|
|
// `camel.metadata` (already parsed); accept that directly. Fall back to parsing a
|
|
// raw string for any other caller.
|
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
return value as CommandCodeAuthMetadata;
|
|
}
|
|
if (typeof value !== "string") return null;
|
|
try {
|
|
const parsed = JSON.parse(value) as CommandCodeAuthMetadata;
|
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function toSafeStatus(row: AuthSessionRow): CommandCodeAuthSafeStatus {
|
|
const camel = rowToCamel(row) as Record<string, unknown>;
|
|
return {
|
|
id: String(camel.id),
|
|
stateHash: String(camel.stateHash),
|
|
status: camel.status as CommandCodeAuthStatus,
|
|
metadata: parseMetadata(camel.metadata ?? camel.metadataJson),
|
|
createdAt: String(camel.createdAt),
|
|
expiresAt: String(camel.expiresAt),
|
|
receivedAt: (camel.receivedAt as string | null | undefined) ?? null,
|
|
appliedAt: (camel.appliedAt as string | null | undefined) ?? null,
|
|
updatedAt: String(camel.updatedAt),
|
|
};
|
|
}
|
|
|
|
function markExpiredForState(stateHash: string, now = nowIso()): void {
|
|
db()
|
|
.prepare(
|
|
`UPDATE command_code_auth_sessions
|
|
SET status = 'expired', updated_at = ?
|
|
WHERE state_hash = ? AND status IN ('pending', 'received') AND expires_at <= ?`
|
|
)
|
|
.run(now, stateHash, now);
|
|
}
|
|
|
|
export function createPendingCommandCodeAuthSession(input: {
|
|
stateHash: string;
|
|
expiresAt: string;
|
|
}): CommandCodeAuthSafeStatus {
|
|
const id = randomUUID();
|
|
const now = nowIso();
|
|
db()
|
|
.prepare(
|
|
`INSERT INTO command_code_auth_sessions (
|
|
id, state_hash, status, encrypted_api_key, metadata_json,
|
|
created_at, expires_at, received_at, applied_at, updated_at
|
|
) VALUES (?, ?, 'pending', NULL, NULL, ?, ?, NULL, NULL, ?)`
|
|
)
|
|
.run(id, input.stateHash, now, input.expiresAt, now);
|
|
|
|
const row = db()
|
|
.prepare<AuthSessionRow>("SELECT * FROM command_code_auth_sessions WHERE id = ?")
|
|
.get(id);
|
|
if (!row) throw new Error("Failed to create Command Code auth session");
|
|
return toSafeStatus(row);
|
|
}
|
|
|
|
export function markCommandCodeAuthSessionReceived(input: {
|
|
stateHash: string;
|
|
apiKey: string;
|
|
metadata?: CommandCodeAuthMetadata;
|
|
}): CommandCodeAuthSafeStatus | null {
|
|
const now = nowIso();
|
|
markExpiredForState(input.stateHash, now);
|
|
const metadata: CommandCodeAuthMetadata = {
|
|
...(input.metadata || {}),
|
|
receivedAt: now,
|
|
};
|
|
const encryptedApiKey = encrypt(input.apiKey);
|
|
db()
|
|
.prepare(
|
|
`UPDATE command_code_auth_sessions
|
|
SET status = 'received', encrypted_api_key = ?, metadata_json = ?, received_at = ?, updated_at = ?
|
|
WHERE state_hash = ? AND status IN ('pending', 'received') AND expires_at > ?`
|
|
)
|
|
.run(encryptedApiKey, JSON.stringify(metadata), now, now, input.stateHash, now);
|
|
|
|
return getCommandCodeAuthSessionSafeStatus(input.stateHash);
|
|
}
|
|
|
|
export function getCommandCodeAuthSessionSafeStatus(
|
|
stateHash: string
|
|
): CommandCodeAuthSafeStatus | null {
|
|
markExpiredForState(stateHash);
|
|
const row = db()
|
|
.prepare<AuthSessionRow>("SELECT * FROM command_code_auth_sessions WHERE state_hash = ?")
|
|
.get(stateHash);
|
|
return row ? toSafeStatus(row) : null;
|
|
}
|
|
|
|
export function consumeCommandCodeAuthSecret(
|
|
stateHash: string
|
|
): ConsumedCommandCodeAuthSecret | null {
|
|
const database = db();
|
|
return database.transaction(() => {
|
|
const now = nowIso();
|
|
database
|
|
.prepare(
|
|
`UPDATE command_code_auth_sessions
|
|
SET status = 'expired', updated_at = ?
|
|
WHERE state_hash = ? AND status IN ('pending', 'received') AND expires_at <= ?`
|
|
)
|
|
.run(now, stateHash, now);
|
|
|
|
const row = database
|
|
.prepare<AuthSessionRow>(
|
|
`SELECT * FROM command_code_auth_sessions
|
|
WHERE state_hash = ? AND status = 'received' AND expires_at > ? AND encrypted_api_key IS NOT NULL`
|
|
)
|
|
.get(stateHash, now);
|
|
if (!row?.encrypted_api_key) return null;
|
|
|
|
const apiKey = decrypt(row.encrypted_api_key);
|
|
if (!apiKey) return null;
|
|
|
|
const result = database
|
|
.prepare(
|
|
`UPDATE command_code_auth_sessions
|
|
SET status = 'applied', encrypted_api_key = NULL, applied_at = ?, updated_at = ?
|
|
WHERE id = ? AND status = 'received'`
|
|
)
|
|
.run(now, now, row.id);
|
|
if (!result.changes) return null;
|
|
|
|
return {
|
|
...toSafeStatus({
|
|
...row,
|
|
status: "applied",
|
|
encrypted_api_key: null,
|
|
applied_at: now,
|
|
updated_at: now,
|
|
}),
|
|
apiKey,
|
|
};
|
|
})() as ConsumedCommandCodeAuthSecret | null;
|
|
}
|