mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 18:08:20 +08:00
760 lines
25 KiB
TypeScript
760 lines
25 KiB
TypeScript
// AI management owns Provider credential binding commands.
|
|
import path from 'node:path';
|
|
|
|
import {
|
|
MODEL_PROVIDER_CREDENTIAL_INSPECTION_OPERATION_ID,
|
|
ModelProviderCredentialAdministrationAuthorizationFenceConflictError,
|
|
ModelProviderCredentialAdministrationMutationConflictError,
|
|
modelProviderCredentialAdministrationOperationId,
|
|
} from '@qinglong/ai/model-provider-credential-administration';
|
|
import {
|
|
MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA,
|
|
InvalidModelProviderCredentialTransitionError,
|
|
ModelProviderCredentialCatalogUnavailableError,
|
|
ModelProviderCredentialTransitionConflictError,
|
|
createModelProviderCredentialTransitionCommand,
|
|
type ModelProviderCredentialTransition,
|
|
} from '@qinglong/ai/model-provider-credential-catalog';
|
|
import { MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA } from '@qinglong/ai/provider-credential';
|
|
import {
|
|
LocalModelProviderCredentialRepository,
|
|
type LocalModelProviderCredentialAuthorizationInput,
|
|
} from '@qinglong/ai/local-model-provider-credential-storage';
|
|
import { assertLocalModelInvocationFeatureActive } from '@qinglong/ai/local-feature-activation';
|
|
import {
|
|
PrivateLocalCommandFileError,
|
|
readPrivateLocalCommandFile,
|
|
} from '@qinglong/local-command-file';
|
|
import {
|
|
AuthenticatedLocalCommandAuthenticationError,
|
|
establishAuthenticatedLocalCommand,
|
|
type AuthenticatedLocalCommand,
|
|
} from '@qinglong/local-owner-console/authenticated-command';
|
|
import {
|
|
LocalSqliteAuthenticatedManagementFenceError,
|
|
commitLocalSqliteSecurityAuditInTransaction,
|
|
confirmLocalSqliteAuthenticatedUserCredentialFence,
|
|
confirmLocalSqliteProjectPolicyFence,
|
|
openLocalSqliteAuthenticatedManagementDatabase,
|
|
type LocalSqliteAuthenticatedManagementDatabase,
|
|
type LocalSqliteAuthenticatedUserCredentialFence,
|
|
type LocalSqliteProfile,
|
|
} from '@qinglong/local-sqlite/authenticated-management';
|
|
import { LocalSqliteProjectPolicyRepository } from '@qinglong/local-sqlite/project-policy';
|
|
import {
|
|
ProjectPolicyEngine,
|
|
ProjectPolicyUnavailableError,
|
|
} from '@qinglong/runtime-core/project-policy';
|
|
import {
|
|
normalizeSecurityPrincipal,
|
|
type SecurityPolicyDecision,
|
|
type SecurityPrincipal,
|
|
} from '@qinglong/runtime-core/security';
|
|
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
|
|
|
|
const MAX_PATH_BYTES = 4_096;
|
|
const UUID_V4_PATTERN =
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
const STRONG_USER_ASSURANCES = new Set([
|
|
'multi_factor',
|
|
'hardware',
|
|
'local_console',
|
|
]);
|
|
|
|
export interface LocalModelProviderCredentialCommandOptions {
|
|
readonly deploymentRoot: string;
|
|
readonly databasePath: string;
|
|
readonly profile: LocalSqliteProfile;
|
|
readonly ownerPepperKeyringDirectory: string;
|
|
readonly credentialFilePath: string;
|
|
readonly busyTimeoutMs?: number;
|
|
}
|
|
|
|
interface BaseRequest {
|
|
readonly requestId: string;
|
|
readonly projectId: string;
|
|
readonly provider: string;
|
|
readonly failureAuditEventId: string;
|
|
}
|
|
|
|
export interface BindLocalModelProviderCredentialCommand {
|
|
readonly schemaVersion: 1;
|
|
readonly operation: 'model-credential.bind';
|
|
readonly options: LocalModelProviderCredentialCommandOptions;
|
|
readonly request: BaseRequest & {
|
|
readonly mutationId: string;
|
|
readonly expectedGeneration: number;
|
|
readonly revision: string;
|
|
readonly secretRef: string;
|
|
};
|
|
}
|
|
|
|
export interface RevokeLocalModelProviderCredentialCommand {
|
|
readonly schemaVersion: 1;
|
|
readonly operation: 'model-credential.revoke';
|
|
readonly options: LocalModelProviderCredentialCommandOptions;
|
|
readonly request: BaseRequest & {
|
|
readonly mutationId: string;
|
|
readonly expectedGeneration: number;
|
|
};
|
|
}
|
|
|
|
export interface InspectLocalModelProviderCredentialCommand {
|
|
readonly schemaVersion: 1;
|
|
readonly operation: 'model-credential.inspect';
|
|
readonly options: LocalModelProviderCredentialCommandOptions;
|
|
readonly request: BaseRequest & {
|
|
readonly auditEventId: string;
|
|
};
|
|
}
|
|
|
|
export type LocalModelProviderCredentialCommand =
|
|
| BindLocalModelProviderCredentialCommand
|
|
| RevokeLocalModelProviderCredentialCommand
|
|
| InspectLocalModelProviderCredentialCommand;
|
|
|
|
export type LocalModelProviderCredentialCommandResult = Readonly<{
|
|
schemaVersion: 1;
|
|
operation: LocalModelProviderCredentialCommand['operation'];
|
|
status?: 'created' | 'existing';
|
|
projectId: string;
|
|
provider: string;
|
|
state: 'bound' | 'revoked' | 'absent';
|
|
generation: number;
|
|
bindingRevision: string | null;
|
|
bindingDigest: string | null;
|
|
transitionDigest: string | null;
|
|
changedAtMs: number | null;
|
|
}>;
|
|
|
|
export class LocalModelProviderCredentialCommandConfigurationError extends TypeError {
|
|
readonly code = 'LOCAL_MODEL_PROVIDER_CREDENTIAL_COMMAND_INVALID';
|
|
|
|
constructor(message: string, readonly cause?: unknown) {
|
|
super(`Local model provider credential command is invalid: ${message}`);
|
|
this.name = 'LocalModelProviderCredentialCommandConfigurationError';
|
|
}
|
|
}
|
|
|
|
export class LocalModelProviderCredentialAuthenticationError extends Error {
|
|
readonly code = 'LOCAL_MODEL_PROVIDER_CREDENTIAL_AUTHENTICATION_REQUIRED';
|
|
|
|
constructor() {
|
|
super('Local model provider credential management requires a strong User');
|
|
this.name = 'LocalModelProviderCredentialAuthenticationError';
|
|
}
|
|
}
|
|
|
|
export class LocalModelProviderCredentialAuthorizationError extends Error {
|
|
readonly code = 'LOCAL_MODEL_PROVIDER_CREDENTIAL_FORBIDDEN';
|
|
|
|
constructor() {
|
|
super('Local model provider credential management is forbidden');
|
|
this.name = 'LocalModelProviderCredentialAuthorizationError';
|
|
}
|
|
}
|
|
|
|
export class LocalModelProviderCredentialUnavailableError extends Error {
|
|
readonly code = 'LOCAL_MODEL_PROVIDER_CREDENTIAL_UNAVAILABLE';
|
|
|
|
constructor() {
|
|
super('Local model provider credential management is unavailable');
|
|
this.name = 'LocalModelProviderCredentialUnavailableError';
|
|
}
|
|
}
|
|
|
|
function exactObject(
|
|
value: unknown,
|
|
expectedKeys: readonly string[],
|
|
label: string,
|
|
): asserts value is Record<string, unknown> {
|
|
if (
|
|
!value ||
|
|
typeof value !== 'object' ||
|
|
Array.isArray(value) ||
|
|
Object.keys(value).sort().join('\0') !==
|
|
[...expectedKeys].sort().join('\0')
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
`${label} shape is invalid`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function boundedPath(value: unknown, label: string): string {
|
|
if (
|
|
typeof value !== 'string' ||
|
|
!path.isAbsolute(value) ||
|
|
path.parse(value).root === value ||
|
|
path.normalize(value) !== value ||
|
|
value.includes('\0') ||
|
|
Buffer.byteLength(value, 'utf8') > MAX_PATH_BYTES
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
`${label} is invalid`,
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function descendant(root: string, candidate: string, label: string): void {
|
|
const relative = path.relative(root, candidate);
|
|
if (
|
|
relative.length === 0 ||
|
|
relative === '..' ||
|
|
relative.startsWith(`..${path.sep}`) ||
|
|
path.isAbsolute(relative)
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
`${label} must be below deploymentRoot`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function normalizeOptions(
|
|
value: unknown,
|
|
): Readonly<LocalModelProviderCredentialCommandOptions> {
|
|
const optional =
|
|
!!value &&
|
|
typeof value === 'object' &&
|
|
!Array.isArray(value) &&
|
|
Object.hasOwn(value, 'busyTimeoutMs')
|
|
? ['busyTimeoutMs']
|
|
: [];
|
|
exactObject(
|
|
value,
|
|
[
|
|
'credentialFilePath',
|
|
'databasePath',
|
|
'deploymentRoot',
|
|
'ownerPepperKeyringDirectory',
|
|
'profile',
|
|
...optional,
|
|
],
|
|
'options',
|
|
);
|
|
const deploymentRoot = boundedPath(value.deploymentRoot, 'deploymentRoot');
|
|
const databasePath = boundedPath(value.databasePath, 'databasePath');
|
|
const ownerPepperKeyringDirectory = boundedPath(
|
|
value.ownerPepperKeyringDirectory,
|
|
'ownerPepperKeyringDirectory',
|
|
);
|
|
const credentialFilePath = boundedPath(
|
|
value.credentialFilePath,
|
|
'credentialFilePath',
|
|
);
|
|
for (const [candidate, label] of [
|
|
[databasePath, 'databasePath'],
|
|
[ownerPepperKeyringDirectory, 'ownerPepperKeyringDirectory'],
|
|
[credentialFilePath, 'credentialFilePath'],
|
|
] as const) {
|
|
descendant(deploymentRoot, candidate, label);
|
|
}
|
|
if (value.profile !== 'edge' && value.profile !== 'standalone') {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'profile is invalid',
|
|
);
|
|
}
|
|
if (
|
|
value.busyTimeoutMs !== undefined &&
|
|
(!Number.isSafeInteger(value.busyTimeoutMs) ||
|
|
(value.busyTimeoutMs as number) < 100 ||
|
|
(value.busyTimeoutMs as number) > 30_000)
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'busyTimeoutMs is invalid',
|
|
);
|
|
}
|
|
return Object.freeze({
|
|
deploymentRoot,
|
|
databasePath,
|
|
profile: value.profile,
|
|
ownerPepperKeyringDirectory,
|
|
credentialFilePath,
|
|
...(value.busyTimeoutMs === undefined
|
|
? {}
|
|
: { busyTimeoutMs: value.busyTimeoutMs as number }),
|
|
});
|
|
}
|
|
|
|
function id(value: unknown, label: string): string {
|
|
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
`${label} is invalid`,
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function uuid(value: unknown, label: string): string {
|
|
if (typeof value !== 'string' || !UUID_V4_PATTERN.test(value)) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
`${label} must be a UUID v4`,
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function normalizeCommand(
|
|
value: unknown,
|
|
): Readonly<LocalModelProviderCredentialCommand> {
|
|
exactObject(
|
|
value,
|
|
['operation', 'options', 'request', 'schemaVersion'],
|
|
'command',
|
|
);
|
|
if (
|
|
value.schemaVersion !== 1 ||
|
|
(value.operation !== 'model-credential.bind' &&
|
|
value.operation !== 'model-credential.revoke' &&
|
|
value.operation !== 'model-credential.inspect')
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'operation is invalid',
|
|
);
|
|
}
|
|
const operation = value.operation;
|
|
const extra =
|
|
operation === 'model-credential.bind'
|
|
? ['expectedGeneration', 'mutationId', 'revision', 'secretRef']
|
|
: operation === 'model-credential.revoke'
|
|
? ['expectedGeneration', 'mutationId']
|
|
: ['auditEventId'];
|
|
exactObject(
|
|
value.request,
|
|
[
|
|
'failureAuditEventId',
|
|
'projectId',
|
|
'provider',
|
|
'requestId',
|
|
...extra,
|
|
],
|
|
'request',
|
|
);
|
|
const request = value.request;
|
|
const normalizedBase = {
|
|
requestId: id(request.requestId, 'requestId'),
|
|
projectId: id(request.projectId, 'projectId'),
|
|
provider: id(request.provider, 'provider'),
|
|
failureAuditEventId: uuid(
|
|
request.failureAuditEventId,
|
|
'failureAuditEventId',
|
|
),
|
|
};
|
|
if (operation === 'model-credential.inspect') {
|
|
const auditEventId = uuid(request.auditEventId, 'auditEventId');
|
|
if (auditEventId === normalizedBase.failureAuditEventId) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'audit identities must be distinct',
|
|
);
|
|
}
|
|
return Object.freeze({
|
|
schemaVersion: 1,
|
|
operation,
|
|
options: normalizeOptions(value.options),
|
|
request: Object.freeze({ ...normalizedBase, auditEventId }),
|
|
});
|
|
}
|
|
const mutationId = uuid(request.mutationId, 'mutationId');
|
|
if (mutationId === normalizedBase.failureAuditEventId) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'audit identities must be distinct',
|
|
);
|
|
}
|
|
if (
|
|
!Number.isSafeInteger(request.expectedGeneration) ||
|
|
(request.expectedGeneration as number) < 0 ||
|
|
(request.expectedGeneration as number) > 2_147_483_646
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'expectedGeneration is invalid',
|
|
);
|
|
}
|
|
const options = normalizeOptions(value.options);
|
|
if (operation === 'model-credential.bind') {
|
|
if (typeof request.secretRef !== 'string') {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'secretRef is invalid',
|
|
);
|
|
}
|
|
return Object.freeze({
|
|
schemaVersion: 1,
|
|
operation,
|
|
options,
|
|
request: Object.freeze({
|
|
...normalizedBase,
|
|
mutationId,
|
|
expectedGeneration: request.expectedGeneration as number,
|
|
revision: id(request.revision, 'revision'),
|
|
secretRef: request.secretRef,
|
|
}),
|
|
});
|
|
}
|
|
return Object.freeze({
|
|
schemaVersion: 1,
|
|
operation,
|
|
options,
|
|
request: Object.freeze({
|
|
...normalizedBase,
|
|
mutationId,
|
|
expectedGeneration: request.expectedGeneration as number,
|
|
}),
|
|
});
|
|
}
|
|
|
|
function readCommandFile(
|
|
candidatePath: string,
|
|
): Readonly<LocalModelProviderCredentialCommand> {
|
|
try {
|
|
return normalizeCommand(readPrivateLocalCommandFile(candidatePath));
|
|
} catch (error) {
|
|
if (error instanceof LocalModelProviderCredentialCommandConfigurationError) {
|
|
throw error;
|
|
}
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'command file cannot be read',
|
|
error instanceof PrivateLocalCommandFileError ? error : undefined,
|
|
);
|
|
}
|
|
}
|
|
|
|
function allowedAudit(
|
|
command: Readonly<LocalModelProviderCredentialCommand>,
|
|
principal: Readonly<SecurityPrincipal>,
|
|
decision: Readonly<SecurityPolicyDecision>,
|
|
occurredAtMs: number,
|
|
): Readonly<SecurityAuditRecord> {
|
|
const eventId =
|
|
command.operation === 'model-credential.inspect'
|
|
? command.request.auditEventId
|
|
: command.request.mutationId;
|
|
return Object.freeze({
|
|
eventId,
|
|
requestId: command.request.requestId,
|
|
operationId:
|
|
command.operation === 'model-credential.inspect'
|
|
? MODEL_PROVIDER_CREDENTIAL_INSPECTION_OPERATION_ID
|
|
: modelProviderCredentialAdministrationOperationId(
|
|
command.operation === 'model-credential.bind' ? 'bind' : 'revoke',
|
|
),
|
|
projectId: command.request.projectId,
|
|
subject: principal.subject,
|
|
authenticationId: principal.authenticationId,
|
|
outcome: 'allowed',
|
|
reasons: decision.reasons,
|
|
fence: decision.fence,
|
|
occurredAtMs,
|
|
});
|
|
}
|
|
|
|
function summary(
|
|
command: Readonly<LocalModelProviderCredentialCommand>,
|
|
transition: Readonly<ModelProviderCredentialTransition> | null,
|
|
status?: 'created' | 'existing',
|
|
): LocalModelProviderCredentialCommandResult {
|
|
return Object.freeze({
|
|
schemaVersion: 1,
|
|
operation: command.operation,
|
|
...(status === undefined ? {} : { status }),
|
|
projectId: command.request.projectId,
|
|
provider: command.request.provider,
|
|
state:
|
|
transition === null
|
|
? ('absent' as const)
|
|
: transition.action === 'bind'
|
|
? ('bound' as const)
|
|
: ('revoked' as const),
|
|
generation: transition?.generation ?? 0,
|
|
bindingRevision: transition?.activeBindingRevision ?? null,
|
|
bindingDigest: transition?.activeBindingDigest ?? null,
|
|
transitionDigest: transition?.transitionDigest ?? null,
|
|
changedAtMs: transition?.changedAtMs ?? null,
|
|
});
|
|
}
|
|
|
|
function now(value: () => number): number {
|
|
const result = value();
|
|
if (!Number.isSafeInteger(result) || result < 0) {
|
|
throw new LocalModelProviderCredentialUnavailableError();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function authorize(
|
|
database: LocalSqliteAuthenticatedManagementDatabase,
|
|
authenticated: Readonly<AuthenticatedLocalCommand>,
|
|
command: Readonly<LocalModelProviderCredentialCommand>,
|
|
nowMs: number,
|
|
): Promise<Readonly<SecurityPolicyDecision>> {
|
|
let principal: Readonly<SecurityPrincipal>;
|
|
try {
|
|
principal = normalizeSecurityPrincipal(authenticated.principal, nowMs);
|
|
} catch {
|
|
throw new LocalModelProviderCredentialAuthenticationError();
|
|
}
|
|
if (
|
|
principal.subject.type !== 'user' ||
|
|
!STRONG_USER_ASSURANCES.has(principal.assurance)
|
|
) {
|
|
throw new LocalModelProviderCredentialAuthenticationError();
|
|
}
|
|
try {
|
|
const decision = await new ProjectPolicyEngine(
|
|
new LocalSqliteProjectPolicyRepository(database.authority),
|
|
).authorize(principal, command.request.projectId, 'secret.manage');
|
|
if (
|
|
decision.effect !== 'allow' ||
|
|
!decision.fence ||
|
|
decision.fence.bindingVersion === null
|
|
) {
|
|
throw new LocalModelProviderCredentialAuthorizationError();
|
|
}
|
|
return decision;
|
|
} catch (error) {
|
|
if (error instanceof LocalModelProviderCredentialAuthorizationError) {
|
|
throw error;
|
|
}
|
|
if (error instanceof ProjectPolicyUnavailableError) {
|
|
throw new LocalModelProviderCredentialUnavailableError();
|
|
}
|
|
throw new LocalModelProviderCredentialUnavailableError();
|
|
}
|
|
}
|
|
|
|
function confirmAuthorization(
|
|
database: LocalSqliteAuthenticatedManagementDatabase,
|
|
credentialFence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
|
|
input: LocalModelProviderCredentialAuthorizationInput,
|
|
): void {
|
|
try {
|
|
confirmLocalSqliteAuthenticatedUserCredentialFence(
|
|
database.authority,
|
|
credentialFence,
|
|
);
|
|
confirmLocalSqliteProjectPolicyFence(
|
|
database.authority,
|
|
input.kind === 'mutation'
|
|
? input.value.command.projectId
|
|
: input.value.projectId,
|
|
input.value.actor,
|
|
input.value.fence,
|
|
);
|
|
} catch {
|
|
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
|
|
}
|
|
try {
|
|
commitLocalSqliteSecurityAuditInTransaction(
|
|
database.authority,
|
|
input.value.audit,
|
|
input.replay,
|
|
);
|
|
} catch {
|
|
throw new ModelProviderCredentialAdministrationMutationConflictError();
|
|
}
|
|
}
|
|
|
|
function failureAudit(
|
|
command: Readonly<LocalModelProviderCredentialCommand>,
|
|
authenticated: Readonly<AuthenticatedLocalCommand> | undefined,
|
|
error: unknown,
|
|
occurredAtMs: number,
|
|
): Readonly<SecurityAuditRecord> | null {
|
|
let outcome: SecurityAuditRecord['outcome'];
|
|
let reason: string;
|
|
if (
|
|
!authenticated ||
|
|
error instanceof AuthenticatedLocalCommandAuthenticationError ||
|
|
error instanceof LocalModelProviderCredentialAuthenticationError
|
|
) {
|
|
outcome = 'authentication_rejected';
|
|
reason = 'credential_rejected';
|
|
} else if (
|
|
error instanceof LocalModelProviderCredentialAuthorizationError ||
|
|
error instanceof LocalSqliteAuthenticatedManagementFenceError ||
|
|
error instanceof
|
|
ModelProviderCredentialAdministrationAuthorizationFenceConflictError
|
|
) {
|
|
outcome = 'denied';
|
|
reason = 'credential_or_policy_fence_rejected';
|
|
} else if (
|
|
error instanceof LocalModelProviderCredentialCommandConfigurationError ||
|
|
error instanceof InvalidModelProviderCredentialTransitionError ||
|
|
error instanceof ModelProviderCredentialTransitionConflictError ||
|
|
error instanceof ModelProviderCredentialAdministrationMutationConflictError
|
|
) {
|
|
outcome = 'denied';
|
|
reason = 'model_provider_credential_conflict';
|
|
} else if (
|
|
error instanceof ModelProviderCredentialCatalogUnavailableError ||
|
|
error instanceof LocalModelProviderCredentialUnavailableError
|
|
) {
|
|
outcome = 'authorization_unavailable';
|
|
reason = 'model_provider_credential_unavailable';
|
|
} else {
|
|
return null;
|
|
}
|
|
return Object.freeze({
|
|
eventId: command.request.failureAuditEventId,
|
|
requestId: command.request.requestId,
|
|
operationId:
|
|
command.operation === 'model-credential.inspect'
|
|
? MODEL_PROVIDER_CREDENTIAL_INSPECTION_OPERATION_ID
|
|
: modelProviderCredentialAdministrationOperationId(
|
|
command.operation === 'model-credential.bind' ? 'bind' : 'revoke',
|
|
),
|
|
projectId: command.request.projectId,
|
|
subject: authenticated?.principal.subject ?? null,
|
|
authenticationId: authenticated?.principal.authenticationId ?? null,
|
|
outcome,
|
|
reasons: Object.freeze([reason]),
|
|
fence: null,
|
|
occurredAtMs,
|
|
});
|
|
}
|
|
|
|
export interface LocalModelProviderCredentialCommandRunner {
|
|
run(commandFilePath: string): Promise<LocalModelProviderCredentialCommandResult>;
|
|
}
|
|
|
|
export interface LocalModelProviderCredentialCommandRunnerDependencies {
|
|
readonly openDatabase: typeof openLocalSqliteAuthenticatedManagementDatabase;
|
|
readonly authenticate: typeof establishAuthenticatedLocalCommand;
|
|
readonly now: () => number;
|
|
}
|
|
|
|
export function createLocalModelProviderCredentialCommandRunner(
|
|
dependencies: LocalModelProviderCredentialCommandRunnerDependencies = {
|
|
openDatabase: openLocalSqliteAuthenticatedManagementDatabase,
|
|
authenticate: establishAuthenticatedLocalCommand,
|
|
now: Date.now,
|
|
},
|
|
): Readonly<LocalModelProviderCredentialCommandRunner> {
|
|
if (
|
|
!dependencies ||
|
|
typeof dependencies !== 'object' ||
|
|
Array.isArray(dependencies) ||
|
|
Object.keys(dependencies).sort().join('\0') !==
|
|
'authenticate\0now\0openDatabase' ||
|
|
typeof dependencies.openDatabase !== 'function' ||
|
|
typeof dependencies.authenticate !== 'function' ||
|
|
typeof dependencies.now !== 'function'
|
|
) {
|
|
throw new LocalModelProviderCredentialCommandConfigurationError(
|
|
'runner dependencies are invalid',
|
|
);
|
|
}
|
|
return Object.freeze({
|
|
async run(commandFilePath: string) {
|
|
const command = readCommandFile(commandFilePath);
|
|
const database = await dependencies.openDatabase({
|
|
databasePath: command.options.databasePath,
|
|
profile: command.options.profile,
|
|
...(command.options.busyTimeoutMs === undefined
|
|
? {}
|
|
: { busyTimeoutMs: command.options.busyTimeoutMs }),
|
|
});
|
|
let authenticated: Readonly<AuthenticatedLocalCommand> | undefined;
|
|
try {
|
|
assertLocalModelInvocationFeatureActive(database.authority.client);
|
|
try {
|
|
authenticated = await dependencies.authenticate(database, {
|
|
deploymentRoot: command.options.deploymentRoot,
|
|
databasePath: command.options.databasePath,
|
|
ownerPepperKeyringDirectory:
|
|
command.options.ownerPepperKeyringDirectory,
|
|
credentialFilePath: command.options.credentialFilePath,
|
|
authenticationNamespace: 'local_model_provider_credential',
|
|
});
|
|
await authenticated.confirm();
|
|
const credentialFence =
|
|
authenticated.databaseFence as Readonly<LocalSqliteAuthenticatedUserCredentialFence>;
|
|
database.confirmUserCredentialFence(credentialFence);
|
|
const observedAtMs = now(dependencies.now);
|
|
const decision = await authorize(
|
|
database,
|
|
authenticated,
|
|
command,
|
|
observedAtMs,
|
|
);
|
|
const audit = allowedAudit(
|
|
command,
|
|
authenticated.principal,
|
|
decision,
|
|
observedAtMs,
|
|
);
|
|
const repository = new LocalModelProviderCredentialRepository(
|
|
database.authority,
|
|
{
|
|
now: dependencies.now,
|
|
authorization: Object.freeze({
|
|
confirm(input: LocalModelProviderCredentialAuthorizationInput) {
|
|
confirmAuthorization(database, credentialFence, input);
|
|
},
|
|
}),
|
|
},
|
|
);
|
|
if (command.operation === 'model-credential.inspect') {
|
|
const transition = await repository.inspectAuthorized({
|
|
projectId: command.request.projectId,
|
|
provider: command.request.provider,
|
|
actor: authenticated.principal.subject,
|
|
fence: decision.fence!,
|
|
audit,
|
|
});
|
|
return summary(command, transition);
|
|
}
|
|
const action =
|
|
command.operation === 'model-credential.bind'
|
|
? ('bind' as const)
|
|
: ('revoke' as const);
|
|
const transitionCommand = createModelProviderCredentialTransitionCommand({
|
|
schema: MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA,
|
|
mutationId: command.request.mutationId,
|
|
projectId: command.request.projectId,
|
|
provider: command.request.provider,
|
|
expectedGeneration: command.request.expectedGeneration,
|
|
action,
|
|
binding:
|
|
command.operation === 'model-credential.bind'
|
|
? Object.freeze({
|
|
schema: MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA,
|
|
projectId: command.request.projectId,
|
|
provider: command.request.provider,
|
|
revision: command.request.revision,
|
|
secretRef: command.request.secretRef,
|
|
scheme: 'bearer' as const,
|
|
})
|
|
: null,
|
|
changedBy: authenticated.principal.subject,
|
|
});
|
|
const result = await repository.commitAuthorized({
|
|
command: transitionCommand,
|
|
actor: authenticated.principal.subject,
|
|
fence: decision.fence!,
|
|
audit,
|
|
});
|
|
return summary(command, result.transition, result.status);
|
|
} catch (error) {
|
|
const audit = failureAudit(
|
|
command,
|
|
authenticated,
|
|
error,
|
|
now(dependencies.now),
|
|
);
|
|
if (audit) await database.securityAudit.record(audit);
|
|
throw error;
|
|
}
|
|
} finally {
|
|
await database.close();
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
export function runLocalModelProviderCredentialCommandFile(
|
|
commandFilePath: string,
|
|
): Promise<LocalModelProviderCredentialCommandResult> {
|
|
return createLocalModelProviderCredentialCommandRunner().run(commandFilePath);
|
|
}
|