All 3 auth methods work & update public profile table as well
This commit is contained in:
parent
07bf94d393
commit
f9fd5dafc5
2
app.json
2
app.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "Tech Tracker",
|
"name": "Tech Tracker",
|
||||||
"slug": "tech-tracker",
|
"slug": "tech-tracker-expo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import * as AppleAuthentication from 'expo-apple-authentication'
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
import { ThemedView } from '@/components/theme';
|
import { ThemedView } from '@/components/theme';
|
||||||
import { StyleSheet, Platform } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
import Constants from 'expo-constants';
|
import Constants from 'expo-constants';
|
||||||
import * as Notifications from 'expo-notifications';
|
import * as Notifications from 'expo-notifications';
|
||||||
|
import type { updateUser } from '@/constants/Types';
|
||||||
|
|
||||||
const AppleSignInButton = () => {
|
const AppleSignInButton = () => {
|
||||||
const scheme = useColorScheme() ?? 'dark';
|
const scheme = useColorScheme() ?? 'dark';
|
||||||
@ -21,18 +22,14 @@ const AppleSignInButton = () => {
|
|||||||
//const projectId = Constants.expoConfig?.extra?.projectId;
|
//const projectId = Constants.expoConfig?.extra?.projectId;
|
||||||
//if (!projectId) throw new Error('No projectId found in expo.config.json');
|
//if (!projectId) throw new Error('No projectId found in expo.config.json');
|
||||||
//const pushToken = await Notifications.getExpoPushTokenAsync({
|
//const pushToken = await Notifications.getExpoPushTokenAsync({
|
||||||
//projectId,
|
//projectId,
|
||||||
//});
|
//});
|
||||||
if (credential.identityToken) {
|
if (credential.identityToken) {
|
||||||
const email = credential.email;
|
const email = credential.email;
|
||||||
const full_name = (
|
const full_name =
|
||||||
credential.fullName &&
|
credential.fullName && credential.fullName.givenName && credential.fullName.familyName
|
||||||
credential.fullName.givenName &&
|
? `${credential.fullName.givenName} ${credential.fullName.familyName}`
|
||||||
credential.fullName.familyName
|
: null;
|
||||||
)
|
|
||||||
? `${credential.fullName.givenName} ${credential.fullName.familyName}`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
data: { user, session },
|
data: { user, session },
|
||||||
@ -40,32 +37,32 @@ const AppleSignInButton = () => {
|
|||||||
provider: 'apple',
|
provider: 'apple',
|
||||||
token: credential.identityToken,
|
token: credential.identityToken,
|
||||||
});
|
});
|
||||||
console.log(JSON.stringify({ error, user }, null, 2))
|
console.log(JSON.stringify({ error, user }, null, 2));
|
||||||
if (!error) {
|
if (!error && session) {
|
||||||
if (user && session) {
|
if (email) {
|
||||||
const data: any = {};
|
const data: updateUser = {
|
||||||
if (email) data.email = email;
|
email,
|
||||||
if (full_name) data.full_name = full_name;
|
full_name: full_name ?? '',
|
||||||
const { error: updateError } = await supabase.auth.updateUser({
|
};
|
||||||
|
const { error: authUpdateError } = await supabase.auth.updateUser({
|
||||||
data,
|
data,
|
||||||
|
})
|
||||||
|
const { error: updateError } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.upsert({
|
||||||
|
id: session.user.id,
|
||||||
|
full_name,
|
||||||
|
email,
|
||||||
|
provider: 'apple',
|
||||||
|
updated_at: new Date(),
|
||||||
});
|
});
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('Error updating user metadata:', updateError);
|
console.error('Error updating user metadata:', updateError);
|
||||||
}
|
}
|
||||||
const { error: updateProfileError } =
|
|
||||||
await supabase.from('profiles').upsert({
|
|
||||||
id: session.user.id,
|
|
||||||
username: data?.email.split('@')[0],
|
|
||||||
full_name: data?.full_name,
|
|
||||||
updated_at: new Date(),
|
|
||||||
});
|
|
||||||
if (updateProfileError) {
|
|
||||||
console.error('Error updating profile:', updateProfileError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No identityToken.')
|
throw new Error('No identityToken.');
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.code === 'ERR_REQUEST_CANCELED') {
|
if (e.code === 'ERR_REQUEST_CANCELED') {
|
||||||
@ -76,26 +73,26 @@ const AppleSignInButton = () => {
|
|||||||
console.log('Error signing in with Apple:', e);
|
console.log('Error signing in with Apple:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (Platform.OS !== 'ios') return <ThemedView />;
|
if (Platform.OS !== 'ios') return <ThemedView />;
|
||||||
else return (
|
else
|
||||||
|
return (
|
||||||
<ThemedView style={styles.verticallySpaced}>
|
<ThemedView style={styles.verticallySpaced}>
|
||||||
<AppleAuthentication.AppleAuthenticationButton
|
<AppleAuthentication.AppleAuthenticationButton
|
||||||
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
|
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
|
||||||
buttonStyle={
|
buttonStyle={
|
||||||
scheme === 'light'
|
scheme === 'light'
|
||||||
? AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
? AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||||
: AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
: AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||||
}
|
}
|
||||||
cornerRadius={5}
|
cornerRadius={5}
|
||||||
style={{ width: 200, height: 64 }}
|
style={{ width: 200, height: 64 }}
|
||||||
onPress={signInWithApple}
|
onPress={signInWithApple}
|
||||||
/>
|
/>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
export default AppleSignInButton;
|
export default AppleSignInButton;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@ -105,4 +102,4 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { supabase } from '@/lib/supabase';
|
|||||||
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
|
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
|
||||||
import AppleSignInButton from '@/components/auth/AppleSignIniOS';
|
import AppleSignInButton from '@/components/auth/AppleSignIniOS';
|
||||||
import AzureSignIn from './AzureSignIn';
|
import AzureSignIn from './AzureSignIn';
|
||||||
|
import type { updateUser } from '@/constants/Types';
|
||||||
|
|
||||||
// Tells Supabase Auth to continuously refresh the session automatically if
|
// Tells Supabase Auth to continuously refresh the session automatically if
|
||||||
// the app is in the foreground. When this is added, you will continue to receive
|
// the app is in the foreground. When this is added, you will continue to receive
|
||||||
@ -20,6 +21,7 @@ if (Platform.OS !== 'web') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
|
const [full_name, setFullName] = useState('');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -50,11 +52,26 @@ const Auth = () => {
|
|||||||
data: { session },
|
data: { session },
|
||||||
error,
|
error,
|
||||||
} = await supabase.auth.signUp({
|
} = await supabase.auth.signUp({
|
||||||
email: email,
|
email,
|
||||||
password: password,
|
password,
|
||||||
});
|
});
|
||||||
if (error) Alert.alert(error.message);
|
if (error) Alert.alert(error.message);
|
||||||
else if (!session) Alert.alert('Please check your inbox for email verification!');
|
else if (!session) Alert.alert('Please check your inbox for email verification!');
|
||||||
|
else {
|
||||||
|
const { error: updateProfileError } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.upsert({
|
||||||
|
id: session.user.id,
|
||||||
|
full_name,
|
||||||
|
email,
|
||||||
|
provider: 'email',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
if (updateProfileError) {
|
||||||
|
Alert.alert('Error updating profile:', updateProfileError.message);
|
||||||
|
console.error('Error updating profile:', updateProfileError.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,6 +84,15 @@ const Auth = () => {
|
|||||||
</ThemedText>
|
</ThemedText>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
|
|
||||||
|
<ThemedView style={styles.verticallySpaced}>
|
||||||
|
<ThemedTextInput
|
||||||
|
fontSize={24}
|
||||||
|
onChangeText={(text) => setFullName(text)}
|
||||||
|
value={full_name}
|
||||||
|
placeholder='Full Name'
|
||||||
|
/>
|
||||||
|
</ThemedView>
|
||||||
|
|
||||||
<ThemedView style={[styles.verticallySpaced]}>
|
<ThemedView style={[styles.verticallySpaced]}>
|
||||||
<ThemedTextInput
|
<ThemedTextInput
|
||||||
fontSize={24}
|
fontSize={24}
|
||||||
|
@ -7,6 +7,7 @@ import * as QueryParams from 'expo-auth-session/build/QueryParams';
|
|||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import { ThemedView, ThemedButton, ThemedText } from '@/components/theme';
|
import { ThemedView, ThemedButton, ThemedText } from '@/components/theme';
|
||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors';
|
||||||
|
import type { updateUser } from '@/constants/Types';
|
||||||
|
|
||||||
WebBrowser.maybeCompleteAuthSession();
|
WebBrowser.maybeCompleteAuthSession();
|
||||||
|
|
||||||
@ -27,8 +28,6 @@ const AzureSignIn = () => {
|
|||||||
const signInWithAzure = async () => {
|
const signInWithAzure = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
console.log('Starting Azure sign-in with tenant-specific endpoint');
|
|
||||||
console.log('Redirect URI:', redirectUri);
|
|
||||||
|
|
||||||
// Create the MSAL auth request
|
// Create the MSAL auth request
|
||||||
const request = new AuthSession.AuthRequest({
|
const request = new AuthSession.AuthRequest({
|
||||||
@ -44,31 +43,27 @@ const AzureSignIn = () => {
|
|||||||
console.log('Generated auth URL:', authUrl);
|
console.log('Generated auth URL:', authUrl);
|
||||||
|
|
||||||
// Open the auth URL in a browser
|
// Open the auth URL in a browser
|
||||||
const result = await WebBrowser.openAuthSessionAsync(
|
const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUri, {
|
||||||
authUrl,
|
showInRecents: true,
|
||||||
redirectUri,
|
});
|
||||||
{
|
|
||||||
showInRecents: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('Auth session result type:', result.type);
|
console.log('Auth session result type:', result.type);
|
||||||
|
|
||||||
if (result.type === 'success' && result.url) {
|
if (result.type === 'success' && result.url) {
|
||||||
// Parse the URL to get the authorization code
|
// Parse the URL to get the authorization code
|
||||||
const { params, errorCode } = QueryParams.getQueryParams(result.url);
|
const { params, errorCode } = QueryParams.getQueryParams(result.url);
|
||||||
|
|
||||||
if (errorCode || params.error) {
|
if (errorCode || params.error) {
|
||||||
const errorMessage = params.error_description || params.error || errorCode;
|
const errorMessage = params.error_description || params.error || errorCode;
|
||||||
throw new Error(`Error during authentication: ${errorMessage}`);
|
throw new Error(`Error during authentication: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.code) {
|
if (!params.code) {
|
||||||
throw new Error('No authorization code received');
|
throw new Error('No authorization code received');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Authorization code received');
|
console.log('Authorization code received');
|
||||||
|
|
||||||
// Exchange the code for tokens
|
// Exchange the code for tokens
|
||||||
const tokenResult = await AuthSession.exchangeCodeAsync(
|
const tokenResult = await AuthSession.exchangeCodeAsync(
|
||||||
{
|
{
|
||||||
@ -79,26 +74,54 @@ const AzureSignIn = () => {
|
|||||||
code_verifier: request.codeVerifier || '',
|
code_verifier: request.codeVerifier || '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
discovery
|
discovery,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Token exchange successful');
|
console.log('Token exchange successful');
|
||||||
|
|
||||||
if (!tokenResult.idToken) {
|
if (!tokenResult.idToken) {
|
||||||
throw new Error('No ID token received');
|
throw new Error('No ID token received');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now use the ID token to sign in with Supabase
|
// Now use the ID token to sign in with Supabase
|
||||||
const { data, error } = await supabase.auth.signInWithIdToken({
|
const { data, error } = await supabase.auth.signInWithIdToken({
|
||||||
provider: 'azure',
|
provider: 'azure',
|
||||||
token: tokenResult.idToken,
|
token: tokenResult.idToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if profies table already has info (User is signing in, not signing up)
|
||||||
|
const { data: profileData, error: profileError } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', data.user?.id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (profileData.email === '' || !profileData.email && data.session?.user.email) {
|
||||||
|
const updateData: updateUser = {
|
||||||
|
email: data.session?.user.email ?? '',
|
||||||
|
};
|
||||||
|
const { error: updateAuthError } = await supabase.auth.updateUser({
|
||||||
|
data: updateData,
|
||||||
|
});
|
||||||
|
if (updateAuthError)
|
||||||
|
Alert.alert('Error updating auth info:', updateAuthError.message);
|
||||||
|
const { error: updateProfileError } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.upsert({
|
||||||
|
id: data.session?.user.id ?? '',
|
||||||
|
email: data.session?.user.email ?? '',
|
||||||
|
provider: 'azure',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
if (updateProfileError)
|
||||||
|
Alert.alert('Error updating profile:', updateProfileError.message);
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Supabase sign-in error:', error);
|
console.error('Supabase sign-in error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Successfully signed in with Azure via Supabase');
|
console.log('Successfully signed in with Azure via Supabase');
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@ -114,16 +137,13 @@ const AzureSignIn = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemedView style={styles.verticallySpaced}>
|
<ThemedView style={styles.verticallySpaced}>
|
||||||
<ThemedButton
|
<ThemedButton disabled={loading} onPress={signInWithAzure}>
|
||||||
disabled={loading}
|
|
||||||
onPress={signInWithAzure}
|
|
||||||
>
|
|
||||||
<ThemedText
|
<ThemedText
|
||||||
lightColor={Colors.dark.text}
|
lightColor={Colors.dark.text}
|
||||||
darkColor={Colors.light.text}
|
darkColor={Colors.light.text}
|
||||||
type="defaultSemiBold"
|
type='defaultSemiBold'
|
||||||
>
|
>
|
||||||
{loading ? "Signing in..." : "Sign in with Microsoft"}
|
{loading ? 'Signing in...' : 'Sign in with Microsoft'}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
</ThemedButton>
|
</ThemedButton>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
export type updateUser = {
|
||||||
|
id?: string;
|
||||||
|
updated_at?: Date;
|
||||||
|
email?: string;
|
||||||
|
full_name?: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
provider?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type NotificationMessage = {
|
export type NotificationMessage = {
|
||||||
sound?: string;
|
sound?: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -13,12 +13,15 @@ Notifications.setNotificationHandler({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sendPushNotification = async(expoPushToken: string | null, notification: NotificationMessage) => {
|
export const sendPushNotification = async (
|
||||||
|
expoPushToken: string | null,
|
||||||
|
notification: NotificationMessage,
|
||||||
|
) => {
|
||||||
if (!expoPushToken) {
|
if (!expoPushToken) {
|
||||||
Alert.alert('Error', 'No push token found.');
|
Alert.alert('Error', 'No push token found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = {
|
const message = {
|
||||||
to: expoPushToken,
|
to: expoPushToken,
|
||||||
sound: notification.sound ?? 'default',
|
sound: notification.sound ?? 'default',
|
||||||
title: notification.title,
|
title: notification.title,
|
||||||
@ -86,18 +89,20 @@ async function registerForPushNotificationsAsync() {
|
|||||||
|
|
||||||
const PushNotificationManager = ({ children }: { children: React.ReactNode }) => {
|
const PushNotificationManager = ({ children }: { children: React.ReactNode }) => {
|
||||||
const [expoPushToken, setExpoPushToken] = useState<string | undefined>('');
|
const [expoPushToken, setExpoPushToken] = useState<string | undefined>('');
|
||||||
const [notification, setNotification] = useState<Notifications.Notification | undefined>(undefined);
|
const [notification, setNotification] = useState<Notifications.Notification | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
const notificationListener = useRef<Notifications.Subscription>();
|
const notificationListener = useRef<Notifications.Subscription>();
|
||||||
const responseListener = useRef<Notifications.Subscription>();
|
const responseListener = useRef<Notifications.Subscription>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
|
registerForPushNotificationsAsync().then((token) => setExpoPushToken(token));
|
||||||
|
|
||||||
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
|
notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
|
||||||
setNotification(notification);
|
setNotification(notification);
|
||||||
});
|
});
|
||||||
|
|
||||||
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
|
responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
// Handle notification response here
|
// Handle notification response here
|
||||||
});
|
});
|
||||||
@ -109,5 +114,5 @@ const PushNotificationManager = ({ children }: { children: React.ReactNode }) =>
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
};
|
||||||
export default PushNotificationManager;
|
export default PushNotificationManager;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user