Files
waseel/components/oauth.tsx
T
KrikoriosandClaude Fable 5 eceb6b45d5 Fix SMTP delivery, add password reset and user deletion
SMTP:
- Add connection/greeting/socket timeouts so a stalled Gmail
  connection no longer hangs sign-up
- Wrap sendMail in try/catch and fall back to logging the code
- Derive secure from port (465 implicit TLS vs 587 STARTTLS)
- Strip whitespace from the Gmail app password
- Document SMTP_HOST/SMTP_PORT in .env.example and environment.d.ts

Password reset (new):
- POST /(api)/auth/forgot-password emails a 6-digit code and does
  not reveal whether the address is registered
- POST /(api)/auth/reset-password validates the code, sets the new
  password, verifies the email, and signs the user in
- password_reset_codes table added to seed-db.mjs
- "Forgot password?" flow on the mobile sign-in screen

User deletion (new):
- DELETE /(api)/admin/users/[id], owner-only, blocks self-deletion
- Delete button with confirmation on the dashboard Users page

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 22:41:41 +03:00

106 lines
2.6 KiB
TypeScript

import * as Google from "expo-auth-session/providers/google";
import { router } from "expo-router";
import { useCallback, useEffect } from "react";
import { Image, Text, View, Alert } from "react-native";
import { icons } from "@/constants";
import { googleAuth } from "@/lib/auth";
import { useSession } from "@/lib/session";
import { CustomButton } from "./custom-button";
type OAuthProps = {
title: string;
};
export const OAuth = ({ title }: OAuthProps) => {
const clientId = process.env.EXPO_PUBLIC_GOOGLE_AUTH_WEB_CLIENT_ID;
const iosClientId = process.env.EXPO_PUBLIC_GOOGLE_AUTH_IOS_CLIENT_ID;
const androidClientId =
process.env.EXPO_PUBLIC_GOOGLE_AUTH_ANDROID_CLIENT_ID;
const isConfigured = Boolean(
clientId && (androidClientId || iosClientId),
);
if (!isConfigured) return null;
return <GoogleOAuth title={title} clientId={clientId!} iosClientId={iosClientId} androidClientId={androidClientId} />;
};
function GoogleOAuth({
title,
clientId,
iosClientId,
androidClientId,
}: OAuthProps & {
clientId: string;
iosClientId?: string;
androidClientId?: string;
}) {
const { setSession } = useSession();
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId,
iosClientId,
androidClientId,
});
useEffect(() => {
if (response?.type !== "success") return;
const idToken = response.params?.id_token;
if (!idToken) {
Alert.alert("Google sign-in failed", "No token returned. Try again.");
return;
}
void (async () => {
try {
await setSession(await googleAuth(idToken));
router.replace("/");
} catch (err: any) {
console.error("OAuth error", err);
Alert.alert(
"Google sign-in failed",
err?.message || "Please try again.",
);
}
})();
}, [response, setSession]);
const handleGoogleOAuth = useCallback(() => {
void promptAsync();
}, [promptAsync]);
return (
<View>
<View className="flex flex-row justify-center items-center mt-4 gap-x-3">
<View className="flex-1 h-px bg-general-100" />
<Text className="text-lg">Or</Text>
<View className="flex-1 h-px bg-general-100" />
</View>
<CustomButton
title={title}
className="mt-5 w-full shadow-none"
iconLeft={() => (
<Image
source={icons.google}
alt="Google logo"
resizeMode="contain"
className="h-5 w-5 mx-2"
/>
)}
bgVariant="outline"
textVariant="primary"
onPress={handleGoogleOAuth}
disabled={!request}
/>
</View>
);
}