maybe working login...
This commit is contained in:
@@ -14,12 +14,15 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {createFullHandle} from '#/lib/strings/handles'
|
import {createFullHandle} from '#/lib/strings/handles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||||
import {useSessionApi} from '#/state/session'
|
import {useSessionApi} from '#/state/session'
|
||||||
|
import {getNativeOAuthClient, getWebOAuthClient} from '#/state/session/oauth'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -32,9 +35,6 @@ import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticke
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
import {useGate} from '#/lib/statsig/statsig'
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {getNativeOAuthClient, getWebOAuthClient} from '#/state/session/oauth'
|
|
||||||
|
|
||||||
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||||
|
|
||||||
@@ -61,11 +61,18 @@ export function LoginForm(props: LoginFormProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function OAuthLoginForm({error, initialHandle, onPressBack}: LoginFormProps) {
|
function OAuthLoginForm({
|
||||||
|
error,
|
||||||
|
initialHandle,
|
||||||
|
onPressBack,
|
||||||
|
setError,
|
||||||
|
}: LoginFormProps) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
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 onPressNext = async () => {
|
const onPressNext = async () => {
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
@@ -74,9 +81,15 @@ function OAuthLoginForm({error, initialHandle, onPressBack}: LoginFormProps) {
|
|||||||
} else {
|
} else {
|
||||||
const client = getNativeOAuthClient()
|
const client = getNativeOAuthClient()
|
||||||
const res = await client.signIn(identifierValueRef.current)
|
const res = await client.signIn(identifierValueRef.current)
|
||||||
// redirect after result
|
if (res.status === 'success') {
|
||||||
|
await loginOauth(res.session, 'LoginForm')
|
||||||
|
} else {
|
||||||
|
logger.error(`Invalid OAuth status: ${res.status}`)
|
||||||
|
setError(_(msg`An error occurred during authentication.`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
|
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import {AtpSessionData, AtpSessionEvent, BskyAgent} from '@atproto/api'
|
import {
|
||||||
|
Agent,
|
||||||
|
type AtpSessionData,
|
||||||
|
type AtpSessionEvent,
|
||||||
|
BskyAgent,
|
||||||
|
} 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 {
|
||||||
@@ -19,10 +26,9 @@ import {
|
|||||||
configureModerationForAccount,
|
configureModerationForAccount,
|
||||||
configureModerationForGuest,
|
configureModerationForGuest,
|
||||||
} from './moderation'
|
} from './moderation'
|
||||||
import {SessionAccount} from './types'
|
|
||||||
import {isSessionExpired, isSignupQueued} from './util'
|
|
||||||
import {BSKY_OAUTH_CLIENT} from './oauth'
|
import {BSKY_OAUTH_CLIENT} from './oauth'
|
||||||
import {ExpoOAuthClient} from 'expo-atproto-auth'
|
import {type SessionAccount} from './types'
|
||||||
|
import {isSessionExpired, isSignupQueued} from './util'
|
||||||
|
|
||||||
export function createPublicAgent() {
|
export function createPublicAgent() {
|
||||||
configureModerationForGuest() // Side effect but only relevant for tests
|
configureModerationForGuest() // Side effect but only relevant for tests
|
||||||
@@ -66,6 +72,22 @@ 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,
|
||||||
@@ -185,6 +207,17 @@ 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) {
|
||||||
@@ -193,6 +226,32 @@ 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 {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ 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'
|
||||||
@@ -36,9 +38,11 @@ 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: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -118,6 +122,28 @@ 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']
|
||||||
>(
|
>(
|
||||||
@@ -182,6 +208,25 @@ 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({
|
||||||
@@ -258,17 +303,21 @@ 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,9 +1,9 @@
|
|||||||
import {BSKY_LABELER_DID, BskyAgent} from '@atproto/api'
|
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 {SessionAccount} from './types'
|
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.
|
||||||
@@ -13,7 +13,7 @@ export function configureModerationForGuest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function configureModerationForAccount(
|
export async function configureModerationForAccount(
|
||||||
agent: BskyAgent,
|
agent: Agent | BskyAgent,
|
||||||
account: SessionAccount,
|
account: SessionAccount,
|
||||||
) {
|
) {
|
||||||
// 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.
|
||||||
@@ -41,7 +41,7 @@ function switchToBskyAppLabeler() {
|
|||||||
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
|
BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function trySwitchToTestAppLabeler(agent: BskyAgent) {
|
async function trySwitchToTestAppLabeler(agent: Agent | BskyAgent) {
|
||||||
const did = (
|
const did = (
|
||||||
await agent
|
await agent
|
||||||
.resolveHandle({handle: 'mod-authority.test'})
|
.resolveHandle({handle: 'mod-authority.test'})
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import {AtpSessionEvent} from '@atproto/api'
|
import {type AtpSessionEvent} from '@atproto/api'
|
||||||
|
|
||||||
import {createPublicAgent} from './agent'
|
import {createPublicAgent} from './agent'
|
||||||
import {wrapSessionReducerForLogging} from './logging'
|
import {wrapSessionReducerForLogging} from './logging'
|
||||||
import {SessionAccount} from './types'
|
import {type SessionAccount} from './types'
|
||||||
|
|
||||||
// A hack so that the reducer can't read anything from the agent.
|
// A hack so that the reducer can't read anything from the agent.
|
||||||
// From the reducer's point of view, it should be a completely opaque object.
|
// From the reducer's point of view, it should be a completely opaque object.
|
||||||
type OpaqueBskyAgent = {
|
type OpaqueBskyAgent = {
|
||||||
readonly service: URL
|
readonly service?: URL | undefined // TODO: do we need service at all? what are implications if we rm?
|
||||||
readonly api: unknown
|
readonly api: unknown
|
||||||
readonly app: unknown
|
readonly app: unknown
|
||||||
readonly com: unknown
|
readonly com: unknown
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {type OAuthSession} from '@atproto/oauth-client-browser'
|
||||||
|
|
||||||
import {type LogEvents} from '#/lib/statsig/statsig'
|
import {type LogEvents} from '#/lib/statsig/statsig'
|
||||||
import {type PersistedAccount} from '#/state/persisted'
|
import {type PersistedAccount} from '#/state/persisted'
|
||||||
|
|
||||||
@@ -32,6 +34,10 @@ export type SessionApiContext = {
|
|||||||
},
|
},
|
||||||
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
|
||||||
@@ -39,5 +45,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user