feat(v1.1.7-secrets): secrets SDK + table + admin API + dashboard
Encrypted per-app secrets, reachable from scripts as
secrets::{get,set,delete,list}(name) and managed from the dashboard
Secrets tab. Values are AES-256-GCM-sealed with the process master key
(picloud_shared::crypto) before they touch Postgres; the repo only ever
sees ciphertext + nonce. JSON round-trip preserves Rhai types.
- migration 0023_secrets.sql (PRIMARY KEY (app_id, name)).
- SecretsService trait (picloud-shared) + SecretsServiceImpl + repo
(manager-core), wired into the Services bundle and Rhai engine.
- Capability::AppSecretsRead/Write (→ script:read / script:write); no
new Scope variants (seven-scope commitment).
- Admin API GET/POST/DELETE /apps/{id}/secrets (list returns names +
updated_at, never values).
- build_app now takes a MasterKey, sourced from PICLOUD_SECRET_KEY in
main.rs; test callers pass a fixed test key.
- 64 KB value cap (PICLOUD_SECRET_MAX_VALUE_BYTES); no ServiceEvent
emission (secret writes don't fire triggers, by design).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,6 +292,11 @@ export interface UpdateTopicInput {
|
||||
auth_mode?: TopicAuthMode;
|
||||
}
|
||||
|
||||
export interface SecretListItem {
|
||||
name: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ExecutionResult {
|
||||
status: number;
|
||||
headers: Record<string, string>;
|
||||
@@ -714,6 +719,27 @@ export const api = {
|
||||
)
|
||||
},
|
||||
|
||||
secrets: {
|
||||
// List returns names + last-modified ONLY — values never leave the
|
||||
// server (v1.1.7).
|
||||
list: (idOrSlug: string) =>
|
||||
adminRequest<{ secrets: SecretListItem[]; next_cursor: string | null }>(
|
||||
`/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/secrets`
|
||||
),
|
||||
// `value` is any JSON value; the dashboard sends a single-line
|
||||
// string. Overwrites if the name already exists.
|
||||
set: (idOrSlug: string, name: string, value: unknown) =>
|
||||
adminRequest<null>(`/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/secrets`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, value })
|
||||
}),
|
||||
remove: (idOrSlug: string, name: string) =>
|
||||
adminRequest<null>(
|
||||
`/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/secrets/${encodeURIComponent(name)}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
},
|
||||
|
||||
execute: async (
|
||||
id: string,
|
||||
body: unknown,
|
||||
|
||||
Reference in New Issue
Block a user