add provider specific auth modes

This commit is contained in:
miloschwartz
2026-08-05 15:17:50 -04:00
parent c673dce484
commit 2e9bd50172
13 changed files with 178 additions and 78 deletions
+4 -11
View File
@@ -19,9 +19,7 @@ import config from "@server/lib/config";
import { decrypt } from "@server/lib/crypto";
import {
AiProviderAuthType,
AiProviderRoutingMode,
AiProviderType,
resolveAiProviderConfig
applyAiProviderAuthHeaders
} from "@server/lib/aiProviderDefaults";
import {
SESSION_COOKIE_NAME,
@@ -499,12 +497,8 @@ export async function chatCompletions(
const secret = config.getRawConfig().server.secret!;
const apiKey = decrypt(provider.apiKey, secret);
const { upstreamUrl, authType } = resolveAiProviderConfig({
type: provider.type as AiProviderType,
upstreamUrl: provider.upstreamUrl,
authType: provider.authType as AiProviderAuthType | null,
routingMode: provider.routingMode as AiProviderRoutingMode | null
});
const upstreamUrl = provider.upstreamUrl;
const authType = provider.authType as AiProviderAuthType;
if (!upstreamUrl) {
return res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
@@ -541,8 +535,7 @@ export async function chatCompletions(
}
headers[key] = Array.isArray(value) ? value.join(", ") : value;
}
// TODO: temporary hardcoded auth for testing; restore bearer from authType
headers["x-api-key"] = apiKey;
applyAiProviderAuthHeaders(headers, authType, apiKey);
// No dedicated per-request TLS agent is wired up (no extra deps for
// this v1 gateway) - toggle the process-wide Node TLS check instead.
+11 -9
View File
@@ -9,6 +9,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import { resolveAiProviderCreateFields } from "@server/lib/aiProviderDefaults";
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
import { toPublicAiProvider } from "@server/routers/aiProvider/types";
import {
@@ -28,7 +29,7 @@ const bodySchema = z
type: aiProviderTypeSchema,
upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional().nullable(),
authType: aiAuthTypeSchema.optional(),
routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(),
enabled: z.boolean().optional()
@@ -101,8 +102,12 @@ export async function createAiProvider(
const encryptedApiKey = apiKey ? encrypt(apiKey, key) : null;
const apiKeyLastChars = apiKey ? apiKey.slice(-4) : null;
const now = Date.now();
const resolvedRoutingMode =
type === "custom" ? (routingMode ?? "url") : "url";
const resolved = resolveAiProviderCreateFields({
type,
upstreamUrl,
authType,
routingMode
});
const [provider] = await db
.insert(aiProviders)
@@ -110,14 +115,11 @@ export async function createAiProvider(
orgId,
name,
type,
upstreamUrl:
resolvedRoutingMode === "target"
? null
: (upstreamUrl ?? null),
upstreamUrl: resolved.upstreamUrl,
apiKey: encryptedApiKey,
apiKeyLastChars,
authType: authType ?? null,
routingMode: resolvedRoutingMode,
authType: resolved.authType,
routingMode: resolved.routingMode,
skipTlsVerification: skipTlsVerification ?? false,
enabled: enabled ?? true,
createdAt: now,
+4 -15
View File
@@ -1,11 +1,6 @@
import type { AiModel, AiProvider } from "@server/db";
import type { PaginatedResponse } from "@server/types/Pagination";
import {
resolveAiProviderConfig,
type AiProviderAuthType,
type AiProviderRoutingMode,
type AiProviderType
} from "@server/lib/aiProviderDefaults";
import type { AiProviderAuthType } from "@server/lib/aiProviderDefaults";
import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
@@ -13,7 +8,7 @@ export type AiProviderPublic = Omit<AiProvider, "apiKey"> & {
/** Decrypted API key. Only included on get/create/update of a single provider. */
apiKey?: string | null;
effectiveUpstreamUrl: string | null;
effectiveAuthType: AiProviderAuthType | null;
effectiveAuthType: AiProviderAuthType;
};
export type ListAiProvidersResponse = PaginatedResponse<{
@@ -45,12 +40,6 @@ export function toPublicAiProvider(
options?: { includeApiKey?: boolean }
): AiProviderPublic {
const { apiKey: encryptedApiKey, ...rest } = provider;
const resolved = resolveAiProviderConfig({
type: provider.type as AiProviderType,
upstreamUrl: provider.upstreamUrl,
authType: provider.authType as AiProviderAuthType | null,
routingMode: provider.routingMode as AiProviderRoutingMode | null
});
let apiKey: string | null | undefined;
if (options?.includeApiKey) {
@@ -67,7 +56,7 @@ export function toPublicAiProvider(
return {
...rest,
...(options?.includeApiKey ? { apiKey } : {}),
effectiveUpstreamUrl: resolved.upstreamUrl,
effectiveAuthType: resolved.authType
effectiveUpstreamUrl: provider.upstreamUrl,
effectiveAuthType: provider.authType as AiProviderAuthType
};
}
+5 -12
View File
@@ -19,6 +19,7 @@ import {
refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation";
import type {
AiProviderAuthType,
AiProviderRoutingMode,
AiProviderType
} from "@server/lib/aiProviderDefaults";
@@ -31,7 +32,7 @@ const bodySchema = z.strictObject({
name: z.string().nonempty().optional(),
upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional().nullable(),
authType: aiAuthTypeSchema.optional(),
routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(),
enabled: z.boolean().optional()
@@ -116,17 +117,16 @@ export async function updateAiProvider(
body.upstreamUrl !== undefined
? body.upstreamUrl
: existing.upstreamUrl;
const nextAuthType =
const nextAuthType: AiProviderAuthType =
body.authType !== undefined
? body.authType
: (existing.authType ??
(providerType === "custom" ? "bearer" : null));
: (existing.authType as AiProviderAuthType);
const validation = z
.object({
type: aiProviderTypeSchema,
upstreamUrl: z.string().nullable().optional(),
authType: aiAuthTypeSchema.nullable().optional(),
authType: aiAuthTypeSchema,
routingMode: aiRoutingModeSchema.optional()
})
.superRefine((data, ctx) => refineProviderUpstreamFields(data, ctx))
@@ -167,13 +167,6 @@ export async function updateAiProvider(
}
if (body.authType !== undefined) {
updateData.authType = body.authType;
} else if (
providerType === "custom" &&
!existing.authType &&
nextAuthType
) {
// Backfill required authType for custom providers created without one
updateData.authType = nextAuthType;
}
if (body.apiKey !== undefined) {
+4 -2
View File
@@ -1,6 +1,8 @@
import { z } from "zod";
import {
AI_PROVIDER_AUTH_TYPES,
providerRequiresUpstreamUrl,
type AiProviderAuthType,
type AiProviderRoutingMode,
type AiProviderType
} from "@server/lib/aiProviderDefaults";
@@ -17,7 +19,7 @@ export const aiProviderTypeSchema = z.enum([
"custom"
]);
export const aiAuthTypeSchema = z.enum(["bearer"]);
export const aiAuthTypeSchema = z.enum(AI_PROVIDER_AUTH_TYPES);
export const aiRoutingModeSchema = z.enum(["url", "target"]);
@@ -25,7 +27,7 @@ export function refineProviderUpstreamFields(
data: {
type: AiProviderType;
upstreamUrl?: string | null;
authType?: "bearer" | null;
authType?: AiProviderAuthType | null;
routingMode?: AiProviderRoutingMode | null;
},
ctx: z.RefinementCtx