Clear stale API-key credentials when a password session begins
Why: the auth middleware prefers Bearer keys over the session cookie, so a stale key left in localStorage shadows valid cookie sessions and every request after password login returns 401 (observed at http://192.168.3.2:8788 on 2026-10-11). Evidence: modules/frontend vitest 19 files / 88 tests green; auth.test.ts covers beginSession clearing the stored Bearer before any fetch. Impact: password logins switch to the HttpOnly cookie session cleanly; API-key logins unchanged. Refs: task card feature-login-clear-stale-key in Agent-System-Dev.
This commit is contained in:
@@ -22,6 +22,9 @@ export const auth = {
|
|||||||
apiKey(): string { return storage.getItem(KEY) ?? '' },
|
apiKey(): string { return storage.getItem(KEY) ?? '' },
|
||||||
setKey(key: string) { storage.setItem(KEY, key.trim()) },
|
setKey(key: string) { storage.setItem(KEY, key.trim()) },
|
||||||
clear() { storage.removeItem(KEY) },
|
clear() { storage.removeItem(KEY) },
|
||||||
|
// 密码登录成功后切换到 HttpOnly 会话 cookie:必须清掉残留 Bearer——服务端
|
||||||
|
// 认证中间件 Bearer 优先于会话 cookie,失效残留 key 会把有效会话也判成 401。
|
||||||
|
beginSession() { this.clear() },
|
||||||
// 带凭证的 fetch:未设置密钥时与原生 fetch 等价(嵌入式档无认证)。
|
// 带凭证的 fetch:未设置密钥时与原生 fetch 等价(嵌入式档无认证)。
|
||||||
fetch(input: string, init?: RequestInit): Promise<Response> {
|
fetch(input: string, init?: RequestInit): Promise<Response> {
|
||||||
const key = this.apiKey()
|
const key = this.apiKey()
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export function LoginPage({ onAuthed, onMustChange }: { onAuthed: () => void; on
|
|||||||
setErr(''); setBusy(true)
|
setErr(''); setBusy(true)
|
||||||
try {
|
try {
|
||||||
const me = await api.login(owner.trim(), password)
|
const me = await api.login(owner.trim(), password)
|
||||||
|
// 会话凭证是 HttpOnly cookie;清残留 Bearer,避免其遮蔽有效会话。
|
||||||
|
auth.beginSession()
|
||||||
if (me.mustChangePassword) {
|
if (me.mustChangePassword) {
|
||||||
// 初始/重置密码:先完成强制改密再放行。
|
// 初始/重置密码:先完成强制改密再放行。
|
||||||
onMustChange(me.owner)
|
onMustChange(me.owner)
|
||||||
|
|||||||
@@ -40,4 +40,15 @@ describe('auth 凭证模块', () => {
|
|||||||
expect(auth.apiKey()).toBe('')
|
expect(auth.apiKey()).toBe('')
|
||||||
expect(auth.withKey('/x')).toBe('/x')
|
expect(auth.withKey('/x')).toBe('/x')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('beginSession:密码会话生效后清残留 Bearer,fetch 不再带头', async () => {
|
||||||
|
auth.setKey('ask_stale')
|
||||||
|
auth.beginSession()
|
||||||
|
expect(auth.apiKey()).toBe('')
|
||||||
|
const spy = vi.fn().mockResolvedValue(new Response('{}'))
|
||||||
|
vi.stubGlobal('fetch', spy)
|
||||||
|
await auth.fetch('/api/me')
|
||||||
|
const [, init] = spy.mock.calls[0] as [string, RequestInit | undefined]
|
||||||
|
expect(new Headers(init?.headers ?? {}).get('Authorization')).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user