Audit provider sign-in launches by employee

This commit is contained in:
ZacharyZhang-NY committed 2026-05-23 22:20:16 -04:00
1 parent 335d57b77f
commit de90d6e944
6 files changed
+61 -10

No files matched your search

@@ -121,7 +121,7 @@ export function registerIpcHandlers(deps: IpcDeps): void {
});
ipcMain.handle(IPC_CHANNELS.launchProviderSignIn, (_event, input) => {
try {
providers.launchSignInWindow(input.orgId, input.providerId);
providers.launchSignInWindow(input);
return ok(null);
} catch (error) {
return fail("provider_sign_in_failed", error instanceof Error ? error.message : String(error));
@@ -1,5 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import type { CancelProviderResponseInput, CreateProviderResponseInput, RevokeProviderCredentialInput, SaveProviderCredentialInput, StreamProviderResponseInput, UpdateProviderPolicyInput } from "../../shared/commands";
import type { CancelProviderResponseInput, CreateProviderResponseInput, LaunchProviderSignInInput, RevokeProviderCredentialInput, SaveProviderCredentialInput, StreamProviderResponseInput, UpdateProviderPolicyInput } from "../../shared/commands";
import { FEATURE_FLAG_KEY, PROVIDER_AUTH_TYPE, PROVIDER_DATA_POLICY, PROVIDER_EXECUTION_MODE, PROVIDER_REQUEST_STATE, type ProviderAuthType, type ProviderExecutionMode } from "../../shared/domain";
import type { ProviderStreamRendererEvent } from "../../shared/provider-request-domain";
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
@@ -14,7 +14,7 @@ import { ProviderRateLimitRegistry } from "./provider-rate-limit-registry";
import { ProviderResponseObserver } from "./provider-response-observer";
import { hashPrompt, readProviderResponseId, requireStreamText } from "./provider-response-utils";
import { ProviderRequestStore } from "./provider-request-store";
import { launchProviderSignInWindow } from "./provider-sign-in-window";
import { launchProviderSignInWindow, type LaunchProviderSignInWindowInput } from "./provider-sign-in-window";
import type { ProviderObservedRateLimitState } from "../../shared/provider-runtime-domain";
const AUTH_STATE_CONFIGURED = "configured";
@@ -41,6 +41,7 @@ export class ProviderService {
private readonly db: DatabaseSync,
private readonly adapters: ProviderAdapterRegistry = providerAdapters,
vault: ProviderSecretVault = new SecretVault(),
private readonly launchSignIn: (input: LaunchProviderSignInWindowInput) => void = launchProviderSignInWindow,
) {
this.audit = new AuditService(db);
this.authBroker = new AuthBrokerService(db, vault);
@@ -141,23 +142,24 @@ export class ProviderService {
}
}
launchSignInWindow(orgId: string, providerId: string): void {
launchSignInWindow(input: LaunchProviderSignInInput): void {
this.assertEmployee(input.orgId, input.employeeId);
const provider = this.db.prepare(`
select id, provider_key, label, auth_type
from providers
where id = ? and org_id = ?
`).get(providerId, orgId) as { id: string; provider_key: string; label: string; auth_type: string } | undefined;
`).get(input.providerId, input.orgId) as { id: string; provider_key: string; label: string; auth_type: string } | undefined;
if (!provider) throw new Error("Provider was not found.");
const catalogEntry = this.requireCatalogEntry(provider.provider_key);
if (!catalogEntry.signInUrl) throw new Error(`${provider.label} does not support browser sign-in.`);
launchProviderSignInWindow({ providerId: provider.id, label: provider.label, signInUrl: catalogEntry.signInUrl });
this.launchSignIn({ providerId: provider.id, label: provider.label, signInUrl: catalogEntry.signInUrl });
this.audit.record({
orgId,
orgId: input.orgId,
entityType: "provider",
entityId: provider.id,
actorType: "employee",
actorId: null,
actorId: input.employeeId,
action: "sign_in_window_opened",
payload: { providerKey: provider.provider_key, authType: provider.auth_type },
});
@@ -465,6 +467,12 @@ export class ProviderService {
return entry;
}
private assertEmployee(orgId: string, employeeId: string): void {
const employee = this.db.prepare("select id from employees where id = ? and org_id = ?")
.get(employeeId, orgId) as { id: string } | undefined;
if (!employee) throw new Error("Provider sign-in employee was not found.");
}
private recordProviderHealth(providerId: string, orgId: string, healthState: string, error: string | null): void {
this.db.prepare(`
update providers
@@ -131,6 +131,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
{tab === "providers" && (
<ProvidersPanel
orgId={orgId}
employeeId={employee.id}
providers={providers}
catalog={snapshot.providerCatalog}
agents={agents}
@@ -15,6 +15,7 @@ import type { SnapshotActions } from "../../app/useSnapshot";
interface ProvidersPanelProps {
orgId: string;
employeeId: string;
providers: ProviderRecord[];
catalog: ProviderCatalogEntry[];
agents: AgentRecord[];
@@ -32,7 +33,7 @@ interface CloudFields {
const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" };
export function ProvidersPanel({ orgId, providers, catalog, agents, providerRateLimits, providerRequests, actions }: ProvidersPanelProps) {
export function ProvidersPanel({ orgId, employeeId, providers, catalog, agents, providerRateLimits, providerRequests, actions }: ProvidersPanelProps) {
const [active, setActive] = useState<ProviderRecord | null>(null);
const [signInLaunched, setSignInLaunched] = useState(false);
const connected = providers.filter((provider) => provider.enabled);
@@ -60,7 +61,7 @@ export function ProvidersPanel({ orgId, providers, catalog, agents, providerRate
const launchSignIn = () => {
if (!active) return;
setSignInLaunched(true);
void actions.run(() => window.agentCompany.launchProviderSignIn({ orgId, providerId: active.id }));
void actions.run(() => window.agentCompany.launchProviderSignIn({ orgId, providerId: active.id, employeeId }));
};
return (
+1
View File
@@ -170,6 +170,7 @@ export interface RevokeProviderCredentialInput {
export interface LaunchProviderSignInInput {
orgId: string;
providerId: string;
employeeId: string;
}
export interface CheckProviderHealthInput {
@@ -169,6 +169,46 @@ describe("provider service", () => {
connection.close();
});
test("audits provider sign-in launches with the employee actor", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
const snapshot = new SnapshotService(connection.db);
let launched: { providerId: string; label: string; signInUrl: string } | null = null;
const providers = new ProviderService(connection.db, providerAdapters, testVault, (input) => { launched = input; });
let orgId = "";
let employeeId = "";
let copilotProviderId = "";
connection.transaction(() => {
orgId = createOrganization(organization);
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
copilotProviderId = providerId(connection, orgId, "github-copilot");
});
expect(() => providers.launchSignInWindow({
orgId,
providerId: copilotProviderId,
employeeId: "emp_missing",
})).toThrow("Provider sign-in employee was not found.");
connection.transaction(() => providers.launchSignInWindow({ orgId, providerId: copilotProviderId, employeeId }));
const state = snapshot.load();
expect(launched).toMatchObject({
providerId: copilotProviderId,
label: "GitHub Copilot",
signInUrl: "https://github.com/login",
});
expect(state.auditEvents).toContainEqual(expect.objectContaining({
entityType: "provider",
entityId: copilotProviderId,
actorType: "employee",
actorId: employeeId,
action: "sign_in_window_opened",
}));
connection.close();
});
test("records provider request results through direct execution adapters", async () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);