Start trying to support Microsoft Azure Sign in
This commit is contained in:
		
							
								
								
									
										5
									
								
								app.json
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								app.json
									
									
									
									
									
								
							@@ -33,7 +33,10 @@
 | 
			
		||||
      },
 | 
			
		||||
      "permissions": [
 | 
			
		||||
        "android.permission.ACCESS_COARSE_LOCATION",
 | 
			
		||||
        "android.permission.ACCESS_FINE_LOCATION"
 | 
			
		||||
        "android.permission.ACCESS_FINE_LOCATION",
 | 
			
		||||
        "android.permission.RECEIVE_BOOT_COMPLETED",
 | 
			
		||||
        "android.permission.VIBRATE",
 | 
			
		||||
        "android.permission.INTERNET"
 | 
			
		||||
      ],
 | 
			
		||||
      "package": "com.gbrown.techtracker"
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ const AppleSignInButton = () => {
 | 
			
		||||
        //projectId,
 | 
			
		||||
      //});
 | 
			
		||||
      if (credential.identityToken) {
 | 
			
		||||
        const email = credential.email;
 | 
			
		||||
        const full_name = (
 | 
			
		||||
          credential.fullName &&
 | 
			
		||||
          credential.fullName.givenName &&
 | 
			
		||||
@@ -41,16 +42,26 @@ const AppleSignInButton = () => {
 | 
			
		||||
        });
 | 
			
		||||
        console.log(JSON.stringify({ error, user }, null, 2))
 | 
			
		||||
        if (!error) {
 | 
			
		||||
          if (user) {
 | 
			
		||||
            const data: any = {
 | 
			
		||||
              full_name,
 | 
			
		||||
            };
 | 
			
		||||
          if (user && session) {
 | 
			
		||||
            const data: any = {};
 | 
			
		||||
            if (email) data.email = email;
 | 
			
		||||
            if (full_name) data.full_name = full_name;
 | 
			
		||||
            const { error: updateError } = await supabase.auth.updateUser({
 | 
			
		||||
              data,
 | 
			
		||||
            });
 | 
			
		||||
            if (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 {
 | 
			
		||||
@@ -67,10 +78,10 @@ const AppleSignInButton = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (Platform.OS !== 'ios') return <div/>;
 | 
			
		||||
  if (Platform.OS !== 'ios') return <ThemedView />;
 | 
			
		||||
  else return (
 | 
			
		||||
 | 
			
		||||
    <ThemedView style={[styles.verticallySpaced, styles.mt20]}>
 | 
			
		||||
    <ThemedView style={styles.verticallySpaced}>
 | 
			
		||||
      <AppleAuthentication.AppleAuthenticationButton
 | 
			
		||||
        buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
 | 
			
		||||
        buttonStyle={
 | 
			
		||||
@@ -92,8 +103,6 @@ const styles = StyleSheet.create({
 | 
			
		||||
    paddingTop: 4,
 | 
			
		||||
    paddingBottom: 4,
 | 
			
		||||
    alignItems: 'center',
 | 
			
		||||
  },
 | 
			
		||||
  mt20: {
 | 
			
		||||
    marginTop: 20,
 | 
			
		||||
  },
 | 
			
		||||
}); 
 | 
			
		||||
@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react';
 | 
			
		||||
import { Alert, StyleSheet, AppState, Image, Platform } from 'react-native';
 | 
			
		||||
import { supabase } from '@/lib/supabase';
 | 
			
		||||
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
 | 
			
		||||
import AppleSignInButton from '@/components/auth/AppleSignIn.native';
 | 
			
		||||
import AppleSignInButton from '@/components/auth/AppleSignIniOS';
 | 
			
		||||
import AzureSignIn from './AzureSignIn';
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
@@ -103,6 +104,7 @@ const Auth = () => {
 | 
			
		||||
        />
 | 
			
		||||
      </ThemedView>
 | 
			
		||||
      <AppleSignInButton />
 | 
			
		||||
      <AzureSignIn />
 | 
			
		||||
    </ThemedView>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								components/auth/AzureSignIn.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								components/auth/AzureSignIn.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import * as WebBrowser from 'expo-web-browser';
 | 
			
		||||
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
 | 
			
		||||
import { Platform, StyleSheet } from 'react-native';
 | 
			
		||||
import { supabase } from '@/lib/supabase';
 | 
			
		||||
import { ThemedView, ThemedTextButton } from '@/components/theme';
 | 
			
		||||
 | 
			
		||||
// This is important - it completes the auth session when the browser redirects back to your app
 | 
			
		||||
WebBrowser.maybeCompleteAuthSession();
 | 
			
		||||
 | 
			
		||||
const AzureSignIn = () => {
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  
 | 
			
		||||
  // Get environment variables
 | 
			
		||||
  const tenantId = process.env.EXPO_PUBLIC_AZURE_TENANT_ID as string;
 | 
			
		||||
  const clientId = process.env.EXPO_PUBLIC_AZURE_CLIENT_ID as string;
 | 
			
		||||
  
 | 
			
		||||
  // Set up the discovery endpoint for Azure AD
 | 
			
		||||
  const discovery = useAutoDiscovery(
 | 
			
		||||
    `https://login.microsoftonline.com/${tenantId}/v2.0`
 | 
			
		||||
  );
 | 
			
		||||
  
 | 
			
		||||
  // Create a redirect URI that matches what you configured in Azure
 | 
			
		||||
  // This should match the redirect URI you set in your Supabase dashboard
 | 
			
		||||
  const redirectUri = makeRedirectUri({
 | 
			
		||||
    scheme: Platform.OS === 'web' ? undefined : 'com.gbrown.techtracker',
 | 
			
		||||
    path: Platform.OS === 'web' ? 'auth/callback' : undefined,
 | 
			
		||||
  });
 | 
			
		||||
  
 | 
			
		||||
  // Set up the auth request with the needed scopes
 | 
			
		||||
  const [request, response, promptAsync] = useAuthRequest(
 | 
			
		||||
    {
 | 
			
		||||
      clientId,
 | 
			
		||||
      scopes: ['openid', 'profile', 'email', 'offline_access'],
 | 
			
		||||
      redirectUri,
 | 
			
		||||
      responseType: 'code', // Important for Supabase
 | 
			
		||||
      usePKCE: true, // Use PKCE for added security
 | 
			
		||||
    },
 | 
			
		||||
    discovery
 | 
			
		||||
  );
 | 
			
		||||
  
 | 
			
		||||
  const signInWithAzure = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      setLoading(true);
 | 
			
		||||
      
 | 
			
		||||
      // For Expo Go and mobile apps, we need to use the Expo Auth Session flow
 | 
			
		||||
      if (Platform.OS !== 'web') {
 | 
			
		||||
        // Launch the browser for authentication
 | 
			
		||||
        const result = await promptAsync();
 | 
			
		||||
        
 | 
			
		||||
        if (result.type === 'success') {
 | 
			
		||||
          // If we got an authorization code, use Supabase to exchange it for a session
 | 
			
		||||
          const { code } = result.params;
 | 
			
		||||
          
 | 
			
		||||
          const { data, error } = await supabase.auth.exchangeCodeForSession(code);
 | 
			
		||||
          
 | 
			
		||||
          if (error) {
 | 
			
		||||
            throw error;
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          console.log('Successfully signed in with Azure!', data.user);
 | 
			
		||||
        } else if (result.type === 'error') {
 | 
			
		||||
          throw new Error(result.error?.message || 'Authentication failed');
 | 
			
		||||
        } else if (result.type === 'cancel') {
 | 
			
		||||
          console.log('User cancelled the login flow');
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        // For web, we can use Supabase's built-in OAuth flow
 | 
			
		||||
        const { data, error } = await supabase.auth.signInWithOAuth({
 | 
			
		||||
          provider: 'azure',
 | 
			
		||||
          options: {
 | 
			
		||||
            scopes: 'email profile openid offline_access',
 | 
			
		||||
            redirectTo: window.location.origin + '/auth/callback',
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        if (error) {
 | 
			
		||||
          throw error;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Error signing in with Azure:', error);
 | 
			
		||||
      alert(`Error signing in: ${error.message}`);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  return (
 | 
			
		||||
    <ThemedView style={styles.verticallySpaced}>
 | 
			
		||||
      <ThemedTextButton
 | 
			
		||||
        text="Sign in with Microsoft"
 | 
			
		||||
        disabled={loading || !request}
 | 
			
		||||
        onPress={signInWithAzure}
 | 
			
		||||
        fontSize={18}
 | 
			
		||||
      />
 | 
			
		||||
    </ThemedView>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
export default AzureSignIn;
 | 
			
		||||
 | 
			
		||||
const styles = StyleSheet.create({
 | 
			
		||||
  verticallySpaced: {
 | 
			
		||||
    paddingTop: 4,
 | 
			
		||||
    paddingBottom: 4,
 | 
			
		||||
    alignItems: 'center',
 | 
			
		||||
    marginTop: 20,
 | 
			
		||||
  },
 | 
			
		||||
}); 
 | 
			
		||||
							
								
								
									
										31
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										31
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -19,6 +19,7 @@
 | 
			
		||||
        "aes-js": "^3.1.2",
 | 
			
		||||
        "expo": "~52.0.28",
 | 
			
		||||
        "expo-apple-authentication": "~7.1.3",
 | 
			
		||||
        "expo-auth-session": "~6.0.3",
 | 
			
		||||
        "expo-blur": "~14.0.3",
 | 
			
		||||
        "expo-constants": "~17.0.7",
 | 
			
		||||
        "expo-dev-client": "~5.0.12",
 | 
			
		||||
@@ -7668,6 +7669,24 @@
 | 
			
		||||
        "react-native": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/expo-auth-session": {
 | 
			
		||||
      "version": "6.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-6.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-s7LmmMPiiY1NXrlcXkc4+09Hlfw9X1CpaQOCDkwfQEodG1uCYGQi/WImTnDzw5YDkWI79uC8F1mB8EIerilkDA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "expo-application": "~6.0.2",
 | 
			
		||||
        "expo-constants": "~17.0.5",
 | 
			
		||||
        "expo-crypto": "~14.0.2",
 | 
			
		||||
        "expo-linking": "~7.0.5",
 | 
			
		||||
        "expo-web-browser": "~14.0.2",
 | 
			
		||||
        "invariant": "^2.2.4"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "*",
 | 
			
		||||
        "react-native": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/expo-blur": {
 | 
			
		||||
      "version": "14.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-14.0.3.tgz",
 | 
			
		||||
@@ -7693,6 +7712,18 @@
 | 
			
		||||
        "react-native": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/expo-crypto": {
 | 
			
		||||
      "version": "14.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-14.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-WRc9PBpJraJN29VD5Ef7nCecxJmZNyRKcGkNiDQC1nhY5agppzwhqh7zEzNFarE/GqDgSiaDHS8yd5EgFhP9AQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "base64-js": "^1.3.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "expo": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/expo-dev-client": {
 | 
			
		||||
      "version": "5.0.12",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.0.12.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,8 @@
 | 
			
		||||
    "@expo/ngrok": "4.1.0",
 | 
			
		||||
    "expo-notifications": "~0.29.13",
 | 
			
		||||
    "expo-device": "~7.0.2",
 | 
			
		||||
    "expo-location": "~18.0.7"
 | 
			
		||||
    "expo-location": "~18.0.7",
 | 
			
		||||
    "expo-auth-session": "~6.0.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "^7.25.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,7 @@ async function registerForPushNotificationsAsync() {
 | 
			
		||||
    }
 | 
			
		||||
    const projectId = Constants.expoConfig?.extra?.eas?.projectId;
 | 
			
		||||
    if (!projectId) {
 | 
			
		||||
      console.warn('No project id found');
 | 
			
		||||
      alert('Project ID not found');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user