mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
feat(ai): add the storage for an OpenAI-compatible gateway provider
Three dedicated User columns — base URL, optional bearer, model — for a provider whose endpoint the user chooses. They are separate from the OpenAI columns on purpose: the OpenAI arm pins api.openai.com, so it must share no column with a user-supplied host, and a gateway must never receive the key issued for OpenAI. The bearer joins the encrypted-column registry and the rotation script; the three columns join the account-wipe reset set.
This commit is contained in:
1 parent
d10ae9e71c
commit
d11019aebc
5 files changed
+40
-1
No files matched your search
@@ -0,0 +1,17 @@
|
||||
-- The OpenAI-compatible gateway provider (#470): a user-configured endpoint
|
||||
-- that speaks `/v1/chat/completions` — LiteLLM, OpenRouter, vLLM, or anything
|
||||
-- else on that wire — with its own base URL, its own optional bearer, and its
|
||||
-- own model.
|
||||
--
|
||||
-- Three dedicated columns instead of reusing `ai_base_url` / the OpenAI key.
|
||||
-- The OpenAI arm pins `api.openai.com` on purpose, so it must have no column
|
||||
-- in common with a provider whose host the user chooses; and the gateway must
|
||||
-- never receive the key the user issued for OpenAI. Separating the storage
|
||||
-- makes both properties structural rather than a comment someone can delete.
|
||||
--
|
||||
-- All three are nullable and additive: an install that never configures the
|
||||
-- provider carries three NULLs and resolves exactly as before.
|
||||
|
||||
ALTER TABLE "users" ADD COLUMN "ai_compat_base_url" TEXT;
|
||||
ALTER TABLE "users" ADD COLUMN "ai_compat_key_encrypted" TEXT;
|
||||
ALTER TABLE "users" ADD COLUMN "ai_compat_model" TEXT;
|
||||
@@ -311,6 +311,18 @@ model User {
|
||||
// before storage.
|
||||
aiOpenaiKeyEncrypted String? @map("ai_openai_key_encrypted")
|
||||
|
||||
// v1.33.1 (#470) — the OpenAI-compatible gateway provider (LiteLLM,
|
||||
// OpenRouter, vLLM, any endpoint speaking `/v1/chat/completions`).
|
||||
// Three DEDICATED columns rather than a reuse of `aiBaseUrl` /
|
||||
// `aiOpenaiKeyEncrypted`: the OpenAI arm pins `api.openai.com` and must
|
||||
// never be able to read a user-supplied host, and the gateway credential
|
||||
// must never be the OpenAI key. Separate columns make that structural
|
||||
// instead of a comment. The base URL passes the same SSRF floor as the
|
||||
// Local provider (`isPublicUrl` unless the operator allowlisted the host).
|
||||
aiCompatBaseUrl String? @map("ai_compat_base_url")
|
||||
aiCompatKeyEncrypted String? @map("ai_compat_key_encrypted")
|
||||
aiCompatModel String? @map("ai_compat_model")
|
||||
|
||||
// v1.4.16 phase B5b — ordered fallback chain for multi-provider AI
|
||||
// redundancy. Each entry is `{ providerType, priority, enabled }`,
|
||||
// sorted by ascending priority at runtime. Credentials live in the
|
||||
|
||||
@@ -170,7 +170,8 @@ async function main() {
|
||||
// "ouraClientSecretEncrypted" "stravaClientIdEncrypted"
|
||||
// "stravaClientSecretEncrypted" "stravaAccessTokenEncrypted"
|
||||
// "stravaRefreshTokenEncrypted" "aiAnthropicKeyEncrypted"
|
||||
// "aiLocalKeyEncrypted" "aiOpenaiKeyEncrypted" "insuranceNumberEncrypted"
|
||||
// "aiLocalKeyEncrypted" "aiOpenaiKeyEncrypted" "aiCompatKeyEncrypted"
|
||||
// "insuranceNumberEncrypted"
|
||||
const userFields = [
|
||||
"codexAccessTokenEncrypted",
|
||||
"codexRefreshTokenEncrypted",
|
||||
@@ -200,6 +201,8 @@ async function main() {
|
||||
"aiAnthropicKeyEncrypted",
|
||||
"aiLocalKeyEncrypted",
|
||||
"aiOpenaiKeyEncrypted",
|
||||
// v1.33.1 (#470) — the OpenAI-compatible gateway's optional bearer.
|
||||
"aiCompatKeyEncrypted",
|
||||
"insuranceNumberEncrypted",
|
||||
// v1.23 — TOTP shared secret (second factor).
|
||||
"totpSecretEncrypted",
|
||||
|
||||
@@ -81,6 +81,10 @@ export const ENCRYPTED_COLUMNS: readonly EncryptedColumn[] = [
|
||||
{ model: "User", field: "aiAnthropicKeyEncrypted", kind: "string" },
|
||||
{ model: "User", field: "aiLocalKeyEncrypted", kind: "string" },
|
||||
{ model: "User", field: "aiOpenaiKeyEncrypted", kind: "string" },
|
||||
// v1.33.1 (#470) — the OpenAI-compatible gateway provider's optional bearer.
|
||||
// A dedicated column, never the OpenAI key: the gateway host is user-chosen,
|
||||
// so the credential that reaches it must be one the user issued for it.
|
||||
{ model: "User", field: "aiCompatKeyEncrypted", kind: "string" },
|
||||
{ model: "User", field: "insuranceNumberEncrypted", kind: "string" },
|
||||
// v1.23 — TOTP shared secret (second factor). String ciphertext like the
|
||||
// other User credential columns.
|
||||
|
||||
@@ -262,6 +262,9 @@ export const USER_RESET = {
|
||||
aiAnthropicKeyEncrypted: null,
|
||||
aiLocalKeyEncrypted: null,
|
||||
aiOpenaiKeyEncrypted: null,
|
||||
aiCompatBaseUrl: null,
|
||||
aiCompatKeyEncrypted: null,
|
||||
aiCompatModel: null,
|
||||
aiProviderChain: Prisma.DbNull,
|
||||
aiResponseTimeoutSeconds: null,
|
||||
disableCoach: false,
|
||||
|
||||
Reference in new issue
Block a user