This commit is contained in:
Hailey
2025-07-18 13:53:36 -07:00
parent e4625c9014
commit 063a29c3d0
16 changed files with 171 additions and 171 deletions
+1
View File
@@ -6,3 +6,4 @@ EXPO_PUBLIC_LOG_LEVEL=debug
EXPO_PUBLIC_LOG_DEBUG= EXPO_PUBLIC_LOG_DEBUG=
EXPO_PUBLIC_BUNDLE_IDENTIFIER= EXPO_PUBLIC_BUNDLE_IDENTIFIER=
EXPO_PUBLIC_BUNDLE_DATE=0 EXPO_PUBLIC_BUNDLE_DATE=0
EXPO_PUBLIC_USE_OAUTH="false"
+1 -1
View File
@@ -5,7 +5,7 @@
"redirect_uris": [ "redirect_uris": [
"https://bsky.hailey.at/auth/web/callback" "https://bsky.hailey.at/auth/web/callback"
], ],
"scope": "atproto transition:generic transition:email", "scope": "atproto transition:generic transition:email transition:chat.bsky",
"token_endpoint_auth_method": "none", "token_endpoint_auth_method": "none",
"response_types": [ "response_types": [
"code" "code"
@@ -5,7 +5,7 @@
"redirect_uris": [ "redirect_uris": [
"at.hailey.bsky:/auth/native/callback" "at.hailey.bsky:/auth/native/callback"
], ],
"scope": "atproto transition:generic transition:email", "scope": "atproto transition:generic transition:email transition:chat.bsky",
"token_endpoint_auth_method": "none", "token_endpoint_auth_method": "none",
"response_types": [ "response_types": [
"code" "code"
+3 -7
View File
@@ -93,7 +93,7 @@ beginResolveGeolocation()
function InnerApp() { function InnerApp() {
const [isReady, setIsReady] = React.useState(false) const [isReady, setIsReady] = React.useState(false)
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {resumeSession, resumeSessionOauth} = useSessionApi() const {resumeSession} = useSessionApi()
const theme = useColorModeTheme() const theme = useColorModeTheme()
const {_} = useLingui() const {_} = useLingui()
const hasCheckedReferrer = useStarterPackEntry() const hasCheckedReferrer = useStarterPackEntry()
@@ -103,11 +103,7 @@ function InnerApp() {
async function onLaunch(account?: SessionAccount) { async function onLaunch(account?: SessionAccount) {
try { try {
if (account) { if (account) {
if (true) { await resumeSession(account)
await resumeSessionOauth(account)
} else {
await resumeSession(account)
}
} else { } else {
await tryFetchGates(undefined, 'prefer-fresh-gates') await tryFetchGates(undefined, 'prefer-fresh-gates')
} }
@@ -119,7 +115,7 @@ function InnerApp() {
} }
const account = readLastActiveAccount() const account = readLastActiveAccount()
onLaunch(account) onLaunch(account)
}, [resumeSession, resumeSessionOauth]) }, [resumeSession])
useEffect(() => { useEffect(() => {
return listenSessionDropped(() => { return listenSessionDropped(() => {
+3 -7
View File
@@ -72,7 +72,7 @@ beginResolveGeolocation()
function InnerApp() { function InnerApp() {
const [isReady, setIsReady] = React.useState(false) const [isReady, setIsReady] = React.useState(false)
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {resumeSession, resumeSessionOauth} = useSessionApi() const {resumeSession} = useSessionApi()
const theme = useColorModeTheme() const theme = useColorModeTheme()
const {_} = useLingui() const {_} = useLingui()
const hasCheckedReferrer = useStarterPackEntry() const hasCheckedReferrer = useStarterPackEntry()
@@ -82,11 +82,7 @@ function InnerApp() {
async function onLaunch(account?: SessionAccount) { async function onLaunch(account?: SessionAccount) {
try { try {
if (account) { if (account) {
if (true) { await resumeSession(account)
await resumeSessionOauth(account)
} else {
await resumeSession(account)
}
} }
} catch (e) { } catch (e) {
logger.error(`session: resumeSession failed`, {message: e}) logger.error(`session: resumeSession failed`, {message: e})
@@ -96,7 +92,7 @@ function InnerApp() {
} }
const account = readLastActiveAccount() const account = readLastActiveAccount()
onLaunch(account) onLaunch(account)
}, [resumeSession, resumeSessionOauth]) }, [resumeSession])
useEffect(() => { useEffect(() => {
return listenSessionDropped(() => { return listenSessionDropped(() => {
+5 -8
View File
@@ -7,13 +7,14 @@ import {isWeb} from '#/platform/detection'
import {type SessionAccount, useSessionApi} from '#/state/session' import {type SessionAccount, useSessionApi} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {USE_OAUTH} from '../app-info'
import {logEvent} from '../statsig/statsig' import {logEvent} from '../statsig/statsig'
import {type LogEvents} from '../statsig/statsig' import {type LogEvents} from '../statsig/statsig'
export function useAccountSwitcher() { export function useAccountSwitcher() {
const [pendingDid, setPendingDid] = useState<string | null>(null) const [pendingDid, setPendingDid] = useState<string | null>(null)
const {_} = useLingui() const {_} = useLingui()
const {resumeSession, resumeSessionOauth} = useSessionApi() const {resumeSession} = useSessionApi()
const {requestSwitchToAccount} = useLoggedOutViewControls() const {requestSwitchToAccount} = useLoggedOutViewControls()
const onPressSwitchAccount = useCallback( const onPressSwitchAccount = useCallback(
@@ -28,7 +29,7 @@ export function useAccountSwitcher() {
try { try {
setPendingDid(account.did) setPendingDid(account.did)
// TODO: this should be checking if it is an oauth session // TODO: this should be checking if it is an oauth session
if (true || account.accessJwt) { if (USE_OAUTH || account.accessJwt) {
if (isWeb) { if (isWeb) {
// We're switching accounts, which remounts the entire app. // We're switching accounts, which remounts the entire app.
// On mobile, this gets us Home, but on the web we also need reset the URL. // On mobile, this gets us Home, but on the web we also need reset the URL.
@@ -37,11 +38,7 @@ export function useAccountSwitcher() {
// So we change the URL ourselves. The navigator will pick it up on remount. // So we change the URL ourselves. The navigator will pick it up on remount.
history.pushState(null, '', '/') history.pushState(null, '', '/')
} }
if (true) { await resumeSession(account)
await resumeSessionOauth(account)
} else {
await resumeSession(account)
}
logEvent('account:loggedIn', {logContext, withPassword: false}) logEvent('account:loggedIn', {logContext, withPassword: false})
Toast.show(_(msg`Signed in as @${account.handle}`)) Toast.show(_(msg`Signed in as @${account.handle}`))
} else { } else {
@@ -64,7 +61,7 @@ export function useAccountSwitcher() {
setPendingDid(null) setPendingDid(null)
} }
}, },
[_, resumeSession, resumeSessionOauth, requestSwitchToAccount, pendingDid], [_, resumeSession, requestSwitchToAccount, pendingDid],
) )
return {onPressSwitchAccount, pendingDid} return {onPressSwitchAccount, pendingDid}
+7 -4
View File
@@ -6,7 +6,7 @@ import {useSessionApi} from '#/state/session'
import {getWebOAuthClient} from '#/state/session/oauth-web-client' import {getWebOAuthClient} from '#/state/session/oauth-web-client'
export function AuthCallback() { export function AuthCallback() {
const {loginOauth} = useSessionApi() const {login} = useSessionApi()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
// TODO: handle errors, loading state, etc... // TODO: handle errors, loading state, etc...
@@ -18,10 +18,13 @@ export function AuthCallback() {
;(async () => { ;(async () => {
const client = getWebOAuthClient() const client = getWebOAuthClient()
const res = await client.callback(params) const res = await client.callback(params)
await loginOauth(res.session, 'LoginForm') // TODO: right context? await login(
navigation.navigate('Home') {service: '', identifier: '', password: '', oauthSession: res.session},
'LoginForm',
)
navigation.replace('Home')
})() })()
}, [loginOauth, navigation]) }, [login, navigation])
return null return null
} }
+4 -8
View File
@@ -3,6 +3,7 @@ import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {USE_OAUTH} from '#/lib/app-info'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
@@ -24,7 +25,7 @@ export const ChooseAccountForm = ({
const [pendingDid, setPendingDid] = React.useState<string | null>(null) const [pendingDid, setPendingDid] = React.useState<string | null>(null)
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {resumeSession, resumeSessionOauth} = useSessionApi() const {resumeSession} = useSessionApi()
const {setShowLoggedOut} = useLoggedOutViewControls() const {setShowLoggedOut} = useLoggedOutViewControls()
const onSelect = React.useCallback( const onSelect = React.useCallback(
@@ -34,7 +35,7 @@ export const ChooseAccountForm = ({
return return
} }
// TODO: this should be checking if it is an oauth session // TODO: this should be checking if it is an oauth session
if (!true || !account.accessJwt) { if (!USE_OAUTH && !account.accessJwt) {
// Move to login form. // Move to login form.
onSelectAccount(account) onSelectAccount(account)
return return
@@ -46,11 +47,7 @@ export const ChooseAccountForm = ({
} }
try { try {
setPendingDid(account.did) setPendingDid(account.did)
if (true) { await resumeSession(account)
await resumeSessionOauth(account)
} else {
await resumeSession(account)
}
logEvent('account:loggedIn', { logEvent('account:loggedIn', {
logContext: 'ChooseAccountForm', logContext: 'ChooseAccountForm',
withPassword: false, withPassword: false,
@@ -69,7 +66,6 @@ export const ChooseAccountForm = ({
[ [
currentAccount, currentAccount,
resumeSession, resumeSession,
resumeSessionOauth,
pendingDid, pendingDid,
onSelectAccount, onSelectAccount,
setShowLoggedOut, setShowLoggedOut,
+14 -5
View File
@@ -13,6 +13,7 @@ import {
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {USE_OAUTH} from '#/lib/app-info'
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
import {isNetworkError} from '#/lib/strings/errors' import {isNetworkError} from '#/lib/strings/errors'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
@@ -53,14 +54,14 @@ interface LoginFormProps {
} }
export function LoginForm(props: LoginFormProps) { export function LoginForm(props: LoginFormProps) {
if (true) { if (USE_OAUTH) {
return <OAuthLoginForm {...props} /> return <OAuthLoginFormInner {...props} />
} else { } else {
return <LoginFormInner {...props} /> return <LoginFormInner {...props} />
} }
} }
function OAuthLoginForm({ function OAuthLoginFormInner({
error, error,
initialHandle, initialHandle,
onPressBack, onPressBack,
@@ -70,7 +71,7 @@ function OAuthLoginForm({
const [isProcessing, setIsProcessing] = React.useState(false) const [isProcessing, setIsProcessing] = React.useState(false)
const identifierValueRef = useRef<string>(initialHandle || '') const identifierValueRef = useRef<string>(initialHandle || '')
const {loginOauth} = useSessionApi() const {login} = useSessionApi()
const onPressNext = async () => { const onPressNext = async () => {
setIsProcessing(true) setIsProcessing(true)
@@ -86,7 +87,15 @@ function OAuthLoginForm({
const client = getNativeOAuthClient() const client = getNativeOAuthClient()
const res = await client.signIn(identifierValueRef.current) const res = await client.signIn(identifierValueRef.current)
if (res.status === 'success') { if (res.status === 'success') {
await loginOauth(res.session, 'LoginForm') await login(
{
service: '',
identifier: '',
password: '',
oauthSession: res.session,
},
'LoginForm',
)
} else { } else {
logger.error(`Invalid OAuth status: ${res.status}`) logger.error(`Invalid OAuth status: ${res.status}`)
setError(_(msg`An error occurred during authentication.`)) setError(_(msg`An error occurred during authentication.`))
-57
View File
@@ -1,12 +1,9 @@
import { import {
Agent,
type AtpSessionData, type AtpSessionData,
type AtpSessionEvent, type AtpSessionEvent,
BskyAgent, BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {type OutputSchema} from '@atproto/api/dist/client/types/com/atproto/server/getSession'
import {TID} from '@atproto/common-web' import {TID} from '@atproto/common-web'
import {type OAuthSession} from '@atproto/oauth-client-browser'
import {networkRetry} from '#/lib/async/retry' import {networkRetry} from '#/lib/async/retry'
import { import {
@@ -26,7 +23,6 @@ import {
configureModerationForAccount, configureModerationForAccount,
configureModerationForGuest, configureModerationForGuest,
} from './moderation' } from './moderation'
import {BSKY_OAUTH_CLIENT} from './oauth-web-client'
import {type SessionAccount} from './types' import {type SessionAccount} from './types'
import {isSessionExpired, isSignupQueued} from './util' import {isSessionExpired, isSignupQueued} from './util'
@@ -72,22 +68,6 @@ export async function createAgentAndResume(
return agent.prepare(gates, moderation, onSessionChange) return agent.prepare(gates, moderation, onSessionChange)
} }
export async function createAgentOauth(session: OAuthSession) {
const agent = new Agent(session)
const account = await oauthAgentAndSessionToSessionAccountOrThrow(
agent,
session,
)
tryFetchGates(account.did, 'prefer-fresh-gates')
configureModerationForAccount(agent, account)
return {agent, account}
}
export async function resumeAgentOauth(account: SessionAccount) {
const session = await BSKY_OAUTH_CLIENT.restore(account.did)
return await createAgentOauth(session)
}
export async function createAgentAndLogin( export async function createAgentAndLogin(
{ {
service, service,
@@ -207,17 +187,6 @@ export async function createAgentAndCreateAccount(
return agent.prepare(gates, moderation, onSessionChange) return agent.prepare(gates, moderation, onSessionChange)
} }
export async function oauthAgentAndSessionToSessionAccountOrThrow(
agent: Agent,
session: OAuthSession,
): Promise<SessionAccount> {
const account = await oauthAgentAndSessionToSessionAccount(agent, session)
if (!account) {
throw Error('Expected an active session')
}
return account
}
export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount { export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount {
const account = agentToSessionAccount(agent) const account = agentToSessionAccount(agent)
if (!account) { if (!account) {
@@ -226,32 +195,6 @@ export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount {
return account return account
} }
export async function oauthAgentAndSessionToSessionAccount(
agent: Agent,
session: OAuthSession,
): Promise<SessionAccount | undefined> {
let data: OutputSchema
try {
const res = await agent.com.atproto.server.getSession()
data = res.data
} catch (e: any) {
logger.error(e)
return undefined
}
return {
service: session.serverMetadata.issuer,
did: session.did,
handle: data.handle,
email: data.email,
emailConfirmed: data.emailConfirmed,
emailAuthFactor: data.emailAuthFactor,
active: data.active,
status: data.status,
pdsUrl: session.serverMetadata.issuer,
isSelfHosted: !session.server.issuer.startsWith(BSKY_SERVICE), // TODO: is this entryway?
}
}
export function agentToSessionAccount( export function agentToSessionAccount(
agent: BskyAgent, agent: BskyAgent,
): SessionAccount | undefined { ): SessionAccount | undefined {
+41 -64
View File
@@ -12,8 +12,6 @@ import {
createAgentAndCreateAccount, createAgentAndCreateAccount,
createAgentAndLogin, createAgentAndLogin,
createAgentAndResume, createAgentAndResume,
createAgentOauth,
resumeAgentOauth,
sessionAccountToSession, sessionAccountToSession,
} from './agent' } from './agent'
import {getInitialState, reducer} from './reducer' import {getInitialState, reducer} from './reducer'
@@ -21,11 +19,18 @@ import {getInitialState, reducer} from './reducer'
export {isSignupQueued} from './util' export {isSignupQueued} from './util'
import {addSessionDebugLog} from './logging' import {addSessionDebugLog} from './logging'
export type {SessionAccount} from '#/state/session/types' export type {SessionAccount} from '#/state/session/types'
import {USE_OAUTH} from '#/lib/app-info'
import {logger} from '#/logger' import {logger} from '#/logger'
import { import {
type SessionAccount,
type SessionApiContext, type SessionApiContext,
type SessionStateContext, type SessionStateContext,
} from '#/state/session/types' } from '#/state/session/types'
import {
type OauthBskyAppAgent,
oauthCreateAgent,
oauthResumeSession,
} from './oauth-agent'
const StateContext = React.createContext<SessionStateContext>({ const StateContext = React.createContext<SessionStateContext>({
accounts: [], accounts: [],
@@ -38,11 +43,9 @@ const AgentContext = React.createContext<BskyAgent | null>(null)
const ApiContext = React.createContext<SessionApiContext>({ const ApiContext = React.createContext<SessionApiContext>({
createAccount: async () => {}, createAccount: async () => {},
login: async () => {}, login: async () => {},
loginOauth: async () => {},
logoutCurrentAccount: async () => {}, logoutCurrentAccount: async () => {},
logoutEveryAccount: async () => {}, logoutEveryAccount: async () => {},
resumeSession: async () => {}, resumeSession: async () => {},
resumeSessionOauth: async () => {},
removeAccount: () => {}, removeAccount: () => {},
}) })
@@ -98,15 +101,25 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const login = React.useCallback<SessionApiContext['login']>( const login = React.useCallback<SessionApiContext['login']>(
async (params, logContext) => { async (params, logContext) => {
addSessionDebugLog({type: 'method:start', method: 'login'}) addSessionDebugLog({type: 'method:start', method: 'login'})
let agentAccount: {
agent: OauthBskyAppAgent | BskyAppAgent
account: SessionAccount
}
const signal = cancelPendingTask() const signal = cancelPendingTask()
const {agent, account} = await createAgentAndLogin(
params, if (params.oauthSession) {
onAgentSessionChange, agentAccount = await oauthCreateAgent(params.oauthSession)
) } else {
agentAccount = await createAgentAndLogin(params, onAgentSessionChange)
}
if (signal.aborted) { if (signal.aborted) {
return return
} }
const {agent, account} = agentAccount
dispatch({ dispatch({
type: 'switched-to-account', type: 'switched-to-account',
newAgent: agent, newAgent: agent,
@@ -122,28 +135,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
[onAgentSessionChange, cancelPendingTask], [onAgentSessionChange, cancelPendingTask],
) )
const loginOauth = React.useCallback<SessionApiContext['loginOauth']>(
async (session, logContext) => {
const signal = cancelPendingTask()
const {agent, account} = await createAgentOauth(session)
if (signal.aborted) {
return
}
dispatch({
type: 'switched-to-account',
newAgent: agent,
newAccount: account,
})
logger.metric(
'account:loggedIn',
{logContext, withPassword: true},
{statsig: true},
)
addSessionDebugLog({type: 'method:end', method: 'login', account})
},
[cancelPendingTask],
)
const logoutCurrentAccount = React.useCallback< const logoutCurrentAccount = React.useCallback<
SessionApiContext['logoutEveryAccount'] SessionApiContext['logoutEveryAccount']
>( >(
@@ -190,14 +181,27 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
account: storedAccount, account: storedAccount,
}) })
const signal = cancelPendingTask() const signal = cancelPendingTask()
const {agent, account} = await createAgentAndResume(
storedAccount, let agentAccount: {
onAgentSessionChange, agent: OauthBskyAppAgent | BskyAppAgent
) account: SessionAccount
}
if (USE_OAUTH) {
agentAccount = await oauthResumeSession(storedAccount)
} else {
agentAccount = await createAgentAndResume(
storedAccount,
onAgentSessionChange,
)
}
const {agent, account} = agentAccount
if (signal.aborted) { if (signal.aborted) {
return return
} }
dispatch({ dispatch({
type: 'switched-to-account', type: 'switched-to-account',
newAgent: agent, newAgent: agent,
@@ -208,25 +212,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
[onAgentSessionChange, cancelPendingTask], [onAgentSessionChange, cancelPendingTask],
) )
const resumeSessionOauth = React.useCallback<
SessionApiContext['resumeSessionOauth']
>(
async storedAccount => {
const signal = cancelPendingTask()
const {agent, account} = await resumeAgentOauth(storedAccount)
if (signal.aborted) {
return
}
dispatch({
type: 'switched-to-account',
newAgent: agent,
newAccount: account,
})
addSessionDebugLog({type: 'method:end', method: 'resumeSession', account})
},
[cancelPendingTask],
)
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>( const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
account => { account => {
addSessionDebugLog({ addSessionDebugLog({
@@ -271,13 +256,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
a => a.did === synced.currentAccount?.did, a => a.did === synced.currentAccount?.did,
) )
// TODO: this should be checking if it is an oauth session // TODO: this should be checking if it is an oauth session
if (syncedAccount && (true || syncedAccount?.refreshJwt)) { if (syncedAccount && (USE_OAUTH || syncedAccount?.refreshJwt)) {
if (syncedAccount.did !== state.currentAgentState.did) { if (syncedAccount.did !== state.currentAgentState.did) {
if (true) { resumeSession(syncedAccount)
resumeSessionOauth(syncedAccount)
} else {
resumeSession(syncedAccount)
}
} else { } else {
const agent = state.currentAgentState.agent as BskyAgent const agent = state.currentAgentState.agent as BskyAgent
const prevSession = agent.session const prevSession = agent.session
@@ -291,7 +272,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
} }
} }
}) })
}, [state, resumeSession, resumeSessionOauth]) }, [state, resumeSession])
const stateContext = React.useMemo( const stateContext = React.useMemo(
() => ({ () => ({
@@ -308,21 +289,17 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
() => ({ () => ({
createAccount, createAccount,
login, login,
loginOauth,
logoutCurrentAccount, logoutCurrentAccount,
logoutEveryAccount, logoutEveryAccount,
resumeSession, resumeSession,
resumeSessionOauth,
removeAccount, removeAccount,
}), }),
[ [
createAccount, createAccount,
login, login,
loginOauth,
logoutCurrentAccount, logoutCurrentAccount,
logoutEveryAccount, logoutEveryAccount,
resumeSession, resumeSession,
resumeSessionOauth,
removeAccount, removeAccount,
], ],
) )
+1 -2
View File
@@ -3,7 +3,6 @@ import {type Agent, BSKY_LABELER_DID, BskyAgent} from '@atproto/api'
import {IS_TEST_USER} from '#/lib/constants' import {IS_TEST_USER} from '#/lib/constants'
import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities' import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities'
import {readLabelers} from './agent-config' import {readLabelers} from './agent-config'
import {type SessionAccount} from './types'
export function configureModerationForGuest() { export function configureModerationForGuest() {
// This global mutation is *only* OK because this code is only relevant for testing. // This global mutation is *only* OK because this code is only relevant for testing.
@@ -14,7 +13,7 @@ export function configureModerationForGuest() {
export async function configureModerationForAccount( export async function configureModerationForAccount(
agent: Agent | BskyAgent, agent: Agent | BskyAgent,
account: SessionAccount, account: {did: string; handle?: string},
) { ) {
// This global mutation is *only* OK because this code is only relevant for testing. // This global mutation is *only* OK because this code is only relevant for testing.
// Don't add any other global behavior here! // Don't add any other global behavior here!
+87
View File
@@ -0,0 +1,87 @@
import {Agent} from '@atproto/api'
import {type OutputSchema} from '@atproto/api/dist/client/types/com/atproto/server/getSession'
import {type OAuthSession} from '@atproto/oauth-client-browser'
import {BSKY_SERVICE} from '#/lib/constants'
import {tryFetchGates} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {configureModerationForAccount} from './moderation'
import {BSKY_OAUTH_CLIENT} from './oauth-web-client'
import {type SessionAccount} from './types'
export async function oauthCreateAgent(session: OAuthSession) {
const agent = new OauthBskyAppAgent(session)
const gates = tryFetchGates(session.did, 'prefer-fresh-gates')
const moderation = configureModerationForAccount(agent, session)
return agent.prepare(gates, moderation)
}
export async function oauthResumeSession(account: SessionAccount) {
const session = await BSKY_OAUTH_CLIENT.restore(account.did)
return await oauthCreateAgent(session)
}
export async function oauthAgentAndSessionToSessionAccountOrThrow(
agent: Agent,
session: OAuthSession,
): Promise<SessionAccount> {
const account = await oauthAgentAndSessionToSessionAccount(agent, session)
if (!account) {
throw Error('Expected an active session')
}
return account
}
export async function oauthAgentAndSessionToSessionAccount(
agent: Agent,
session: OAuthSession,
): Promise<SessionAccount | undefined> {
let data: OutputSchema
try {
const res = await agent.com.atproto.server.getSession()
data = res.data
} catch (e: any) {
logger.error(e)
return undefined
}
return {
service: session.serverMetadata.issuer,
did: session.did,
handle: data.handle,
email: data.email,
emailConfirmed: data.emailConfirmed,
emailAuthFactor: data.emailAuthFactor,
active: data.active,
status: data.status,
pdsUrl: session.serverMetadata.issuer,
isSelfHosted: !session.server.issuer.startsWith(BSKY_SERVICE), // TODO: is this entryway?
}
}
export class OauthBskyAppAgent extends Agent {
#session: OAuthSession
#account?: SessionAccount
constructor(session: OAuthSession) {
super(session)
this.#session = session
}
async prepare(gates: Promise<void>, moderation: Promise<void>) {
// we have to await account fetching, since we need a separate call to getSession. this doesn't get returned in the
// OAuthSession itself, unlike the old agent
const account = await oauthAgentAndSessionToSessionAccountOrThrow(
this,
this.#session,
)
this.#account = account
await Promise.all([gates, moderation])
return {account, agent: this}
}
// does nothing, but aligning with BskyAppAgent
dispose() {}
}
+1 -1
View File
@@ -9,7 +9,7 @@ export function createNativeOAuthClient() {
client_name: 'Bluesky Native App (Hailey Demo)', client_name: 'Bluesky Native App (Hailey Demo)',
client_uri: 'https://bsky.hailey.at', client_uri: 'https://bsky.hailey.at',
redirect_uris: ['at.hailey.bsky:/auth/native/callback'], redirect_uris: ['at.hailey.bsky:/auth/native/callback'],
scope: 'atproto transition:generic transition:email', scope: 'atproto transition:generic transition:email transition:chat.bsky',
token_endpoint_auth_method: 'none', token_endpoint_auth_method: 'none',
response_types: ['code'], response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'], grant_types: ['authorization_code', 'refresh_token'],
+1 -1
View File
@@ -14,7 +14,7 @@ export function createWebOAuthClient() {
client_name: 'Bluesky (Hailey Demo)', client_name: 'Bluesky (Hailey Demo)',
client_uri: 'https://bsky.hailey.at', client_uri: 'https://bsky.hailey.at',
redirect_uris: ['https://bsky.hailey.at/auth/web/callback'], redirect_uris: ['https://bsky.hailey.at/auth/web/callback'],
scope: 'atproto transition:generic transition:email', scope: 'atproto transition:generic transition:email transition:chat.bsky',
token_endpoint_auth_method: 'none', token_endpoint_auth_method: 'none',
response_types: ['code'], response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'], grant_types: ['authorization_code', 'refresh_token'],
+1 -5
View File
@@ -31,13 +31,10 @@ export type SessionApiContext = {
identifier: string identifier: string
password: string password: string
authFactorToken?: string | undefined authFactorToken?: string | undefined
oauthSession?: OAuthSession
}, },
logContext: LogEvents['account:loggedIn']['logContext'], logContext: LogEvents['account:loggedIn']['logContext'],
) => Promise<void> ) => Promise<void>
loginOauth: (
session: OAuthSession,
logContext: LogEvents['account:loggedIn']['logContext'],
) => Promise<void>
logoutCurrentAccount: ( logoutCurrentAccount: (
logContext: LogEvents['account:loggedOut']['logContext'], logContext: LogEvents['account:loggedOut']['logContext'],
) => void ) => void
@@ -45,6 +42,5 @@ export type SessionApiContext = {
logContext: LogEvents['account:loggedOut']['logContext'], logContext: LogEvents['account:loggedOut']['logContext'],
) => void ) => void
resumeSession: (account: SessionAccount) => Promise<void> resumeSession: (account: SessionAccount) => Promise<void>
resumeSessionOauth: (account: SessionAccount) => Promise<void>
removeAccount: (account: SessionAccount) => void removeAccount: (account: SessionAccount) => void
} }