mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
* ✨ support cli api key auth * 🔒 reject invalid x-api-key without fallback auth * ♻️ clean up cli api key auth diff * ♻️ clean up cli auth command diff * ♻️ clean up remaining cli auth diff * ♻️ split stored auth token fields * ♻️ trim connect auth surface * ♻️ drop redundant jwt user id carry-over * ♻️ trim auth test wording diff * 🐛 fix api key model imports * 🐛 fix api key util subpath import * 🔐 chore(cli): use env-only api key auth * ♻️ refactor(cli): simplify auth credential flow * ✨ feat: simplify cli api key login flow * 🐛 fix(cli): prefer jwt for webapi auth * ♻️ refactor(cli): trim auth http diff * 🐛 fix(cli): skip api key auth expiry handling * 🐛 fix(cli): restore non-jwt expiry handling * ♻️ refactor(cli): trim connect auth expired diff * ♻️ refactor(cli): trim login comment diff * ♻️ refactor(cli): trim resolve token comment diff * ♻️ refactor(cli): restore connect expiry flow * ♻️ refactor(cli): trim login api key message * 🐛 fix(cli): support api key gateway auth * ♻️ refactor(cli): restore resolve token comment * ♻️ refactor(cli): trim test-only auth diffs * ♻️ refactor(cli): restore resolve token comments * ✅ test(cli): add api key expiry coverage * 🐛 fix cli auth server resolution and gateway auth * ♻️ prune auth fix diff noise * ♻️ unify cli server url precedence * ♻️ simplify device gateway auth tests * ✅ add gateway auth edge case coverage * ✅ remove low-value gateway auth test * 🐛 fix api key context test mock typing
68 lines
1.9 KiB
TypeScript
68 lines
1.9 KiB
TypeScript
import { resolveServerUrl } from '../settings';
|
|
import { loadCredentials, saveCredentials, type StoredCredentials } from './credentials';
|
|
|
|
const CLIENT_ID = 'lobehub-cli';
|
|
|
|
/**
|
|
* Get a valid access token, refreshing if expired.
|
|
* Returns null if no credentials or refresh fails.
|
|
*/
|
|
export async function getValidToken(): Promise<{ credentials: StoredCredentials } | null> {
|
|
const credentials = loadCredentials();
|
|
if (!credentials) return null;
|
|
|
|
// Check if token is still valid (with 60s buffer)
|
|
if (credentials.expiresAt && Date.now() / 1000 < credentials.expiresAt - 60) {
|
|
return { credentials };
|
|
}
|
|
|
|
// Token expired — try refresh
|
|
if (!credentials.refreshToken) return null;
|
|
|
|
const serverUrl = resolveServerUrl();
|
|
const refreshed = await refreshAccessToken(serverUrl, credentials.refreshToken);
|
|
if (!refreshed) return null;
|
|
|
|
const updated: StoredCredentials = {
|
|
accessToken: refreshed.access_token,
|
|
expiresAt: refreshed.expires_in
|
|
? Math.floor(Date.now() / 1000) + refreshed.expires_in
|
|
: undefined,
|
|
refreshToken: refreshed.refresh_token || credentials.refreshToken,
|
|
};
|
|
|
|
saveCredentials(updated);
|
|
return { credentials: updated };
|
|
}
|
|
|
|
interface TokenResponse {
|
|
access_token: string;
|
|
expires_in?: number;
|
|
refresh_token?: string;
|
|
token_type: string;
|
|
}
|
|
|
|
async function refreshAccessToken(
|
|
serverUrl: string,
|
|
refreshToken: string,
|
|
): Promise<TokenResponse | null> {
|
|
try {
|
|
const res = await fetch(`${serverUrl}/oidc/token`, {
|
|
body: new URLSearchParams({
|
|
client_id: CLIENT_ID,
|
|
grant_type: 'refresh_token',
|
|
refresh_token: refreshToken,
|
|
}),
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
method: 'POST',
|
|
});
|
|
|
|
const body = (await res.json()) as TokenResponse & { error?: string };
|
|
|
|
if (!res.ok || body.error || !body.access_token) return null;
|
|
|
|
return body;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|