From aa2b1f0eece62493d499eab19b25ee4d71d913db Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 17 Jul 2025 17:58:25 -0700 Subject: [PATCH] add the clients --- package.json | 2 + src/lib/statsig/gates.ts | 1 + src/screens/Login/LoginForm.tsx | 122 ++++++++++++++++++++++++++++---- src/state/session/agent.ts | 2 + src/state/session/oauth.tsx | 51 +++++++++++++ yarn.lock | 78 +++++++++++++++++++- 6 files changed, 241 insertions(+), 15 deletions(-) create mode 100644 src/state/session/oauth.tsx diff --git a/package.json b/package.json index cd7c482154..c9a2c974c2 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ }, "dependencies": { "@atproto/api": "^0.15.26", + "@atproto/oauth-client-browser": "^0.3.27", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", @@ -132,6 +133,7 @@ "eventemitter3": "^5.0.1", "expo": "53.0.11", "expo-application": "~6.1.4", + "expo-atproto-auth": "^0.1.1", "expo-blur": "~14.1.5", "expo-build-properties": "~0.14.6", "expo-camera": "~16.1.8", diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index efd7d605a0..587f5fc3df 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -5,6 +5,7 @@ export type Gate = | 'debug_show_feedcontext' | 'debug_subscriptions' | 'explore_show_suggested_feeds' + | 'oauth' | 'old_postonboarding' | 'onboarding_add_video_feed' | 'post_threads_v2_unspecced' diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index b6a528e42b..aaebbee3a9 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -32,22 +32,13 @@ import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticke import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' 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 -export const LoginForm = ({ - error, - serviceUrl, - serviceDescription, - initialHandle, - setError, - setServiceUrl, - onPressRetryConnect, - onPressBack, - onPressForgotPassword, - onAttemptSuccess, - onAttemptFailed, -}: { +interface LoginFormProps { error: string serviceUrl: string serviceDescription: ServiceDescription | undefined @@ -59,7 +50,110 @@ export const LoginForm = ({ onPressForgotPassword: () => void onAttemptSuccess: () => void onAttemptFailed: () => void -}) => { +} + +export function LoginForm(props: LoginFormProps) { + const gate = useGate() + if (gate('oauth')) { + return + } else { + return + } +} + +function OAuthLoginForm({error, initialHandle, onPressBack}: LoginFormProps) { + const {_} = useLingui() + const [isProcessing, setIsProcessing] = React.useState(false) + const identifierValueRef = useRef(initialHandle || '') + + const onPressNext = async () => { + setIsProcessing(true) + if (isWeb) { + const client = getWebOAuthClient() + await client.signIn(identifierValueRef.current) + } else { + const client = getNativeOAuthClient() + const res = await client.signIn(identifierValueRef.current) + // redirect after result + } + } + + return ( + Sign in}> + + + Account + + + + + { + identifierValueRef.current = v + }} + onSubmitEditing={() => {}} + blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field + editable={!isProcessing} + accessibilityHint={_( + msg`Enter the username or email address you used when you created your account`, + )} + /> + + + + + + + + + + + ) +} + +const LoginFormInner = ({ + error, + serviceUrl, + serviceDescription, + initialHandle, + setError, + setServiceUrl, + onPressRetryConnect, + onPressBack, + onPressForgotPassword, + onAttemptSuccess, + onAttemptFailed, +}: LoginFormProps) => { const t = useTheme() const [isProcessing, setIsProcessing] = useState(false) const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] = diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 531e285ab1..b37089acb3 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -21,6 +21,8 @@ import { } from './moderation' import {SessionAccount} from './types' import {isSessionExpired, isSignupQueued} from './util' +import {BSKY_OAUTH_CLIENT} from './oauth' +import {ExpoOAuthClient} from 'expo-atproto-auth' export function createPublicAgent() { configureModerationForGuest() // Side effect but only relevant for tests diff --git a/src/state/session/oauth.tsx b/src/state/session/oauth.tsx new file mode 100644 index 0000000000..55435d76a4 --- /dev/null +++ b/src/state/session/oauth.tsx @@ -0,0 +1,51 @@ +import {ExpoOAuthClient} from 'expo-atproto-auth' +import {BrowserOAuthClient} from '@atproto/oauth-client-browser' +import {OAuthClient} from '@atproto/oauth-client' +import {Platform} from 'react-native' + +export const BSKY_OAUTH_CLIENT: OAuthClient = + Platform.OS === 'web' ? createWebOAuthClient() : createNativeOAuthClient() + +export function createWebOAuthClient() { + return new BrowserOAuthClient({ + clientMetadata: { + client_id: 'https://bsky.hailey.at/oauth-client-metadata.json', + client_name: 'Bluesky (Hailey Demo)', + client_uri: 'https://bsky.hailey.at', + redirect_uris: ['https://bsky.hailey.at/auth/callback'], + scope: 'atproto transition:generic', + token_endpoint_auth_method: 'none', + response_types: ['code'], + grant_types: ['authorization_code', 'refresh_token'], + application_type: 'web', + dpop_bound_access_tokens: true, + }, + handleResolver: 'https://bsky.social', + }) +} + +export function createNativeOAuthClient() { + return new ExpoOAuthClient({ + clientMetadata: { + client_id: 'https://bsky.hailey.at/oauth-client-metadata.native.json', + client_name: 'Bluesky Native App (Hailey Demo)', + client_uri: 'https://hailey.at', + redirect_uris: ['at.hailey:/auth/callback'], + scope: 'atproto transition:generic', + token_endpoint_auth_method: 'none', + response_types: ['code'], + grant_types: ['authorization_code', 'refresh_token'], + application_type: 'native', + dpop_bound_access_tokens: true, + }, + handleResolver: 'https://bsky.social', + }) +} + +export function getNativeOAuthClient() { + return BSKY_OAUTH_CLIENT as ExpoOAuthClient +} + +export function getWebOAuthClient() { + return BSKY_OAUTH_CLIENT as BrowserOAuthClient +} diff --git a/yarn.lock b/yarn.lock index 8079c90bfc..6884900f1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,6 +20,18 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@atproto-labs/did-resolver@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@atproto-labs/did-resolver/-/did-resolver-0.2.0.tgz#85762cd6992555e78bde9e202dea803a23e7d896" + integrity sha512-y9GOx2gUETynDKmANnBrU5DTf+u0AwKBJpGns1vDDOYMdLdRCFIeYy3UH+TI8YOkcEazjgF5Q3m+LjwriE1KqQ== + dependencies: + "@atproto-labs/fetch" "0.2.3" + "@atproto-labs/pipe" "0.1.1" + "@atproto-labs/simple-store" "0.2.0" + "@atproto-labs/simple-store-memory" "0.1.3" + "@atproto/did" "0.1.5" + zod "^3.23.8" + "@atproto-labs/fetch-node@0.1.9": version "0.1.9" resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.1.9.tgz#5df902413cc2ebfff914999ad3fbbc13b20e1dd0" @@ -37,6 +49,24 @@ dependencies: "@atproto-labs/pipe" "0.1.1" +"@atproto-labs/handle-resolver@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto-labs/handle-resolver/-/handle-resolver-0.3.0.tgz#b7928358e59cbacd4f0337a807222798fe133f60" + integrity sha512-TREelvXB6P2eHxx6QjINRkBzUZu/aXWrdY9iN57shQe3C8rzsHNEHHuTVvRa33Hc7vFdQbZN0TnCgKveoyiL/A== + dependencies: + "@atproto-labs/simple-store" "0.2.0" + "@atproto-labs/simple-store-memory" "0.1.3" + "@atproto/did" "0.1.5" + zod "^3.23.8" + +"@atproto-labs/identity-resolver@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto-labs/identity-resolver/-/identity-resolver-0.3.0.tgz#8988fc2e91c2060614e0f7698f5561e5c84904fc" + integrity sha512-ZmmRV6m17kIaX4WllYrFIa7d23lNng0fIk6pLyepRGZobQhM5d4wDezICTESAG+RoD0e5fisWs+Tamdvx3mx/Q== + dependencies: + "@atproto-labs/did-resolver" "0.2.0" + "@atproto-labs/handle-resolver" "0.3.0" + "@atproto-labs/pipe@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.1.tgz#1c4232d16bf95f251e993cb6ee440f9aa4e87ce6" @@ -270,6 +300,15 @@ "@atproto/jwk" "0.4.0" jose "^5.2.0" +"@atproto/jwk-webcrypto@0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.1.9.tgz#b58abc4a77a3352abd6f6d33825916325d82496a" + integrity sha512-ecciePHT0JEDZNAbMKSkdqoBYsjvhwuVno0jsS600SZmuvi2fAMhGraDZ5ZOO5M0hHHBiDbN7Ar/qcnIwyoxsA== + dependencies: + "@atproto/jwk" "0.4.0" + "@atproto/jwk-jose" "0.1.9" + zod "^3.23.8" + "@atproto/jwk@0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.4.0.tgz#f32265be172492c38434c556a124b954f249cee8" @@ -289,6 +328,38 @@ multiformats "^9.9.0" zod "^3.23.8" +"@atproto/oauth-client-browser@^0.3.27": + version "0.3.27" + resolved "https://registry.yarnpkg.com/@atproto/oauth-client-browser/-/oauth-client-browser-0.3.27.tgz#929045d9dc834339b8c76c372d44e6444f34cfb2" + integrity sha512-sUZP27KjlS3qJVPMC+RgWNARQZo7n6CWCXN55+QqLnHTfh+dLCXDS9jMUreXUGMQkVETEogDZ/v0Pb0xHQwBsg== + dependencies: + "@atproto-labs/did-resolver" "0.2.0" + "@atproto-labs/handle-resolver" "0.3.0" + "@atproto-labs/simple-store" "0.2.0" + "@atproto/did" "0.1.5" + "@atproto/jwk" "0.4.0" + "@atproto/jwk-webcrypto" "0.1.9" + "@atproto/oauth-client" "0.5.1" + "@atproto/oauth-types" "0.4.0" + +"@atproto/oauth-client@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@atproto/oauth-client/-/oauth-client-0.5.1.tgz#e0b7995f395a898e1faa736f177f242e5b92f0fa" + integrity sha512-wNC9RdfH1LGyZKF+UOmY+z4TFNx1gBur3fx91MCCrNaU0aTHBzgEH9UquL2031J7VNXhBsKJnHfEB5ZYy0AEHQ== + dependencies: + "@atproto-labs/did-resolver" "0.2.0" + "@atproto-labs/fetch" "0.2.3" + "@atproto-labs/handle-resolver" "0.3.0" + "@atproto-labs/identity-resolver" "0.3.0" + "@atproto-labs/simple-store" "0.2.0" + "@atproto-labs/simple-store-memory" "0.1.3" + "@atproto/did" "0.1.5" + "@atproto/jwk" "0.4.0" + "@atproto/oauth-types" "0.4.0" + "@atproto/xrpc" "0.7.1" + multiformats "^9.9.0" + zod "^3.23.8" + "@atproto/oauth-provider-api@0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.1.6.tgz#769a70caaac9b5144f9f867518523d1568a6b47c" @@ -480,7 +551,7 @@ ws "^8.12.0" zod "^3.23.8" -"@atproto/xrpc@^0.7.1": +"@atproto/xrpc@0.7.1", "@atproto/xrpc@^0.7.1": version "0.7.1" resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.1.tgz#51a8fc131eb21bd1229129d0a46384accc50ad65" integrity sha512-ANHEzlskYlMEdH18m+Itp3a8d0pEJao2qoDybDoMupTnoeNkya4VKIaOgAi6ERQnqatBBZyn9asW+7rJmSt/8g== @@ -11182,6 +11253,11 @@ expo-asset@~11.1.5: "@expo/image-utils" "^0.7.4" expo-constants "~17.1.5" +expo-atproto-auth@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/expo-atproto-auth/-/expo-atproto-auth-0.1.1.tgz#250798e87c22e963a3c3edb9914c13f46ca88de4" + integrity sha512-tJ9y3uJiTFXMaviKOWfbSKYcWaWdvEByMJGXB4h8RxEhmnD7C7CTdVRR4ZLfcUucaDQLRczMFWUwBdZKbS1slA== + expo-blur@~14.1.5: version "14.1.5" resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-14.1.5.tgz#910712389e19286ccdc136275bf569f427aa05ef"