feat(FN-1933): show masked API key hints in auth settings

- Add optional keyHint field to the auth provider client interface
- Return masked API key hints from /api/auth/status for API-key providers
- Render key hints in both Settings and onboarding provider cards when authenticated
- Add auth-key-hint styling using existing dashboard design tokens
This commit is contained in:
Fusion
2026-04-18 05:24:10 -07:00
committed by gsxdsm
parent 4341ceb52a
commit 2ec668059a
5 changed files with 41 additions and 2 deletions

View File

@@ -853,6 +853,8 @@ export interface AuthProvider {
authenticated: boolean;
/** Whether this provider uses OAuth or API key authentication */
type?: "oauth" | "api_key";
/** Masked hint of the stored API key (first 3 + bullets + last 4 chars) */
keyHint?: string;
}
/** Fetch authentication status for all OAuth providers */

View File

@@ -1633,6 +1633,9 @@ export function ModelOnboardingModal({
{providerInfo.description}
</span>
<ProviderStatusBadge status={getProviderStatus(provider)} />
{provider.authenticated && provider.keyHint && (
<span className="auth-key-hint">Key: {provider.keyHint}</span>
)}
</div>
<div className="onboarding-provider-card__actions onboarding-provider-card__actions--api-key">
<ApiKeyEntryForm

View File

@@ -2978,6 +2978,9 @@ export function SettingsModal({
>
Active
</span>
{provider.authenticated && provider.keyHint && (
<span className="auth-key-hint">Key: {provider.keyHint}</span>
)}
</div>
{provider.type === "api_key" ? (
<div className="auth-apikey-section">

View File

@@ -3821,6 +3821,17 @@ body {
color: var(--color-error);
padding-right: 4px;
}
/* === Key Hint === */
.auth-key-hint {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-muted);
display: inline-block;
margin-top: var(--space-xs);
user-select: none;
}
.settings-empty-state {
padding: 12px 20px;
font-size: 13px;

View File

@@ -18017,6 +18017,18 @@ function registerAuthRoutes(router: Router, authStorage?: AuthStorageLike): void
return authStorage;
}
/**
* Mask an API key for safe display.
* - If key length <= 8: return 8 bullets (never reveal short keys)
* - Otherwise: first 3 chars + 5 bullets + last 4 chars
*/
function maskApiKey(key: string): string {
if (key.length <= 8) {
return "••••••••";
}
return key.slice(0, 3) + "•••••" + key.slice(-4);
}
/**
* Track in-progress login flows to prevent concurrent logins for the same provider.
* Maps provider ID → AbortController for the active login.
@@ -18027,14 +18039,14 @@ function registerAuthRoutes(router: Router, authStorage?: AuthStorageLike): void
* GET /api/auth/status
* Returns list of all providers with their authentication status and type.
* Includes both OAuth-backed and API-key-backed providers.
* Response: { providers: [{ id, name, authenticated, type }] }
* Response: { providers: [{ id, name, authenticated, type, keyHint? }] }
*/
router.get("/auth/status", (_req, res) => {
try {
const storage = getAuthStorage();
storage.reload();
const oauthProviders = storage.getOAuthProviders();
const providers: { id: string; name: string; authenticated: boolean; type: "oauth" | "api_key" }[] = oauthProviders.map((p) => ({
const providers: { id: string; name: string; authenticated: boolean; type: "oauth" | "api_key"; keyHint?: string }[] = oauthProviders.map((p) => ({
id: p.id,
name: p.name,
authenticated: storage.hasAuth(p.id),
@@ -18047,11 +18059,19 @@ function registerAuthRoutes(router: Router, authStorage?: AuthStorageLike): void
for (const p of apiKeyProviders) {
// Skip if already listed as an OAuth provider (avoid duplicates)
if (providers.some((existing) => existing.id === p.id)) continue;
let keyHint: string | undefined;
if (storage.get) {
const cred = storage.get(p.id);
if (cred?.type === "api_key" && cred?.key) {
keyHint = maskApiKey(cred.key);
}
}
providers.push({
id: p.id,
name: p.name,
authenticated: storage.hasApiKey ? storage.hasApiKey(p.id) : false,
type: "api_key" as const,
keyHint,
});
}
}