-
Notifications
You must be signed in to change notification settings - Fork 0
Clerk Auth Implementation #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
152915b
7864dd4
3a9fa43
7f67cc8
f1a5e5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_REPLACE_ME |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ | |
| }, | ||
| "plugins": [ | ||
| "expo-router", | ||
| "@clerk/expo", | ||
| "expo-secure-store", | ||
| [ | ||
| "expo-splash-screen", | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,12 @@ | ||
| import "@/global.css"; | ||
| import { Stack } from "expo-router"; | ||
| import { useAuth } from "@clerk/expo"; | ||
| import { Redirect, Stack } from "expo-router"; | ||
|
|
||
| export default function RootLayout() { | ||
| export default function AuthLayout() { | ||
| const { isSignedIn, isLoaded } = useAuth(); | ||
| if (!isLoaded) return null; | ||
| if (isSignedIn) { | ||
| return <Redirect href="/" />; | ||
| } | ||
| return <Stack screenOptions={{ headerShown: false }} />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,201 @@ | ||
| import { Link } from "expo-router"; | ||
| import { useSignIn } from "@clerk/expo"; | ||
| import { Link, useRouter } from "expo-router"; | ||
| import { styled } from "nativewind"; | ||
| import React from "react"; | ||
| import { | ||
| KeyboardAvoidingView, | ||
| Platform, | ||
| Pressable, | ||
| ScrollView, | ||
| Text, | ||
| TextInput, | ||
| View, | ||
| } from "react-native"; | ||
| import { SafeAreaView as RNSafeAreaView } from "react-native-safe-area-context"; | ||
|
|
||
| const SafeAreaView = styled(RNSafeAreaView) as any; | ||
|
|
||
| export default function SignIn() { | ||
| const { signIn, errors, fetchStatus } = useSignIn(); | ||
| const router = useRouter(); | ||
|
|
||
| const [emailAddress, setEmailAddress] = React.useState(""); | ||
| const [password, setPassword] = React.useState(""); | ||
| const [code, setCode] = React.useState(""); | ||
|
|
||
| const handleSubmit = async () => { | ||
| const { error } = await signIn.password({ | ||
| emailAddress, | ||
| password, | ||
| }); | ||
| if (error) { | ||
| console.error(JSON.stringify(error, null, 2)); | ||
| return; | ||
| } | ||
|
|
||
| if (signIn.status === "complete") { | ||
| await signIn.finalize({ | ||
| navigate: ({ session, decorateUrl }) => { | ||
| if (session?.currentTask) { | ||
| console.log(session?.currentTask); | ||
| return; | ||
| } | ||
| router.replace("/"); | ||
| }, | ||
| }); | ||
| } else if (signIn.status === "needs_client_trust") { | ||
| const emailCodeFactor = signIn.supportedSecondFactors.find( | ||
| (factor) => factor.strategy === "email_code", | ||
| ); | ||
|
|
||
| if (emailCodeFactor) { | ||
| await signIn.mfa.sendEmailCode(); | ||
| } | ||
|
Comment on lines
+46
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- app/(auth)/sign-in.tsx (outline) ---'
ast-grep outline 'app/(auth)/sign-in.tsx' --view expanded || true
echo
echo '--- relevant lines in app/(auth)/sign-in.tsx ---'
nl -ba 'app/(auth)/sign-in.tsx' | sed -n '1,180p'
echo
echo '--- search for needs_client_trust / sendEmailCode / verification screen ---'
rg -n 'needs_client_trust|sendEmailCode|email_code|verificationError|Enter the code sent to your email|auth-error' app -S || trueRepository: saikumarbt/react-native-recurly Length of output: 411 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- app/(auth)/sign-in.tsx (1-220 with line numbers) ---'
awk '{printf("%4d %s\n", NR, $0)}' 'app/(auth)/sign-in.tsx' | sed -n '1,220p'
echo
echo '--- targeted search in app/ ---'
rg -n 'needs_client_trust|sendEmailCode|email_code|verificationError|Enter the code sent to your email|auth-error' app -S || trueRepository: saikumarbt/react-native-recurly Length of output: 9014 Guard the email-code challenge before switching to the verification screen. 🤖 Prompt for AI Agents |
||
| } | ||
| }; | ||
|
|
||
| const handleVerify = async () => { | ||
| await signIn.mfa.verifyEmailCode({ code }); | ||
|
|
||
| if (signIn.status === "complete") { | ||
| await signIn.finalize({ | ||
| navigate: ({ session, decorateUrl }) => { | ||
| if (session?.currentTask) { | ||
| console.log(session?.currentTask); | ||
| return; | ||
| } | ||
| router.replace("/"); | ||
| }, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| if (signIn.status === "needs_client_trust") { | ||
| return ( | ||
| <SafeAreaView className="auth-screen"> | ||
| <View className="auth-content"> | ||
| <Text className="auth-title">Verify your account</Text> | ||
| <Text className="auth-subtitle"> | ||
| Enter the code sent to your email. | ||
| </Text> | ||
|
|
||
| <View className="auth-card"> | ||
| <View className="auth-form"> | ||
| <View className="auth-field"> | ||
| <Text className="auth-label">Verification Code</Text> | ||
| <TextInput | ||
| className="auth-input" | ||
| value={code} | ||
| placeholder="Enter your verification code" | ||
| placeholderTextColor="#666666" | ||
| onChangeText={(code) => setCode(code)} | ||
| keyboardType="numeric" | ||
| /> | ||
| {errors?.fields?.code && ( | ||
| <Text className="auth-error"> | ||
| {errors.fields.code.message} | ||
| </Text> | ||
| )} | ||
| </View> | ||
|
|
||
| <Pressable | ||
| className={`auth-button ${fetchStatus === "fetching" ? "auth-button-disabled" : ""}`} | ||
| onPress={handleVerify} | ||
| disabled={fetchStatus === "fetching"} | ||
| > | ||
| <Text className="auth-button-text">Verify</Text> | ||
| </Pressable> | ||
| </View> | ||
| </View> | ||
| </View> | ||
| </SafeAreaView> | ||
| ); | ||
| } | ||
|
|
||
| import { Text, View } from "react-native"; | ||
| const SignIn = () => { | ||
| return ( | ||
| <View> | ||
| <Text>SignIn</Text> | ||
| <Link href="/(auth)/sign-up">Create Account</Link> | ||
| </View> | ||
| <SafeAreaView className="auth-screen"> | ||
| <KeyboardAvoidingView | ||
| behavior={Platform.OS === "ios" ? "padding" : "height"} | ||
| style={{ flex: 1 }} | ||
| > | ||
| <ScrollView className="auth-scroll"> | ||
| <View className="auth-content"> | ||
| <View className="auth-brand-block"> | ||
| <View className="auth-logo-wrap"> | ||
| <View className="auth-logo-mark"> | ||
| <Text className="auth-logo-mark-text">R</Text> | ||
| </View> | ||
| <View> | ||
| <Text className="auth-wordmark">Recurrly</Text> | ||
| <Text className="auth-wordmark-sub">SMART BILLING</Text> | ||
| </View> | ||
| </View> | ||
|
|
||
| <Text className="auth-title">Welcome back</Text> | ||
| <Text className="auth-subtitle"> | ||
| Sign in to manage your subscriptions | ||
| </Text> | ||
| </View> | ||
| <View className="auth-card"> | ||
| <View className="auth-form"> | ||
| <View className="auth-field"> | ||
| <Text className="auth-label">Email address</Text> | ||
| <TextInput | ||
| className="auth-input" | ||
| autoCapitalize="none" | ||
| value={emailAddress} | ||
| placeholder="Enter email" | ||
| placeholderTextColor="#666666" | ||
| onChangeText={(emailAddress) => | ||
| setEmailAddress(emailAddress) | ||
| } | ||
| keyboardType="email-address" | ||
| /> | ||
| {errors?.fields?.identifier && ( | ||
| <Text className="auth-error"> | ||
| {errors.fields.identifier.message} | ||
| </Text> | ||
| )} | ||
| </View> | ||
|
|
||
| <View className="auth-field"> | ||
| <Text className="auth-label">Password</Text> | ||
| <TextInput | ||
| className="auth-input" | ||
| value={password} | ||
| placeholder="Enter password" | ||
| placeholderTextColor="#666666" | ||
| secureTextEntry={true} | ||
| onChangeText={(password) => setPassword(password)} | ||
| /> | ||
| {errors?.fields?.password && ( | ||
| <Text className="auth-error"> | ||
| {errors.fields.password.message} | ||
| </Text> | ||
| )} | ||
| </View> | ||
|
|
||
| <Pressable | ||
| className={`auth-button ${!emailAddress || !password || fetchStatus === "fetching" ? "auth-button-disabled" : ""}`} | ||
| onPress={handleSubmit} | ||
| disabled={ | ||
| !emailAddress || !password || fetchStatus === "fetching" | ||
| } | ||
| > | ||
| <Text className="auth-button-text">Sign in</Text> | ||
| </Pressable> | ||
| </View> | ||
| </View> | ||
|
|
||
| <View className="auth-link-row"> | ||
| <Text className="auth-link-copy">Don't have an account?</Text> | ||
| <Link href="/(auth)/sign-up"> | ||
| <Text className="auth-link">Sign up</Text> | ||
| </Link> | ||
| </View> | ||
| </View> | ||
| </ScrollView> | ||
| </KeyboardAvoidingView> | ||
| </SafeAreaView> | ||
| ); | ||
| }; | ||
| export default SignIn; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: saikumarbt/react-native-recurly
Length of output: 8692
🏁 Script executed:
Repository: saikumarbt/react-native-recurly
Length of output: 246
Avoid logging raw auth/session payloads.
JSON.stringify(error, null, 2)andsession?.currentTaskcan leak sensitive auth details into device logs; keep these messages generic and behind__DEV__. Also applies toapp/(auth)/sign-in.tsx:39-40, 63-64.🤖 Prompt for AI Agents