fix(auth): keep pending login credentials inactive until completion

Refs: OPEND-4373
This commit is contained in:
heggria
2026-10-26 16:48:57 +08:00
parent 6ddf919462
commit 99013315cb
3 changed files with 16 additions and 4 deletions
+7 -3
View File
@@ -397,6 +397,7 @@ export interface VelaApiContext {
}
interface VelaProfileShape {
pendingLogin?: unknown;
controlKeyId?: string;
controlKey?: string;
runtimeKey?: string;
@@ -530,7 +531,7 @@ function readRawVelaLoginStatus(
const file = readConfigFile();
const stored = file?.profiles?.[profile];
const storedRuntimeKey = stored?.runtimeKey?.trim() ?? '';
if (!storedRuntimeKey) {
if (!storedRuntimeKey || stored?.pendingLogin) {
return {
loggedIn: false,
loginInFlight,
@@ -747,8 +748,9 @@ function velaControlKeyDigest(controlKey: string): string {
export function readVelaControlApiContext(
env: NodeJS.ProcessEnv = process.env,
configuredEnv: Record<string, string> = {},
allowPendingLogin = false,
): VelaControlApiContext | null {
const context = readRawVelaControlApiContext(env, configuredEnv);
const context = readRawVelaControlApiContext(env, configuredEnv, allowPendingLogin);
if (
context
&& expiredVelaControlKeys.has(velaControlKeyDigest(context.controlKey))
@@ -759,6 +761,7 @@ export function readVelaControlApiContext(
function readRawVelaControlApiContext(
env: NodeJS.ProcessEnv = process.env,
configuredEnv: Record<string, string> = {},
allowPendingLogin = false,
): VelaControlApiContext | null {
const mergedEnv = mergeVelaEnv(env, configuredEnv);
const profile = resolveAmrProfile(mergedEnv);
@@ -778,7 +781,7 @@ function readRawVelaControlApiContext(
const apiContext = readVelaApiContext(env, configuredEnv, snapshot);
const stored = snapshot.stored;
const controlKey = stored?.controlKey?.trim() ?? '';
if (!controlKey) return null;
if (!controlKey || (stored?.pendingLogin && !allowPendingLogin)) return null;
return {
...apiContext,
controlKey,
@@ -825,6 +828,7 @@ export function forgetVelaLogin(env: NodeJS.ProcessEnv = process.env): void {
delete keptProfileConfig.controlKeyId;
delete keptProfileConfig.runtimeKey;
delete keptProfileConfig.user;
delete keptProfileConfig.pendingLogin;
const nextProfiles = { ...parsed.profiles };
nextProfiles[profile] = keptProfileConfig;
writeFileSync(
+1 -1
View File
@@ -1514,7 +1514,7 @@ export function registerVelaRoutes(app: Express, deps: RegisterVelaRoutesDeps):
acceptCredentials: () => {
const mergedEnv = mergeVelaEnv(env, configuredEnv);
const target = readVelaLogoutTarget(mergedEnv);
if (!target?.controlKeyId || !readVelaControlApiContext(env, configuredEnv) || mergedEnv.VELA_RUNTIME_KEY?.trim() || mergedEnv.VELA_CONTROL_KEY?.trim()) throw new Error('saved_credentials_not_active');
if (!target?.controlKeyId || !readVelaControlApiContext(env, configuredEnv, true) || mergedEnv.VELA_RUNTIME_KEY?.trim() || mergedEnv.VELA_CONTROL_KEY?.trim()) throw new Error('saved_credentials_not_active');
clearVelaAuthorizationState();
clearAllVelaLiveAccounts();
clearVelaWalletSnapshotCache();
@@ -108,6 +108,14 @@ describe('resolveAmrProfile', () => {
});
describe('readVelaLoginStatus', () => {
it('does not publish credentials with an unfinished receipt after a daemon restart', () => {
writeConfig({ profiles: { local: { controlKey: 'pending-control', runtimeKey: 'pending-runtime', pendingLogin: { deviceId: 'attempt' }, user: {id: 'u', email: 'u@example.test'} } } });
const env = { OPEN_DESIGN_AMR_PROFILE: 'local' };
expect(readVelaLoginStatus(env)).toMatchObject({loggedIn: false, sessionState: 'signed_out'});
expect(readVelaControlApiContext(env)).toBeNull();
expect(readVelaControlApiContext(env, {}, true)?.controlKey).toBe('pending-control');
});
afterEach(() => clearVelaAuthorizationState());
it('marks only the rejected credential revision as requiring sign-in again', () => {