Skip to content

Commit 673f5c2

Browse files
committed
fix(auth): handle transient getCurrentUser failure gracefully in --token login
If getCurrentUser() throws after the token is validated and stored, the login command now succeeds instead of crashing. The token is already valid (getUserRegions() passed), so a network hiccup on /auth/ should not cause an inconsistent state where the token is saved but the command reports failure. User info display is skipped when unavailable.
1 parent 6710912 commit 673f5c2

File tree

2 files changed

+38
-10
lines changed

2 files changed

+38
-10
lines changed

src/commands/auth/login.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,25 @@ export const loginCommand = buildCommand({
6767
);
6868
}
6969

70-
// Fetch and store user info via /auth/ (works with all token types)
71-
const user = await getCurrentUser();
72-
setUserInfo({
73-
userId: user.id,
74-
email: user.email,
75-
username: user.username,
76-
name: user.name,
77-
});
70+
// Fetch and cache user info via /auth/ (works with all token types).
71+
// A transient failure here must not block login — the token is already valid.
72+
let user: Awaited<ReturnType<typeof getCurrentUser>> | undefined;
73+
try {
74+
user = await getCurrentUser();
75+
setUserInfo({
76+
userId: user.id,
77+
email: user.email,
78+
username: user.username,
79+
name: user.name,
80+
});
81+
} catch {
82+
// Non-fatal: user info is supplementary. Token remains stored and valid.
83+
}
7884

7985
stdout.write(`${success("✓")} Authenticated with API token\n`);
80-
stdout.write(` Logged in as: ${muted(formatUserIdentity(user))}\n`);
86+
if (user) {
87+
stdout.write(` Logged in as: ${muted(formatUserIdentity(user))}\n`);
88+
}
8189
stdout.write(` Config saved to: ${getDbPath()}\n`);
8290
return;
8391
}

test/commands/auth/login.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe("loginCommand.func --token path", () => {
142142
expect(getCurrentUserSpy).not.toHaveBeenCalled();
143143
});
144144

145-
test("--token: always shows 'Logged in as' with user identity", async () => {
145+
test("--token: shows 'Logged in as' when user info fetch succeeds", async () => {
146146
isAuthenticatedSpy.mockResolvedValue(false);
147147
setAuthTokenSpy.mockResolvedValue(undefined);
148148
getUserRegionsSpy.mockResolvedValue([]);
@@ -156,6 +156,26 @@ describe("loginCommand.func --token path", () => {
156156
expect(getStdout()).toContain("only@email.com");
157157
});
158158

159+
test("--token: login succeeds even when getCurrentUser() fails transiently", async () => {
160+
isAuthenticatedSpy.mockResolvedValue(false);
161+
setAuthTokenSpy.mockResolvedValue(undefined);
162+
getUserRegionsSpy.mockResolvedValue([]);
163+
getCurrentUserSpy.mockRejectedValue(new Error("Network error"));
164+
165+
const { context, getStdout } = createContext();
166+
167+
// Must not throw — login should succeed with the stored token
168+
await func.call(context, { token: "valid-token", timeout: 900 });
169+
170+
const out = getStdout();
171+
expect(out).toContain("Authenticated");
172+
// 'Logged in as' is omitted when user info is unavailable
173+
expect(out).not.toContain("Logged in as");
174+
// Token was stored and not cleared
175+
expect(clearAuthSpy).not.toHaveBeenCalled();
176+
expect(setUserInfoSpy).not.toHaveBeenCalled();
177+
});
178+
159179
test("no token: falls through to interactive login", async () => {
160180
isAuthenticatedSpy.mockResolvedValue(false);
161181
runInteractiveLoginSpy.mockResolvedValue(true);

0 commit comments

Comments
 (0)