feat: store app logins agents can read

Username and password live in the local vault and the project vault, keyed by a stable app id rather than a host or port. Agents fetch one named secret or login when they need it. Lists stay metadata, and project reveals stay audit-logged.

Authored By: TDvorak <[email protected]>
This commit is contained in:
Tomas Dvorak committed 2026-10-29 09:31:02 +01:00
1 parent 61a45cc48f
commit 2a49de2db5
40 files changed
+1815 -43

No files matched your search

@@ -98,6 +98,7 @@ export type { Invitation } from './models/Invitation';
export type { InvitationID } from './models/InvitationID';
export type { InvokeFunctionRequest } from './models/InvokeFunctionRequest';
export type { JobID } from './models/JobID';
export type { LoginApp } from './models/LoginApp';
export type { MeResponse } from './models/MeResponse';
export type { MetricPoint } from './models/MetricPoint';
export type { MetricSeriesResponse } from './models/MetricSeriesResponse';
@@ -114,6 +115,8 @@ export { PresignedURL } from './models/PresignedURL';
export { PresignObjectRequest } from './models/PresignObjectRequest';
export type { Project } from './models/Project';
export type { ProjectID } from './models/ProjectID';
export type { ProjectLogin } from './models/ProjectLogin';
export type { ProjectLoginListResponse } from './models/ProjectLoginListResponse';
export { ProjectMember } from './models/ProjectMember';
export type { ProjectOverview } from './models/ProjectOverview';
export type { ProjectSecret } from './models/ProjectSecret';
@@ -121,6 +124,7 @@ export type { ProjectSecretListResponse } from './models/ProjectSecretListRespon
export type { ProjectSummary } from './models/ProjectSummary';
export type { PublishEventRequest } from './models/PublishEventRequest';
export type { ReadinessStatus } from './models/ReadinessStatus';
export type { RevealProjectLoginResponse } from './models/RevealProjectLoginResponse';
export type { RevealProjectSecretResponse } from './models/RevealProjectSecretResponse';
export type { RybbitMetricItem } from './models/RybbitMetricItem';
export type { RybbitSite } from './models/RybbitSite';
@@ -131,6 +135,7 @@ export { ScheduledJobRun } from './models/ScheduledJobRun';
export type { ScheduledJobRunListResponse } from './models/ScheduledJobRunListResponse';
export type { SchemaQuery } from './models/SchemaQuery';
export type { SecretName } from './models/SecretName';
export type { SetProjectLoginRequest } from './models/SetProjectLoginRequest';
export type { SetProjectSecretRequest } from './models/SetProjectSecretRequest';
export type { TableQuery } from './models/TableQuery';
export type { TelemetryComponent } from './models/TelemetryComponent';
@@ -253,6 +258,7 @@ export { $Invitation } from './schemas/$Invitation';
export { $InvitationID } from './schemas/$InvitationID';
export { $InvokeFunctionRequest } from './schemas/$InvokeFunctionRequest';
export { $JobID } from './schemas/$JobID';
export { $LoginApp } from './schemas/$LoginApp';
export { $MeResponse } from './schemas/$MeResponse';
export { $MetricPoint } from './schemas/$MetricPoint';
export { $MetricSeriesResponse } from './schemas/$MetricSeriesResponse';
@@ -269,6 +275,8 @@ export { $PresignedURL } from './schemas/$PresignedURL';
export { $PresignObjectRequest } from './schemas/$PresignObjectRequest';
export { $Project } from './schemas/$Project';
export { $ProjectID } from './schemas/$ProjectID';
export { $ProjectLogin } from './schemas/$ProjectLogin';
export { $ProjectLoginListResponse } from './schemas/$ProjectLoginListResponse';
export { $ProjectMember } from './schemas/$ProjectMember';
export { $ProjectOverview } from './schemas/$ProjectOverview';
export { $ProjectSecret } from './schemas/$ProjectSecret';
@@ -276,6 +284,7 @@ export { $ProjectSecretListResponse } from './schemas/$ProjectSecretListResponse
export { $ProjectSummary } from './schemas/$ProjectSummary';
export { $PublishEventRequest } from './schemas/$PublishEventRequest';
export { $ReadinessStatus } from './schemas/$ReadinessStatus';
export { $RevealProjectLoginResponse } from './schemas/$RevealProjectLoginResponse';
export { $RevealProjectSecretResponse } from './schemas/$RevealProjectSecretResponse';
export { $RybbitMetricItem } from './schemas/$RybbitMetricItem';
export { $RybbitSite } from './schemas/$RybbitSite';
@@ -286,6 +295,7 @@ export { $ScheduledJobRun } from './schemas/$ScheduledJobRun';
export { $ScheduledJobRunListResponse } from './schemas/$ScheduledJobRunListResponse';
export { $SchemaQuery } from './schemas/$SchemaQuery';
export { $SecretName } from './schemas/$SecretName';
export { $SetProjectLoginRequest } from './schemas/$SetProjectLoginRequest';
export { $SetProjectSecretRequest } from './schemas/$SetProjectSecretRequest';
export { $TableQuery } from './schemas/$TableQuery';
export { $TelemetryComponent } from './schemas/$TelemetryComponent';
@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Stable app id. Not a URL and not a port.
*/
export type LoginApp = string;
@@ -0,0 +1,20 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProjectLogin = {
id: string;
/**
* Stable app id. The current origin is separate and may change.
*/
app: string;
username: string;
/**
* Current URL of the app, if known. Not the identity.
*/
origin: string;
notes: string;
created_at: string;
updated_at: string;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ProjectLogin } from './ProjectLogin';
export type ProjectLoginListResponse = {
items: Array<ProjectLogin>;
};
@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type RevealProjectLoginResponse = {
app: string;
username: string;
password: string;
origin: string;
};
@@ -0,0 +1,14 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type SetProjectLoginRequest = {
username: string;
/**
* Plaintext — stored AES-256-GCM encrypted, never returned by list.
*/
password: string;
origin?: string;
notes?: string;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $LoginApp = {
type: 'string',
description: `Stable app id. Not a URL and not a port.`,
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$',
} as const;
@@ -0,0 +1,41 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ProjectLogin = {
properties: {
id: {
type: 'string',
isRequired: true,
format: 'uuid',
},
app: {
type: 'string',
description: `Stable app id. The current origin is separate and may change.`,
isRequired: true,
},
username: {
type: 'string',
isRequired: true,
},
origin: {
type: 'string',
description: `Current URL of the app, if known. Not the identity.`,
isRequired: true,
},
notes: {
type: 'string',
isRequired: true,
},
created_at: {
type: 'string',
isRequired: true,
format: 'date-time',
},
updated_at: {
type: 'string',
isRequired: true,
format: 'date-time',
},
},
} as const;
@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ProjectLoginListResponse = {
properties: {
items: {
type: 'array',
contains: {
type: 'ProjectLogin',
},
isRequired: true,
},
},
} as const;
@@ -0,0 +1,24 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $RevealProjectLoginResponse = {
properties: {
app: {
type: 'string',
isRequired: true,
},
username: {
type: 'string',
isRequired: true,
},
password: {
type: 'string',
isRequired: true,
},
origin: {
type: 'string',
isRequired: true,
},
},
} as const;
@@ -0,0 +1,23 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $SetProjectLoginRequest = {
properties: {
username: {
type: 'string',
isRequired: true,
},
password: {
type: 'string',
description: `Plaintext — stored AES-256-GCM encrypted, never returned by list.`,
isRequired: true,
},
origin: {
type: 'string',
},
notes: {
type: 'string',
},
},
} as const;
@@ -2,9 +2,13 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ProjectLogin } from '../models/ProjectLogin';
import type { ProjectLoginListResponse } from '../models/ProjectLoginListResponse';
import type { ProjectSecret } from '../models/ProjectSecret';
import type { ProjectSecretListResponse } from '../models/ProjectSecretListResponse';
import type { RevealProjectLoginResponse } from '../models/RevealProjectLoginResponse';
import type { RevealProjectSecretResponse } from '../models/RevealProjectSecretResponse';
import type { SetProjectLoginRequest } from '../models/SetProjectLoginRequest';
import type { SetProjectSecretRequest } from '../models/SetProjectSecretRequest';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
@@ -109,4 +113,112 @@ export class SecretsService {
},
});
}
/**
* Lists app logins — username and origin, never the password.
* @returns ProjectLoginListResponse Project logins (password omitted)
* @throws ApiError
*/
public static listProjectLogins({
projectId,
}: {
projectId: string,
}): CancelablePromise<ProjectLoginListResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/projects/{projectID}/logins',
path: {
'projectID': projectId,
},
errors: {
401: `Error response`,
},
});
}
/**
* Creates or replaces an app login. The password is AES-256-GCM at rest. Omitted origin/notes keep existing values. The app id is stable — it is not a host or port.
* @returns ProjectLogin Login stored
* @throws ApiError
*/
public static setProjectLogin({
projectId,
app,
requestBody,
}: {
projectId: string,
/**
* Stable app id. Not a URL and not a port.
*/
app: string,
requestBody: SetProjectLoginRequest,
}): CancelablePromise<ProjectLogin> {
return __request(OpenAPI, {
method: 'PUT',
url: '/projects/{projectID}/logins/{app}',
path: {
'projectID': projectId,
'app': app,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error response`,
401: `Error response`,
},
});
}
/**
* @returns void
* @throws ApiError
*/
public static deleteProjectLogin({
projectId,
app,
}: {
projectId: string,
/**
* Stable app id. Not a URL and not a port.
*/
app: string,
}): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/projects/{projectID}/logins/{app}',
path: {
'projectID': projectId,
'app': app,
},
errors: {
401: `Error response`,
404: `Error response`,
},
});
}
/**
* Returns the username, password, and origin for one app. Every call is audit-logged. Read one login when you need it. Do not copy it into chat or a .env file.
* @returns RevealProjectLoginResponse Decrypted login
* @throws ApiError
*/
public static revealProjectLogin({
projectId,
app,
}: {
projectId: string,
/**
* Stable app id. Not a URL and not a port.
*/
app: string,
}): CancelablePromise<RevealProjectLoginResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/projects/{projectID}/logins/{app}/reveal',
path: {
'projectID': projectId,
'app': app,
},
errors: {
401: `Error response`,
404: `Error response`,
},
});
}
}