Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: OAuth requiring reauthentication when switching accounts

xan.lol 1ae3d814 0db7d147

+57 -4
+3 -2
src/lib/hooks/useAccountSwitcher.ts
··· 4 4 5 5 import {logger} from '#/logger' 6 6 import {type SessionAccount, useSessionApi} from '#/state/session' 7 + import {canAttemptSessionResume} from '#/state/session/util' 7 8 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 8 9 import * as Toast from '#/components/Toast' 9 10 import {useAnalytics} from '#/analytics' ··· 28 29 } 29 30 try { 30 31 setPendingDid(account.did) 31 - if (account.accessJwt) { 32 + if (canAttemptSessionResume(account)) { 32 33 // Store navigation state before switching so user stays on the same page 33 34 storeNavigationStateForAccountSwitch() 34 35 await resumeSession(account, true) ··· 42 43 } 43 44 } catch (e: any) { 44 45 logger.error(`switch account: selectAccount failed`, { 45 - message: e.message, 46 + message: e instanceof Error ? e.message : String(e), 46 47 }) 47 48 requestSwitchToAccount({requestedAccount: account.did}) 48 49 Toast.show(_(msg`Please sign in as @${account.handle}`), {
+5 -2
src/screens/Login/ChooseAccountForm.tsx
··· 6 6 7 7 import {logger} from '#/logger' 8 8 import {type SessionAccount, useSession, useSessionApi} from '#/state/session' 9 + import {canAttemptSessionResume} from '#/state/session/util' 9 10 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 10 11 import {atoms as a, web} from '#/alf' 11 12 import {AccountList} from '#/components/AccountList' ··· 36 37 // The session API isn't resilient to race conditions so let's just ignore this. 37 38 return 38 39 } 39 - if (!account.isOauthSession && !account.accessJwt) { 40 + if (!canAttemptSessionResume(account)) { 40 41 // Move to login form. 41 42 onSelectAccount(account) 42 43 return ··· 96 97 </TextField.LabelText> 97 98 )} 98 99 <AccountList 99 - onSelectAccount={onSelect} 100 + onSelectAccount={account => { 101 + void onSelect(account) 102 + }} 100 103 onSelectOther={() => onSelectAccount()} 101 104 pendingDid={pendingDid} 102 105 />
+45
src/state/session/__tests__/util-test.ts
··· 1 + import {describe, expect, it, jest} from '@jest/globals' 2 + 3 + jest.mock('#/state/persisted', () => ({ 4 + get: jest.fn(), 5 + })) 6 + 7 + jest.mock('../agent', () => ({ 8 + sessionAccountToSession: jest.fn(), 9 + })) 10 + 11 + import {canAttemptSessionResume} from '../util' 12 + 13 + describe('canAttemptSessionResume', () => { 14 + it('allows OAuth accounts without persisted JWTs', () => { 15 + expect( 16 + canAttemptSessionResume({ 17 + service: 'https://bsky.social', 18 + did: 'did:plc:alice', 19 + handle: 'alice.test', 20 + isOauthSession: true, 21 + }), 22 + ).toBe(true) 23 + }) 24 + 25 + it('allows legacy accounts with a refresh token even if access token is missing', () => { 26 + expect( 27 + canAttemptSessionResume({ 28 + service: 'https://bsky.social', 29 + did: 'did:plc:alice', 30 + handle: 'alice.test', 31 + refreshJwt: 'refresh-token', 32 + }), 33 + ).toBe(true) 34 + }) 35 + 36 + it('rejects accounts with no resumable session state', () => { 37 + expect( 38 + canAttemptSessionResume({ 39 + service: 'https://bsky.social', 40 + did: 'did:plc:alice', 41 + handle: 'alice.test', 42 + }), 43 + ).toBe(false) 44 + }) 45 + })
+4
src/state/session/util.ts
··· 27 27 return false 28 28 } 29 29 30 + export function canAttemptSessionResume(account: SessionAccount) { 31 + return !!(account.isOauthSession || account.refreshJwt || account.accessJwt) 32 + } 33 + 30 34 export function isSessionExpired(account: SessionAccount) { 31 35 if (account.accessJwt) { 32 36 return isJwtExpired(account.accessJwt)