From d09fddf96046f52a2c100f7670bc6c420d8b86f3 Mon Sep 17 00:00:00 2001 From: Hunter Halloran Date: Fri, 9 Jan 2026 12:17:00 -0500 Subject: [PATCH 1/3] feat: Begin support for OIDC login --- docs/MICROSOFT_ENTRA_QUICKSTART.md | 109 ++++++ docs/MICROSOFT_ENTRA_SETUP.md | 342 ++++++++++++++++++ management-dashboard-web-app/.env.example | 7 + management-dashboard-web-app/src/App.tsx | 2 +- .../src/components/Login.tsx | 55 +++ .../src/components/MicrosoftIcon.tsx | 11 + .../src/vite-env.d.ts | 1 + package-lock.json | 61 ++++ package.json | 9 + 9 files changed, 596 insertions(+), 1 deletion(-) create mode 100644 docs/MICROSOFT_ENTRA_QUICKSTART.md create mode 100644 docs/MICROSOFT_ENTRA_SETUP.md create mode 100644 management-dashboard-web-app/src/components/MicrosoftIcon.tsx create mode 100644 package-lock.json create mode 100644 package.json diff --git a/docs/MICROSOFT_ENTRA_QUICKSTART.md b/docs/MICROSOFT_ENTRA_QUICKSTART.md new file mode 100644 index 0000000..9b343cf --- /dev/null +++ b/docs/MICROSOFT_ENTRA_QUICKSTART.md @@ -0,0 +1,109 @@ +# Microsoft Entra OpenID Connect - Quick Start + +## What Was Implemented + +The Login flow now supports Microsoft Entra ID (Azure AD) authentication via OpenID Connect using Supabase's Azure OAuth provider. + +## Files Modified + +1. **[Login.tsx](management-dashboard-web-app/src/components/Login.tsx)**: Added Microsoft login button and OAuth flow +2. **[MicrosoftIcon.tsx](management-dashboard-web-app/src/components/MicrosoftIcon.tsx)**: Created Microsoft logo component +3. **[vite-env.d.ts](management-dashboard-web-app/src/vite-env.d.ts)**: Added TypeScript type for new environment variable +4. **[.env.example](management-dashboard-web-app/.env.example)**: Added Microsoft login configuration + +## How It Works + +### User Experience +1. User visits login page and sees two options: + - Traditional email/password login (existing) + - "Sign in with Microsoft" button (new) +2. Clicking Microsoft button redirects to Microsoft login +3. User authenticates with Microsoft credentials +4. Microsoft redirects back to app with authenticated session + +### Technical Flow +``` +User clicks "Sign in with Microsoft" + ↓ +Supabase signInWithOAuth() with provider: 'azure' + ↓ +Redirect to Microsoft Entra login page + ↓ +User authenticates with Microsoft + ↓ +Microsoft redirects to Supabase callback URL + ↓ +Supabase validates and creates session + ↓ +User redirected back to application (authenticated) +``` + +## Configuration Required + +### 1. Azure Portal Setup +- Register application in Microsoft Entra ID +- Configure redirect URI: `https://.supabase.co/auth/v1/callback` +- Generate client ID and client secret +- Set API permissions (openid, profile, email) + +### 2. Supabase Configuration +Navigate to Authentication > Providers > Azure and configure: +- **Azure Client ID**: From Azure app registration +- **Azure Secret**: From Azure client secrets +- **Azure Tenant**: Use `common` for multi-tenant or specific tenant ID + +### 3. Application Environment +Set in `.env` file: +```bash +VITE_ENABLE_MICROSOFT_LOGIN=true +``` + +## Testing + +### Enable Microsoft Login +```bash +# In .env file +VITE_ENABLE_MICROSOFT_LOGIN=true +``` + +### Disable Microsoft Login +```bash +# In .env file +VITE_ENABLE_MICROSOFT_LOGIN=false +# Or simply remove the variable +``` + +## Features + +✅ **Hybrid Authentication**: Supports both email/password and Microsoft login +✅ **Feature Flag**: Microsoft login can be enabled/disabled via environment variable +✅ **Dark Mode Support**: Microsoft button matches the existing theme +✅ **Error Handling**: Displays authentication errors to users +✅ **Loading States**: Shows loading indicator during authentication +✅ **Type Safety**: Full TypeScript support with proper types + +## Next Steps + +1. **Complete Azure Setup**: Follow [MICROSOFT_ENTRA_SETUP.md](./MICROSOFT_ENTRA_SETUP.md) for detailed configuration +2. **Configure Supabase**: Enable Azure provider in Supabase dashboard +3. **Test Authentication**: Verify the complete login flow +4. **Deploy**: Update production environment variables + +## Documentation + +- **Full Setup Guide**: [MICROSOFT_ENTRA_SETUP.md](./MICROSOFT_ENTRA_SETUP.md) - Complete Azure and Supabase configuration +- **Supabase Docs**: https://supabase.com/docs/guides/auth/social-login/auth-azure +- **Microsoft Identity Platform**: https://docs.microsoft.com/azure/active-directory/develop/ + +## Support + +For issues or questions: +- Check [MICROSOFT_ENTRA_SETUP.md](./MICROSOFT_ENTRA_SETUP.md) troubleshooting section +- Review Supabase Auth logs +- Check Azure sign-in logs in Azure Portal +- Verify redirect URIs match exactly + +--- + +**Implementation Date**: January 9, 2026 +**Status**: Ready for configuration and testing diff --git a/docs/MICROSOFT_ENTRA_SETUP.md b/docs/MICROSOFT_ENTRA_SETUP.md new file mode 100644 index 0000000..38278bf --- /dev/null +++ b/docs/MICROSOFT_ENTRA_SETUP.md @@ -0,0 +1,342 @@ +# Microsoft Entra (Azure AD) OpenID Connect Setup Guide + +## Overview + +This guide walks you through configuring Microsoft Entra ID (formerly Azure Active Directory) authentication for the USDA Vision Management Dashboard using Supabase's Azure OAuth provider. + +## Prerequisites + +- Access to Azure Portal (https://portal.azure.com) +- Admin permissions to register applications in Azure AD +- Access to your Supabase project dashboard +- The USDA Vision application deployed and accessible via URL + +## Step 1: Register Application in Microsoft Entra ID + +### 1.1 Navigate to Azure Portal + +1. Log in to [Azure Portal](https://portal.azure.com) +2. Navigate to **Microsoft Entra ID** (or **Azure Active Directory**) +3. Select **App registrations** from the left sidebar +4. Click **+ New registration** + +### 1.2 Configure Application Registration + +Fill in the following details: + +- **Name**: `USDA Vision Management Dashboard` (or your preferred name) +- **Supported account types**: Choose one of: + - **Single tenant**: Only users in your organization can sign in (most restrictive) + - **Multitenant**: Users in any Azure AD tenant can sign in + - **Multitenant + personal Microsoft accounts**: Broadest support +- **Redirect URI**: + - Platform: **Web** + - URI: `https://.supabase.co/auth/v1/callback` + - Example: `https://abcdefghij.supabase.co/auth/v1/callback` + +Click **Register** to create the application. + +### 1.3 Note Application (Client) ID + +After registration, you'll be taken to the app overview page. Copy and save: +- **Application (client) ID**: This is your `AZURE_CLIENT_ID` +- **Directory (tenant) ID**: This is your `AZURE_TENANT_ID` + +## Step 2: Configure Client Secret + +### 2.1 Create a Client Secret + +1. In your app registration, navigate to **Certificates & secrets** +2. Click **+ New client secret** +3. Add a description: `Supabase Auth` +4. Choose an expiration period (recommendation: 12-24 months) +5. Click **Add** + +### 2.2 Save the Secret Value + +**IMPORTANT**: Copy the **Value** immediately - it will only be shown once! +- This is your `AZURE_CLIENT_SECRET` +- Store it securely (password manager, secure vault, etc.) + +## Step 3: Configure API Permissions + +### 3.1 Add Required Permissions + +1. Navigate to **API permissions** in your app registration +2. Click **+ Add a permission** +3. Select **Microsoft Graph** +4. Choose **Delegated permissions** +5. Add the following permissions: + - `openid` (Sign users in) + - `profile` (View users' basic profile) + - `email` (View users' email address) + - `User.Read` (Sign in and read user profile) + +6. Click **Add permissions** + +### 3.2 Grant Admin Consent (Optional but Recommended) + +If you have admin privileges: +1. Click **Grant admin consent for [Your Organization]** +2. Confirm the action + +This prevents users from seeing a consent prompt on first login. + +## Step 4: Configure Authentication Settings + +### 4.1 Set Token Configuration + +1. Navigate to **Token configuration** in your app registration +2. Click **+ Add optional claim** +3. Choose **ID** token type +4. Add the following claims: + - `email` + - `family_name` + - `given_name` + - `upn` (User Principal Name) + +5. Check **Turn on the Microsoft Graph email, profile permission** if prompted + +### 4.2 Configure Authentication Flow + +1. Navigate to **Authentication** in your app registration +2. Under **Implicit grant and hybrid flows**, ensure: + - ✅ **ID tokens** (used for implicit and hybrid flows) is checked +3. Under **Allow public client flows**: + - Select **No** (keep it secure) + +## Step 5: Configure Supabase + +### 5.1 Navigate to Supabase Auth Settings + +1. Log in to your [Supabase Dashboard](https://app.supabase.com) +2. Select your project +3. Navigate to **Authentication** > **Providers** +4. Find **Azure** in the provider list + +### 5.2 Enable and Configure Azure Provider + +1. Toggle **Enable Sign in with Azure** to ON +2. Fill in the configuration: + + - **Azure Client ID**: Paste your Application (client) ID from Step 1.3 + - **Azure Secret**: Paste your client secret value from Step 2.2 + - **Azure Tenant**: You have two options: + - Use `common` for multi-tenant applications + - Use your specific **Directory (tenant) ID** for single-tenant + - Format: Just the GUID (e.g., `12345678-1234-1234-1234-123456789012`) + +3. Click **Save** + +### 5.3 Note the Callback URL + +Supabase provides the callback URL in the format: +``` +https://.supabase.co/auth/v1/callback +``` + +Verify this matches what you configured in Azure (Step 1.2). + +## Step 6: Configure Application Environment + +### 6.1 Update Environment Variables + +In your application's `.env` file, add or update: + +```bash +# Supabase Configuration (if not already present) +VITE_SUPABASE_URL=https://.supabase.co +VITE_SUPABASE_ANON_KEY= + +# Enable Microsoft Login +VITE_ENABLE_MICROSOFT_LOGIN=true +``` + +### 6.2 Restart Development Server + +If running locally: +```bash +npm run dev +``` + +## Step 7: Test the Integration + +### 7.1 Test Login Flow + +1. Navigate to your application's login page +2. You should see a "Sign in with Microsoft" button +3. Click the button +4. You should be redirected to Microsoft's login page +5. Sign in with your Microsoft account +6. Grant consent if prompted +7. You should be redirected back to your application, logged in + +### 7.2 Verify User Data + +After successful login, check that: +- User profile information is available +- Email address is correctly populated +- User roles/permissions are properly assigned (if using RBAC) + +### 7.3 Common Issues and Troubleshooting + +#### Redirect URI Mismatch +**Error**: `AADSTS50011: The redirect URI ... does not match the redirect URIs configured` + +**Solution**: Ensure the redirect URI in Azure matches exactly: +``` +https://.supabase.co/auth/v1/callback +``` + +#### Invalid Client Secret +**Error**: `Invalid client secret provided` + +**Solution**: +- Verify you copied the secret **Value**, not the Secret ID +- Generate a new client secret if the old one expired + +#### Missing Permissions +**Error**: User consent required or permission denied + +**Solution**: +- Add the required API permissions in Azure (Step 3) +- Grant admin consent if available + +#### CORS Errors +**Error**: CORS policy blocking requests + +**Solution**: +- Ensure your application URL is properly configured in Supabase +- Check that you're using the correct Supabase URL + +## Step 8: Production Deployment + +### 8.1 Update Redirect URI for Production + +1. Go back to Azure Portal > App registrations +2. Navigate to **Authentication** +3. Add production redirect URI: + ``` + https://your-production-domain.com/ + ``` +4. Ensure Supabase callback URI is still present + +### 8.2 Set Production Environment Variables + +Update your production environment with: +```bash +VITE_SUPABASE_URL=https://.supabase.co +VITE_SUPABASE_ANON_KEY= +VITE_ENABLE_MICROSOFT_LOGIN=true +``` + +### 8.3 Security Best Practices + +1. **Rotate Secrets Regularly**: Set calendar reminders before client secret expiration +2. **Use Azure Key Vault**: Store secrets in Azure Key Vault for enhanced security +3. **Monitor Sign-ins**: Use Azure AD sign-in logs to monitor authentication activity +4. **Implement MFA**: Require multi-factor authentication for sensitive accounts +5. **Review Permissions**: Regularly audit API permissions and remove unnecessary ones + +## Advanced Configuration + +### Multi-Tenant Support + +If you want to support users from multiple Azure AD tenants: + +1. In Azure App Registration > **Authentication**: + - Set **Supported account types** to **Multitenant** + +2. In Supabase Azure provider configuration: + - Set **Azure Tenant** to `common` + +### Custom Domain Configuration + +If using a custom domain with Supabase: + +1. Configure custom domain in Supabase dashboard +2. Update redirect URI in Azure to use custom domain +3. Update application environment variables + +### User Attribute Mapping + +To map Azure AD attributes to Supabase user metadata: + +1. Use Supabase database triggers or functions +2. Extract attributes from JWT token +3. Update user metadata in `auth.users` table + +Example attributes available: +- `sub`: User's unique ID +- `email`: Email address +- `name`: Full name +- `given_name`: First name +- `family_name`: Last name +- `preferred_username`: Username + +## Integration with RBAC System + +To integrate Microsoft Entra authentication with your existing RBAC system: + +### Option 1: Manual Role Assignment + +After user signs in via Microsoft: +1. Admin assigns roles in the management dashboard +2. Roles stored in your `user_roles` table +3. User gets appropriate permissions on next login + +### Option 2: Azure AD Group Mapping + +Map Azure AD groups to application roles: +1. Configure group claims in Azure token configuration +2. Read groups from JWT token in Supabase +3. Automatically assign roles based on group membership + +### Option 3: Hybrid Approach + +Support both Microsoft and email/password login: +- Keep existing email/password authentication +- Add Microsoft as an additional option +- Users can link accounts or use either method + +## Monitoring and Maintenance + +### Azure AD Monitoring + +- **Sign-in logs**: Monitor authentication attempts +- **Audit logs**: Track configuration changes +- **Usage analytics**: Understand authentication patterns + +### Supabase Monitoring + +- **Auth logs**: View authentication events +- **User activity**: Track active users +- **Error logs**: Identify authentication issues + +### Regular Maintenance Tasks + +- [ ] Rotate client secrets before expiration (set reminders) +- [ ] Review and update API permissions quarterly +- [ ] Audit user access and remove inactive users +- [ ] Update token configuration as needed +- [ ] Test authentication flow after any infrastructure changes + +## Support and Resources + +### Microsoft Documentation +- [Microsoft identity platform documentation](https://docs.microsoft.com/azure/active-directory/develop/) +- [Azure AD app registration](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) +- [OpenID Connect on Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc) + +### Supabase Documentation +- [Supabase Auth with Azure](https://supabase.com/docs/guides/auth/social-login/auth-azure) +- [Supabase Auth API reference](https://supabase.com/docs/reference/javascript/auth-signinwithoauth) + +### Troubleshooting Resources +- Azure AD Error Codes: https://docs.microsoft.com/azure/active-directory/develop/reference-aadsts-error-codes +- Supabase Discord Community: https://discord.supabase.com + +--- + +**Last Updated**: January 2026 +**Maintained By**: USDA Vision System Team diff --git a/management-dashboard-web-app/.env.example b/management-dashboard-web-app/.env.example index 91a7ca1..f292910 100755 --- a/management-dashboard-web-app/.env.example +++ b/management-dashboard-web-app/.env.example @@ -10,3 +10,10 @@ VITE_VISION_SYSTEM_REMOTE_URL=http://exp-dash:3002/assets/remoteEntry.js?v=$(dat # API URLs VITE_VISION_API_URL=http://exp-dash:8000 VITE_MEDIA_API_URL=http://exp-dash:8090 + +# Supabase Configuration +VITE_SUPABASE_URL=https://your-project-url.supabase.co +VITE_SUPABASE_ANON_KEY=your-anon-key + +# Microsoft Entra (Azure AD) OAuth Configuration +VITE_ENABLE_MICROSOFT_LOGIN=true diff --git a/management-dashboard-web-app/src/App.tsx b/management-dashboard-web-app/src/App.tsx index f7f3786..2cae9fd 100755 --- a/management-dashboard-web-app/src/App.tsx +++ b/management-dashboard-web-app/src/App.tsx @@ -14,7 +14,7 @@ function App() { checkAuthState() // Listen for auth changes - const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => { + const { data: { subscription } } = supabase.auth.onAuthStateChange((event: string, session: any) => { console.log('Auth state changed:', event, !!session) setIsAuthenticated(!!session) setLoading(false) diff --git a/management-dashboard-web-app/src/components/Login.tsx b/management-dashboard-web-app/src/components/Login.tsx index 8e4929f..26a89c1 100755 --- a/management-dashboard-web-app/src/components/Login.tsx +++ b/management-dashboard-web-app/src/components/Login.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' import { supabase } from '../lib/supabase' +import { MicrosoftIcon } from './MicrosoftIcon' interface LoginProps { onLoginSuccess: () => void @@ -10,6 +11,7 @@ export function Login({ onLoginSuccess }: LoginProps) { const [password, setPassword] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const enableMicrosoftLogin = import.meta.env.VITE_ENABLE_MICROSOFT_LOGIN === 'true' const handleLogin = async (e: React.FormEvent) => { e.preventDefault() @@ -35,6 +37,32 @@ export function Login({ onLoginSuccess }: LoginProps) { } } + const handleMicrosoftLogin = async () => { + setLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'azure', + options: { + scopes: 'email openid profile', + redirectTo: `${window.location.origin}/`, + }, + }) + + if (error) { + setError(error.message) + setLoading(false) + } + // If successful, user will be redirected to Microsoft login + // and then back to the app, so we don't stop loading here + } catch (err) { + setError('An unexpected error occurred during Microsoft login') + console.error('Microsoft login error:', err) + setLoading(false) + } + } + return (
@@ -108,6 +136,33 @@ export function Login({ onLoginSuccess }: LoginProps) {
+ + {enableMicrosoftLogin && ( + <> +
+
+
+
+
+ + Or continue with + +
+
+ +
+ +
+ + )}
) diff --git a/management-dashboard-web-app/src/components/MicrosoftIcon.tsx b/management-dashboard-web-app/src/components/MicrosoftIcon.tsx new file mode 100644 index 0000000..8676966 --- /dev/null +++ b/management-dashboard-web-app/src/components/MicrosoftIcon.tsx @@ -0,0 +1,11 @@ +export function MicrosoftIcon({ className = "w-5 h-5" }: { className?: string }) { + return ( + + + + + + + + ) +} diff --git a/management-dashboard-web-app/src/vite-env.d.ts b/management-dashboard-web-app/src/vite-env.d.ts index 46479a0..14266d5 100755 --- a/management-dashboard-web-app/src/vite-env.d.ts +++ b/management-dashboard-web-app/src/vite-env.d.ts @@ -4,6 +4,7 @@ interface ImportMetaEnv { readonly VITE_SUPABASE_URL: string; readonly VITE_SUPABASE_ANON_KEY: string; readonly VITE_VISION_API_URL?: string; // optional; defaults to "/api" via vite proxy + readonly VITE_ENABLE_MICROSOFT_LOGIN?: string; // optional; enable Microsoft Entra authentication } interface ImportMeta { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4d8cb8b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,61 @@ +{ + "name": "usda-vision", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.7" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7ad4e9b --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.7" + } +} From 0b2c698ea5d5a1257527a413678b22c4ab3d2ffc Mon Sep 17 00:00:00 2001 From: Hunter Halloran Date: Fri, 9 Jan 2026 12:52:42 -0500 Subject: [PATCH 2/3] feat: Add Azure external auth provider --- .env.azure.example | 24 +++++ docs/MICROSOFT_ENTRA_QUICKSTART.md | 31 +++++- docs/MICROSOFT_ENTRA_SETUP.md | 91 ++++++++++++++++- docs/SELF_HOSTED_AZURE_SETUP.md | 158 +++++++++++++++++++++++++++++ supabase/config.toml | 12 +++ 5 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 .env.azure.example create mode 100644 docs/SELF_HOSTED_AZURE_SETUP.md diff --git a/.env.azure.example b/.env.azure.example new file mode 100644 index 0000000..4c7b6c6 --- /dev/null +++ b/.env.azure.example @@ -0,0 +1,24 @@ +# Microsoft Entra (Azure AD) OAuth Configuration for Self-Hosted Supabase +# Copy this file to your actual environment configuration and fill in the values + +# Azure Application (Client) ID +# Get this from Azure Portal > App registrations > Your app > Overview +AZURE_CLIENT_ID=your-application-client-id-here + +# Azure Client Secret +# Get this from Azure Portal > App registrations > Your app > Certificates & secrets +AZURE_CLIENT_SECRET=your-client-secret-value-here + +# Azure Tenant ID or 'common' +# Options: +# - 'common': Multi-tenant (any Azure AD organization) +# - 'organizations': Any Azure AD organization (excludes personal accounts) +# - 'consumers': Personal Microsoft accounts only +# - Your specific tenant ID: Single-tenant (e.g., '12345678-1234-1234-1234-123456789012') +# Get tenant ID from Azure Portal > App registrations > Your app > Overview +AZURE_TENANT_ID=common + +# Notes: +# 1. These variables are used in supabase/config.toml via env() substitution +# 2. Never commit this file with real secrets to git +# 3. After setting these, restart your Supabase services: docker-compose restart diff --git a/docs/MICROSOFT_ENTRA_QUICKSTART.md b/docs/MICROSOFT_ENTRA_QUICKSTART.md index 9b343cf..9f79d7a 100644 --- a/docs/MICROSOFT_ENTRA_QUICKSTART.md +++ b/docs/MICROSOFT_ENTRA_QUICKSTART.md @@ -42,16 +42,45 @@ User redirected back to application (authenticated) ### 1. Azure Portal Setup - Register application in Microsoft Entra ID -- Configure redirect URI: `https://.supabase.co/auth/v1/callback` +- Configure redirect URI: + - **Supabase Cloud**: `https://.supabase.co/auth/v1/callback` + - **Self-hosted**: `http://:/auth/v1/callback` - Generate client ID and client secret - Set API permissions (openid, profile, email) ### 2. Supabase Configuration + +#### For Supabase Cloud: Navigate to Authentication > Providers > Azure and configure: - **Azure Client ID**: From Azure app registration - **Azure Secret**: From Azure client secrets - **Azure Tenant**: Use `common` for multi-tenant or specific tenant ID +#### For Self-Hosted Supabase: + +Edit `supabase/config.toml`: +```toml +[auth.external.azure] +enabled = true +client_id = "env(AZURE_CLIENT_ID)" +secret = "env(AZURE_CLIENT_SECRET)" +redirect_uri = "" +url = "https://login.microsoftonline.com/env(AZURE_TENANT_ID)/v2.0" +skip_nonce_check = false +``` + +Set environment variables: +```bash +AZURE_CLIENT_ID="your-application-client-id" +AZURE_CLIENT_SECRET="your-client-secret" +AZURE_TENANT_ID="common" # or specific tenant ID +``` + +Restart Supabase: +```bash +docker-compose down && docker-compose up -d +``` + ### 3. Application Environment Set in `.env` file: ```bash diff --git a/docs/MICROSOFT_ENTRA_SETUP.md b/docs/MICROSOFT_ENTRA_SETUP.md index 38278bf..d532815 100644 --- a/docs/MICROSOFT_ENTRA_SETUP.md +++ b/docs/MICROSOFT_ENTRA_SETUP.md @@ -4,11 +4,13 @@ This guide walks you through configuring Microsoft Entra ID (formerly Azure Active Directory) authentication for the USDA Vision Management Dashboard using Supabase's Azure OAuth provider. +> **📌 Self-Hosted Supabase Users**: If you're using a self-hosted Supabase instance, see the simplified guide: [SELF_HOSTED_AZURE_SETUP.md](SELF_HOSTED_AZURE_SETUP.md). Self-hosted instances configure OAuth providers via `config.toml` and environment variables, not through the UI. + ## Prerequisites - Access to Azure Portal (https://portal.azure.com) - Admin permissions to register applications in Azure AD -- Access to your Supabase project dashboard +- Access to your Supabase project (Cloud dashboard or self-hosted instance) - The USDA Vision application deployed and accessible via URL ## Step 1: Register Application in Microsoft Entra ID @@ -107,14 +109,16 @@ This prevents users from seeing a consent prompt on first login. ## Step 5: Configure Supabase -### 5.1 Navigate to Supabase Auth Settings +### For Supabase Cloud (Hosted) + +#### 5.1 Navigate to Supabase Auth Settings 1. Log in to your [Supabase Dashboard](https://app.supabase.com) 2. Select your project 3. Navigate to **Authentication** > **Providers** 4. Find **Azure** in the provider list -### 5.2 Enable and Configure Azure Provider +#### 5.2 Enable and Configure Azure Provider 1. Toggle **Enable Sign in with Azure** to ON 2. Fill in the configuration: @@ -128,7 +132,7 @@ This prevents users from seeing a consent prompt on first login. 3. Click **Save** -### 5.3 Note the Callback URL +#### 5.3 Note the Callback URL Supabase provides the callback URL in the format: ``` @@ -137,6 +141,85 @@ https://.supabase.co/auth/v1/callback Verify this matches what you configured in Azure (Step 1.2). +### For Self-Hosted Supabase + +If you're running a self-hosted Supabase instance, OAuth providers are configured via the `config.toml` file and environment variables rather than through the UI. + +#### 5.1 Edit config.toml + +1. Open your `supabase/config.toml` file +2. Find or add the `[auth.external.azure]` section: + +```toml +[auth.external.azure] +enabled = true +client_id = "env(AZURE_CLIENT_ID)" +secret = "env(AZURE_CLIENT_SECRET)" +redirect_uri = "" +url = "https://login.microsoftonline.com/env(AZURE_TENANT_ID)/v2.0" +skip_nonce_check = false +``` + +3. Set `enabled = true` to activate Azure authentication + +#### 5.2 Set Environment Variables + +Create or update your environment file (`.env` or set in your deployment): + +```bash +# Azure AD OAuth Configuration +AZURE_CLIENT_ID="your-application-client-id-from-azure" +AZURE_CLIENT_SECRET="your-client-secret-from-azure" +AZURE_TENANT_ID="common" # or your specific tenant ID +``` + +**Important**: +- Use `common` for multi-tenant (any Azure AD organization) +- Use `organizations` for any Azure AD organization (excludes personal Microsoft accounts) +- Use `consumers` for personal Microsoft accounts only +- Use your specific tenant ID (GUID) for single-tenant applications + +#### 5.3 Update Azure Redirect URI + +For self-hosted Supabase, your callback URL will be: +``` +http://:/auth/v1/callback +``` + +For example, if your Supabase API is at `http://192.168.1.100:54321`: +``` +http://192.168.1.100:54321/auth/v1/callback +``` + +**Go back to Azure Portal** (Step 1.2) and add this redirect URI to your app registration. + +#### 5.4 Restart Supabase Services + +After making these changes, restart your Supabase services: + +```bash +# If using docker-compose +docker-compose down +docker-compose up -d + +# Or if using the provided script +./docker-compose.sh restart +``` + +#### 5.5 Verify Configuration + +Check that the auth service picked up your configuration: + +```bash +# View auth service logs +docker-compose logs auth + +# Or for specific service name +docker-compose logs supabase-auth +``` + +Look for log entries indicating Azure provider is enabled. + ## Step 6: Configure Application Environment ### 6.1 Update Environment Variables diff --git a/docs/SELF_HOSTED_AZURE_SETUP.md b/docs/SELF_HOSTED_AZURE_SETUP.md new file mode 100644 index 0000000..188f358 --- /dev/null +++ b/docs/SELF_HOSTED_AZURE_SETUP.md @@ -0,0 +1,158 @@ +# Self-Hosted Supabase - Microsoft Entra Setup + +## Quick Setup Guide + +For self-hosted Supabase instances, OAuth providers like Microsoft Entra (Azure AD) are configured through config files and environment variables, not through the UI. + +### Step 1: Configure Azure Application + +Follow steps 1-4 in [MICROSOFT_ENTRA_SETUP.md](MICROSOFT_ENTRA_SETUP.md) to: +1. Register your app in Azure Portal +2. Get your Client ID and Secret +3. Set up API permissions +4. Configure token claims + +**Important**: Your redirect URI should be: +``` +http://:/auth/v1/callback +``` + +Example: `http://192.168.1.100:54321/auth/v1/callback` + +### Step 2: Configure Supabase + +The Azure provider configuration is already added to `supabase/config.toml`: + +```toml +[auth.external.azure] +enabled = false # Change this to true +client_id = "env(AZURE_CLIENT_ID)" +secret = "env(AZURE_CLIENT_SECRET)" +redirect_uri = "" +url = "https://login.microsoftonline.com/env(AZURE_TENANT_ID)/v2.0" +skip_nonce_check = false +``` + +### Step 3: Set Environment Variables + +1. Copy the example file: + ```bash + cp .env.azure.example .env.azure + ``` + +2. Edit `.env.azure` with your actual values: + ```bash + AZURE_CLIENT_ID=your-application-client-id + AZURE_CLIENT_SECRET=your-client-secret + AZURE_TENANT_ID=common # or your specific tenant ID + ``` + +3. Source the environment file before starting Supabase: + ```bash + source .env.azure + ``` + + Or add it to your docker-compose environment. + +### Step 4: Enable Azure Provider + +Edit `supabase/config.toml` and change: +```toml +[auth.external.azure] +enabled = true # Change from false to true +``` + +### Step 5: Restart Supabase + +```bash +docker-compose down +docker-compose up -d +``` + +Or if using the project script: +```bash +./docker-compose.sh restart +``` + +### Step 6: Enable in Application + +In `management-dashboard-web-app/.env`: +```bash +VITE_ENABLE_MICROSOFT_LOGIN=true +``` + +### Verification + +1. Check auth service logs: + ```bash + docker-compose logs auth | grep -i azure + ``` + +2. You should see the Microsoft login button on your application's login page + +3. Click it and verify you're redirected to Microsoft login + +### Troubleshooting + +#### Azure Provider Not Working + +**Check logs**: +```bash +docker-compose logs auth +``` + +**Verify environment variables are loaded**: +```bash +docker-compose exec auth env | grep AZURE +``` + +#### Redirect URI Mismatch + +Ensure the redirect URI in Azure exactly matches: +``` +http://:/auth/v1/callback +``` + +Common mistake: Using `localhost` instead of the actual IP address. + +#### Environment Variables Not Set + +If you see errors about missing AZURE variables, make sure to: +1. Export them in your shell before running docker-compose +2. Or add them to your docker-compose.yml environment section +3. Or use a .env file that docker-compose automatically loads + +### Docker Compose Environment Variables + +You can also add the variables directly to your `docker-compose.yml`: + +```yaml +services: + auth: + environment: + AZURE_CLIENT_ID: ${AZURE_CLIENT_ID} + AZURE_CLIENT_SECRET: ${AZURE_CLIENT_SECRET} + AZURE_TENANT_ID: ${AZURE_TENANT_ID:-common} +``` + +Then create a `.env` file in the same directory: +```bash +AZURE_CLIENT_ID=your-client-id +AZURE_CLIENT_SECRET=your-secret +AZURE_TENANT_ID=common +``` + +### Security Notes + +- Never commit `.env.azure` or `.env` files with real secrets to git +- Add them to `.gitignore` +- Use environment variable substitution in config.toml +- Rotate client secrets regularly (before expiration) +- Monitor sign-in logs in Azure Portal + +### Additional Resources + +- Full setup guide: [MICROSOFT_ENTRA_SETUP.md](MICROSOFT_ENTRA_SETUP.md) +- Quick reference: [MICROSOFT_ENTRA_QUICKSTART.md](MICROSOFT_ENTRA_QUICKSTART.md) +- Supabase self-hosting docs: https://supabase.com/docs/guides/self-hosting +- Azure OAuth docs: https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow diff --git a/supabase/config.toml b/supabase/config.toml index b4e8807..8e5f4ef 100755 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -278,6 +278,18 @@ url = "" # If enabled, the nonce check will be skipped. Required for local sign in with Google auth. skip_nonce_check = false +[auth.external.azure] +enabled = "env(VITE_ENABLE_MICROSOFT_LOGIN)" +client_id = "env(AZURE_CLIENT_ID)" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(AZURE_CLIENT_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Azure tenant ID or 'common' for multi-tenant. Use 'common', 'organizations', 'consumers', or your specific tenant ID. +url = "https://login.microsoftonline.com/env(AZURE_TENANT_ID)/v2.0" +# If enabled, the nonce check will be skipped. +skip_nonce_check = false + # Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. # You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. [auth.web3.solana] From f625a3e9e16ce5ad191a86842802382b33b55be9 Mon Sep 17 00:00:00 2001 From: Hunter Halloran Date: Tue, 13 Jan 2026 13:47:33 -0500 Subject: [PATCH 3/3] feat: Enable UGA SSO with Microsoft Entra --- docs/OAUTH_USER_SYNC_FIX.md | 139 ++++++++++++++++++ management-dashboard-web-app/src/App.tsx | 9 +- .../src/lib/supabase.ts | 54 +++++++ .../migrations/00003_oauth_user_sync.sql | 46 ++++++ supabase/config.toml | 4 +- supabase/migrations/00003_oauth_user_sync.sql | 46 ++++++ 6 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 docs/OAUTH_USER_SYNC_FIX.md create mode 100644 management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql create mode 100644 supabase/migrations/00003_oauth_user_sync.sql diff --git a/docs/OAUTH_USER_SYNC_FIX.md b/docs/OAUTH_USER_SYNC_FIX.md new file mode 100644 index 0000000..8aa63b8 --- /dev/null +++ b/docs/OAUTH_USER_SYNC_FIX.md @@ -0,0 +1,139 @@ +# OAuth User Synchronization Fix + +## Problem +When a user signs on with an OAuth provider (Microsoft Entra/Azure AD) for the first time, the user is added to `auth.users` in Supabase but NOT to the application's `user_profiles` table. This causes the application to fail when trying to load user data, as there's no user profile available. + +## Solution +Implemented automatic user profile creation for OAuth users through a multi-layered approach: + +### 1. Database Trigger (00003_oauth_user_sync.sql) +- **Location**: `supabase/migrations/00003_oauth_user_sync.sql` and `management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql` +- **Function**: `handle_new_oauth_user()` + - Automatically creates a user profile in `public.user_profiles` when a new user is created in `auth.users` + - Handles the synchronization at the database level, ensuring it works regardless of where users are created + - Includes race condition handling with `ON CONFLICT DO NOTHING` + +- **Trigger**: `on_auth_user_created` + - Fires after INSERT on `auth.users` + - Ensures every new OAuth user gets a profile entry + +### 2. Client-Side Utility Function (src/lib/supabase.ts) +- **Function**: `userManagement.syncOAuthUser()` + - Provides a fallback synchronization mechanism for any OAuth users that slip through + - Checks if user profile exists before creating + - Handles race conditions gracefully (duplicate key errors) + - Includes comprehensive error logging for debugging + +**Logic**: +```typescript +1. Get current authenticated user from Supabase Auth +2. Check if user profile already exists in user_profiles table +3. If exists: return (no action needed) +4. If not exists: create user profile with: + - id: user's UUID from auth.users + - email: user's email from auth.users + - status: 'active' (default status) +5. Handle errors gracefully: + - "No rows returned" (PGRST116): Expected, user doesn't exist yet + - "Duplicate key" (23505): Race condition, another process created it first + - Other errors: Log and continue +``` + +### 3. App Integration (src/App.tsx) +- **Updated**: Auth state change listener +- **Triggers**: When `SIGNED_IN` or `INITIAL_SESSION` event occurs +- **Action**: Calls `userManagement.syncOAuthUser()` asynchronously +- **Benefit**: Ensures user profile exists before the rest of the app tries to access it + +## How It Works + +### OAuth Sign-In Flow (New) +``` +1. User clicks "Sign in with Microsoft" + ↓ +2. Redirected to Microsoft login + ↓ +3. Microsoft authenticates and redirects back + ↓ +4. Supabase creates entry in auth.users + ↓ +5. Database trigger fires → user_profiles entry created + ↓ +6. App receives SIGNED_IN event + ↓ +7. App calls syncOAuthUser() as extra safety measure + ↓ +8. User profile is guaranteed to exist + ↓ +9. getUserProfile() and loadData() succeed +``` + +## Backward Compatibility +- **Non-invasive**: The solution uses triggers and utility functions, doesn't modify existing tables +- **Graceful degradation**: If either layer fails, the other provides a fallback +- **No breaking changes**: Existing APIs and components remain unchanged + +## Testing Recommendations + +### Test 1: First-Time OAuth Sign-In +1. Clear browser cookies/session +2. Click "Sign in with Microsoft" +3. Complete OAuth flow +4. Verify: + - User is in Supabase auth + - User is in `user_profiles` table + - App loads user data without errors + - Dashboard displays correctly + +### Test 2: Verify Database Trigger +1. Directly create a user in `auth.users` via SQL +2. Verify that `user_profiles` entry is automatically created +3. Check timestamp to confirm trigger fired + +### Test 3: Verify Client-Side Fallback +1. Manually delete a user's `user_profiles` entry +2. Reload the app +3. Verify that `syncOAuthUser()` recreates the profile +4. Check browser console for success logs + +## Files Modified +1. **Database Migrations**: + - `/usda-vision/supabase/migrations/00003_oauth_user_sync.sql` (NEW) + - `/usda-vision/management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql` (NEW) + +2. **TypeScript/React**: + - `/usda-vision/management-dashboard-web-app/src/lib/supabase.ts` (MODIFIED) + - Added `syncOAuthUser()` method to `userManagement` object + + - `/usda-vision/management-dashboard-web-app/src/App.tsx` (MODIFIED) + - Import `userManagement` + - Call `syncOAuthUser()` on auth state change + +## Deployment Steps + +1. **Apply Database Migrations**: + ```bash + # Run the new migration + supabase migration up + ``` + +2. **Deploy Application Code**: + - Push the changes to `src/lib/supabase.ts` and `src/App.tsx` + - No environment variable changes needed + - No configuration changes needed + +3. **Test in Staging**: + - Test OAuth sign-in with a fresh account + - Verify user profile is created + - Check app functionality + +4. **Monitor in Production**: + - Watch browser console for any errors from `syncOAuthUser()` + - Check database logs to confirm trigger is firing + - Monitor user creation metrics + +## Future Enhancements +- Assign default roles to new OAuth users (currently requires manual assignment) +- Pre-populate `first_name` and `last_name` from OAuth provider data +- Add user profile completion workflow for new OAuth users +- Auto-disable account creation for users outside organization diff --git a/management-dashboard-web-app/src/App.tsx b/management-dashboard-web-app/src/App.tsx index 2cae9fd..195558a 100755 --- a/management-dashboard-web-app/src/App.tsx +++ b/management-dashboard-web-app/src/App.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { supabase } from './lib/supabase' +import { supabase, userManagement } from './lib/supabase' import { Login } from './components/Login' import { Dashboard } from './components/Dashboard' import { CameraRoute } from './components/CameraRoute' @@ -19,6 +19,13 @@ function App() { setIsAuthenticated(!!session) setLoading(false) + // Sync OAuth user on successful sign in (creates user profile if needed) + if ((event === 'SIGNED_IN' || event === 'INITIAL_SESSION') && session) { + userManagement.syncOAuthUser().catch((err) => { + console.error('Failed to sync OAuth user:', err) + }) + } + // Handle signout route if (event === 'SIGNED_OUT') { setCurrentRoute('/') diff --git a/management-dashboard-web-app/src/lib/supabase.ts b/management-dashboard-web-app/src/lib/supabase.ts index 720182b..af5cebc 100755 --- a/management-dashboard-web-app/src/lib/supabase.ts +++ b/management-dashboard-web-app/src/lib/supabase.ts @@ -557,6 +557,60 @@ export const userManagement = { if (error) throw error return data + }, + + // Sync OAuth user - ensures user profile exists for OAuth-authenticated users + async syncOAuthUser(): Promise { + try { + const { data: { user: authUser }, error: authError } = await supabase.auth.getUser() + + if (authError || !authUser) { + console.warn('No authenticated user found for OAuth sync') + return + } + + // Check if user profile already exists + const { data: existingProfile, error: checkError } = await supabase + .from('user_profiles') + .select('id') + .eq('id', authUser.id) + .single() + + // If profile already exists, no need to create it + if (existingProfile && !checkError) { + console.log('User profile already exists for user:', authUser.id) + return + } + + // If error is not "no rows returned", it's a real error + if (checkError && checkError.code !== 'PGRST116') { + console.error('Error checking for existing profile:', checkError) + return + } + + // Create user profile for new OAuth user + const { error: insertError } = await supabase + .from('user_profiles') + .insert({ + id: authUser.id, + email: authUser.email || '', + status: 'active' + }) + + if (insertError) { + // Ignore "duplicate key value" errors in case of race condition + if (insertError.code === '23505') { + console.log('User profile was already created (race condition handled)') + return + } + console.error('Error creating user profile for OAuth user:', insertError) + return + } + + console.log('Successfully created user profile for OAuth user:', authUser.id) + } catch (error) { + console.error('Unexpected error in syncOAuthUser:', error) + } } } diff --git a/management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql b/management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql new file mode 100644 index 0000000..26101fb --- /dev/null +++ b/management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql @@ -0,0 +1,46 @@ +-- OAuth User Synchronization +-- This migration adds functionality to automatically create user profiles when users sign up via OAuth + +-- ============================================= +-- 1. CREATE FUNCTION FOR OAUTH USER AUTO-PROFILE CREATION +-- ============================================= + +CREATE OR REPLACE FUNCTION public.handle_new_oauth_user() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if user profile already exists + IF NOT EXISTS ( + SELECT 1 FROM public.user_profiles WHERE id = NEW.id + ) THEN + -- Create user profile with default active status + INSERT INTO public.user_profiles (id, email, status) + VALUES ( + NEW.id, + NEW.email, + 'active' + ) + ON CONFLICT (id) DO NOTHING; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- ============================================= +-- 2. CREATE TRIGGER FOR NEW AUTH USERS +-- ============================================= + +-- Drop the trigger if it exists to avoid conflicts +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; + +-- Create trigger that fires after a new user is created in auth.users +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_oauth_user(); + +-- ============================================= +-- 3. COMMENT FOR DOCUMENTATION +-- ============================================= + +COMMENT ON FUNCTION public.handle_new_oauth_user() IS +'Automatically creates a user profile in public.user_profiles when a new user is created via OAuth in auth.users. This ensures OAuth users are immediately accessible in the application without manual provisioning.'; diff --git a/supabase/config.toml b/supabase/config.toml index 8e5f4ef..0cfe5de 100755 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -284,9 +284,9 @@ client_id = "env(AZURE_CLIENT_ID)" # DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: secret = "env(AZURE_CLIENT_SECRET)" # Overrides the default auth redirectUrl. -redirect_uri = "" +redirect_uri = "env(AZURE_REDIRECT_URI)" # Azure tenant ID or 'common' for multi-tenant. Use 'common', 'organizations', 'consumers', or your specific tenant ID. -url = "https://login.microsoftonline.com/env(AZURE_TENANT_ID)/v2.0" +url = "env(AZURE_TENANT_URL)" # If enabled, the nonce check will be skipped. skip_nonce_check = false diff --git a/supabase/migrations/00003_oauth_user_sync.sql b/supabase/migrations/00003_oauth_user_sync.sql new file mode 100644 index 0000000..26101fb --- /dev/null +++ b/supabase/migrations/00003_oauth_user_sync.sql @@ -0,0 +1,46 @@ +-- OAuth User Synchronization +-- This migration adds functionality to automatically create user profiles when users sign up via OAuth + +-- ============================================= +-- 1. CREATE FUNCTION FOR OAUTH USER AUTO-PROFILE CREATION +-- ============================================= + +CREATE OR REPLACE FUNCTION public.handle_new_oauth_user() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if user profile already exists + IF NOT EXISTS ( + SELECT 1 FROM public.user_profiles WHERE id = NEW.id + ) THEN + -- Create user profile with default active status + INSERT INTO public.user_profiles (id, email, status) + VALUES ( + NEW.id, + NEW.email, + 'active' + ) + ON CONFLICT (id) DO NOTHING; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- ============================================= +-- 2. CREATE TRIGGER FOR NEW AUTH USERS +-- ============================================= + +-- Drop the trigger if it exists to avoid conflicts +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; + +-- Create trigger that fires after a new user is created in auth.users +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_oauth_user(); + +-- ============================================= +-- 3. COMMENT FOR DOCUMENTATION +-- ============================================= + +COMMENT ON FUNCTION public.handle_new_oauth_user() IS +'Automatically creates a user profile in public.user_profiles when a new user is created via OAuth in auth.users. This ensures OAuth users are immediately accessible in the application without manual provisioning.';