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:
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user