Compare commits
6 Commits
c6250d2b47
...
967829b612
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
967829b612 | ||
|
|
08e56a22d8 | ||
|
|
fa1adcf291 | ||
|
|
079c0f947c | ||
|
|
06d6845456 | ||
|
|
219ad11202 |
8
.env
Normal file
8
.env
Normal file
@@ -0,0 +1,8 @@
|
||||
SITE_URL=https://with.dwindi.com/
|
||||
VITE_APP_ENV=production
|
||||
VITE_GOOGLE_CLIENT_ID=650232746742-nup9nrp27001n0c6a3vqlc156g4tqfqa.apps.googleusercontent.com
|
||||
VITE_PAKASIR_API_KEY=iP13osgh7lAzWWIPsj7TbW5M3iGEAQMo
|
||||
VITE_PAKASIR_PROJECT_SLUG=withdwindi
|
||||
VITE_SUPABASE_ANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTc2NjAzNzEyMCwiZXhwIjo0OTIxNzEwNzIwLCJyb2xlIjoiYW5vbiJ9.Sa-eECy9dgBUQy3O4X5X-3tDPmF01J5zeT-Qtb-koYc
|
||||
VITE_SUPABASE_EDGE_URL=https://lovable.backoffice.biz.id/functions/v1
|
||||
VITE_SUPABASE_URL=https://lovable.backoffice.biz.id/
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.env
|
||||
|
||||
17
check-template.sql
Normal file
17
check-template.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Check if the email template exists and has content
|
||||
SELECT
|
||||
key,
|
||||
name,
|
||||
is_active,
|
||||
email_subject,
|
||||
LENGTH(email_body_html) as html_length,
|
||||
SUBSTRING(email_body_html, 1, 500) as html_preview,
|
||||
CASE
|
||||
WHEN email_body_html IS NULL THEN 'NULL - empty template'
|
||||
WHEN LENGTH(email_body_html) < 100 THEN 'TOO SHORT - template incomplete'
|
||||
WHEN email_body_html LIKE '%<html>%' THEN 'Has HTML tag'
|
||||
WHEN email_body_html LIKE '%---%' THEN 'Has YAML delimiters'
|
||||
ELSE 'Unknown format'
|
||||
END as template_status
|
||||
FROM notification_templates
|
||||
WHERE key = 'auth_email_verification';
|
||||
14
check_email_logs.sql
Normal file
14
check_email_logs.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Check recent notification logs for auth_email_verification
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
email,
|
||||
notification_type,
|
||||
status,
|
||||
provider,
|
||||
error_message,
|
||||
created_at
|
||||
FROM notification_logs
|
||||
WHERE notification_type = 'auth_email_verification'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5;
|
||||
20
check_template.sh
Executable file
20
check_template.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test query to check if template exists and what's in the table
|
||||
# Run this in your Supabase SQL editor or via psql
|
||||
|
||||
echo "=== Check if template exists ==="
|
||||
cat << 'SQL'
|
||||
-- Check if template exists
|
||||
SELECT key, name, is_active
|
||||
FROM notification_templates
|
||||
WHERE key = 'auth_email_verification';
|
||||
|
||||
-- Check all templates
|
||||
SELECT key, name, is_active
|
||||
FROM notification_templates
|
||||
ORDER BY key;
|
||||
|
||||
-- Check table structure
|
||||
\d notification_templates;
|
||||
SQL
|
||||
30
cleanup-user.sql
Normal file
30
cleanup-user.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
-- ============================================================================
|
||||
-- Clean Up User from Supabase Auth Completely
|
||||
-- ============================================================================
|
||||
|
||||
-- NOTE: You CANNOT just DELETE from auth.users
|
||||
-- Supabase keeps deleted users in a recycle bin
|
||||
|
||||
-- To completely remove a user, you need to use Supabase Auth Admin API
|
||||
-- OR use a cascade delete from a linked table
|
||||
|
||||
-- Option 1: Delete via cascade (if you have foreign keys)
|
||||
-- This works because auth_otps has ON DELETE CASCADE
|
||||
DELETE FROM auth.users WHERE email = 'your@email.com';
|
||||
|
||||
-- Option 2: Check if user still exists in recycle bin
|
||||
SELECT id, email, deleted_at
|
||||
FROM auth.users
|
||||
WHERE email = 'your@email.com';
|
||||
|
||||
-- If you see deleted_at IS NOT NULL, the user is in recycle bin
|
||||
|
||||
-- To permanently delete from recycle bin, you need to:
|
||||
-- 1. Go to Supabase Dashboard → Authentication → Users
|
||||
-- 2. Find the user
|
||||
-- 3. Click "Permanently delete"
|
||||
|
||||
-- OR use the Auth Admin API from an edge function:
|
||||
/*
|
||||
const { data, error } = await supabase.auth.admin.deleteUser(userId);
|
||||
*/
|
||||
91
debug-email.sh
Executable file
91
debug-email.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script to debug email sending issue
|
||||
# Run this after registering a user
|
||||
|
||||
echo "🔍 OTP Email Debug Script"
|
||||
echo "==========================="
|
||||
echo ""
|
||||
|
||||
SUPABASE_URL="https://lovable.backoffice.biz.id"
|
||||
SERVICE_KEY="YOUR_SERVICE_ROLE_KEY_HERE" # Replace with actual service role key
|
||||
|
||||
echo "1. Checking recent OTP records..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " id,"
|
||||
echo " user_id,"
|
||||
echo " email,"
|
||||
echo " otp_code,"
|
||||
echo " expires_at,"
|
||||
echo " used_at,"
|
||||
echo " created_at"
|
||||
echo "FROM auth_otps"
|
||||
echo "ORDER BY created_at DESC"
|
||||
echo "LIMIT 1;"
|
||||
echo ""
|
||||
|
||||
echo "2. Checking notification logs..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " id,"
|
||||
echo " user_id,"
|
||||
echo " email,"
|
||||
echo " notification_type,"
|
||||
echo " status,"
|
||||
echo " provider,"
|
||||
echo " error_message,"
|
||||
echo " created_at"
|
||||
echo "FROM notification_logs"
|
||||
echo "WHERE notification_type = 'auth_email_verification'"
|
||||
echo "ORDER BY created_at DESC"
|
||||
echo "LIMIT 5;"
|
||||
echo ""
|
||||
|
||||
echo "3. Checking notification settings..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " platform_name,"
|
||||
echo " from_name,"
|
||||
echo " from_email,"
|
||||
echo " api_token,"
|
||||
echo " mailketing_api_token"
|
||||
echo "FROM notification_settings"
|
||||
echo "LIMIT 1;"
|
||||
echo ""
|
||||
|
||||
echo "4. Checking email template..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " key,"
|
||||
echo " name,"
|
||||
echo " is_active,"
|
||||
echo " email_subject,"
|
||||
echo " LEFT(email_body_html, 200) as email_preview"
|
||||
echo "FROM notification_templates"
|
||||
echo "WHERE key = 'auth_email_verification';"
|
||||
echo ""
|
||||
|
||||
echo "5. Testing email sending manually..."
|
||||
echo "Replace USER_ID and EMAIL with actual values from step 1, then run:"
|
||||
echo ""
|
||||
echo "curl -X POST ${SUPABASE_URL}/functions/v1/send-auth-otp \\"
|
||||
echo " -H \"Authorization: Bearer ${SERVICE_KEY}\" \\"
|
||||
echo " -H \"Content-Type: application/json\" \\"
|
||||
echo " -d '{"
|
||||
echo " \"user_id\": \"USER_UUID\","
|
||||
echo " \"email\": \"your@email.com\""
|
||||
echo " }'"
|
||||
echo ""
|
||||
|
||||
echo "6. Common issues to check:"
|
||||
echo " ✓ from_email is not 'noreply@example.com' (set real domain)"
|
||||
echo " ✓ api_token or mailketing_api_token is set"
|
||||
echo " ✓ Email template is_active = true"
|
||||
echo " ✓ Mailketing API is accessible from Supabase server"
|
||||
echo " ✓ Check notification_logs.error_message for specific error"
|
||||
echo ""
|
||||
18
deploy-auth-functions.sh
Executable file
18
deploy-auth-functions.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Deploy Auth OTP Edge Functions to Self-Hosted Supabase
|
||||
|
||||
SUPABASE_URL="https://lovable.backoffice.biz.id"
|
||||
SERVICE_ROLE_KEY="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTc2NjAzNzEyMCwiZXhwIjo0OTIxNzEwNzIwLCJyb2xlIjoic2VydmljZV9yb2xlIn0.t6D9VwaukYGq4c_VbW1bkd3ZkKgldpCKRR13nN14XXc"
|
||||
|
||||
echo "Deploying send-auth-otp..."
|
||||
|
||||
curl -X POST "${SUPABASE_URL}/functions/v1/send-auth-otp" \
|
||||
-H "Authorization: Bearer ${SERVICE_ROLE_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"user_id":"test","email":"test@test.com"}' \
|
||||
-v
|
||||
|
||||
echo ""
|
||||
echo "If you see a response above, the function is deployed."
|
||||
echo "If you see 404, the function needs to be deployed manually to your Supabase instance."
|
||||
341
otp-testing-guide.md
Normal file
341
otp-testing-guide.md
Normal file
@@ -0,0 +1,341 @@
|
||||
# OTP Email Verification Testing Guide
|
||||
|
||||
## Current Status
|
||||
✅ **Backend Working**: Edge functions tested with curl and working
|
||||
✅ **Database Setup**: Migrations applied, tables created
|
||||
⚠️ **Frontend Integration**: Need to test and debug
|
||||
|
||||
## What Should Happen
|
||||
|
||||
### Registration Flow
|
||||
1. User fills registration form (name, email, password)
|
||||
2. Clicks "Daftar" (Register) button
|
||||
3. Supabase Auth creates user account
|
||||
4. Frontend calls `send-auth-otp` edge function
|
||||
5. Edge function:
|
||||
- Generates 6-digit OTP
|
||||
- Stores in `auth_otps` table (15 min expiry)
|
||||
- Fetches email template from `notification_templates`
|
||||
- Sends email via Mailketing API
|
||||
6. Frontend shows OTP input form
|
||||
7. User receives email with 6-digit code
|
||||
8. User enters OTP code
|
||||
9. Frontend calls `verify-auth-otp` edge function
|
||||
10. Edge function:
|
||||
- Validates OTP (not expired, not used)
|
||||
- Marks OTP as used
|
||||
- Confirms email in Supabase Auth
|
||||
11. User can now login
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### 1. Backend Verification (Already Done ✅)
|
||||
|
||||
Test edge function with curl:
|
||||
```bash
|
||||
curl -X POST https://lovable.backoffice.biz.id/functions/v1/send-auth-otp \
|
||||
-H "Authorization: Bearer YOUR_SERVICE_ROLE_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"user_id":"USER_UUID","email":"test@example.com"}'
|
||||
```
|
||||
|
||||
Expected: `{"success":true,"message":"OTP sent successfully"}`
|
||||
|
||||
### 2. Frontend Testing (Do This Now)
|
||||
|
||||
#### Step 1: Open Browser DevTools
|
||||
1. Open your browser
|
||||
2. Press F12 (or Cmd+Option+I on Mac)
|
||||
3. Go to **Console** tab
|
||||
4. Go to **Network** tab
|
||||
|
||||
#### Step 2: Attempt Registration
|
||||
1. Navigate to `/auth` page
|
||||
2. Click "Belum punya akun? Daftar" (switch to registration)
|
||||
3. Fill in:
|
||||
- Nama: Test User
|
||||
- Email: Your real email address
|
||||
- Password: Any password (6+ characters)
|
||||
4. Click "Daftar" button
|
||||
|
||||
#### Step 3: Check Console Logs
|
||||
You should see these logs in order:
|
||||
|
||||
**Log 1:**
|
||||
```
|
||||
SignUp result: {
|
||||
error: null,
|
||||
data: { user: {...}, session: null },
|
||||
hasUser: true,
|
||||
hasSession: false
|
||||
}
|
||||
```
|
||||
If you see this → User creation succeeded ✅
|
||||
|
||||
**Log 2:**
|
||||
```
|
||||
User created successfully: {
|
||||
userId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
email: "your@email.com",
|
||||
session: null
|
||||
}
|
||||
```
|
||||
If you see this → Proceeding to OTP sending ✅
|
||||
|
||||
**Log 3:**
|
||||
```
|
||||
Sending OTP request: {
|
||||
userId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
email: "your@email.com",
|
||||
hasSession: false
|
||||
}
|
||||
```
|
||||
If you see this → OTP function is being called ✅
|
||||
|
||||
**Log 4:**
|
||||
```
|
||||
OTP response status: 200
|
||||
```
|
||||
If you see this → Edge function responded successfully ✅
|
||||
|
||||
**Log 5:**
|
||||
```
|
||||
OTP send result: { success: true, message: "OTP sent successfully" }
|
||||
```
|
||||
If you see this → Everything worked! 🎉
|
||||
|
||||
#### Step 4: Check Network Tab
|
||||
1. In DevTools Network tab
|
||||
2. Look for request to `/functions/v1/send-auth-otp`
|
||||
3. Click on it
|
||||
4. Check:
|
||||
- **Status**: Should be 200
|
||||
- **Payload**: Should contain `{"user_id":"...", "email":"..."}`
|
||||
- **Response**: Should be `{"success":true,"message":"OTP sent successfully"}`
|
||||
|
||||
### 3. Database Verification
|
||||
|
||||
After registration, check the database:
|
||||
|
||||
```sql
|
||||
-- Check if OTP was created
|
||||
SELECT * FROM auth_otps ORDER BY created_at DESC LIMIT 1;
|
||||
|
||||
-- Check if user was created
|
||||
SELECT id, email, email_confirmed_at, created_at
|
||||
FROM auth.users
|
||||
WHERE email = 'your@email.com';
|
||||
```
|
||||
|
||||
Expected:
|
||||
- `auth_otps` table has 1 new row with your email
|
||||
- `auth.users` table has your user with `email_confirmed_at = NULL`
|
||||
|
||||
### 4. Email Verification
|
||||
|
||||
1. Check your email inbox (and spam folder)
|
||||
2. Look for email with subject like "Kode Verifikasi Email Anda"
|
||||
3. Open email and find 6-digit code (e.g., "123456")
|
||||
4. Go back to browser
|
||||
5. Enter the 6-digit code in OTP form
|
||||
6. Click "Verifikasi" button
|
||||
|
||||
Expected:
|
||||
- Toast notification: "Verifikasi Berhasil - Email Anda telah terverifikasi"
|
||||
- Form switches back to login mode
|
||||
|
||||
## Debugging Common Issues
|
||||
|
||||
### Issue 1: No Console Logs Appear
|
||||
|
||||
**Symptoms**: Submit form but nothing shows in console
|
||||
|
||||
**Possible Causes**:
|
||||
1. Dev server not running → Run `npm run dev`
|
||||
2. Code not reloaded → Refresh browser (Cmd+R / F5)
|
||||
3. JavaScript error → Check Console for red error messages
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Stop dev server (Cmd+C)
|
||||
# Then restart
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Issue 2: Console Shows "SignUp result: hasUser: false"
|
||||
|
||||
**Symptoms**: User creation fails
|
||||
|
||||
**Possible Causes**:
|
||||
1. Email already registered
|
||||
2. Supabase Auth configuration issue
|
||||
3. Network error
|
||||
|
||||
**Solution**:
|
||||
```sql
|
||||
-- Check if user exists
|
||||
SELECT id, email FROM auth.users WHERE email = 'your@email.com';
|
||||
|
||||
-- If exists, delete and try again
|
||||
DELETE FROM auth.users WHERE email = 'your@email.com';
|
||||
```
|
||||
|
||||
### Issue 3: Log 3 Appears But No Log 4
|
||||
|
||||
**Symptoms**: OTP request sent but no response
|
||||
|
||||
**Possible Causes**:
|
||||
1. CORS error
|
||||
2. Edge function not deployed
|
||||
3. Wrong Supabase URL in env variables
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check env variables
|
||||
cat .env
|
||||
# Should have VITE_SUPABASE_URL=https://lovable.backoffice.biz.id
|
||||
```
|
||||
|
||||
Check Console for CORS errors (red text like "Access-Control-Allow-Origin")
|
||||
|
||||
### Issue 4: Log 4 Shows Status != 200
|
||||
|
||||
**Symptoms**: Edge function returns error
|
||||
|
||||
**Solution**: Check the error message in Log 4 or Console
|
||||
|
||||
Common errors:
|
||||
- `401 Unauthorized`: Check authorization token
|
||||
- `404 Not Found`: Edge function not deployed
|
||||
- `500 Server Error`: Check edge function logs
|
||||
|
||||
```bash
|
||||
# Check edge function logs
|
||||
supabase functions logs send-auth-otp --tail
|
||||
```
|
||||
|
||||
### Issue 5: OTP Created But No Email Received
|
||||
|
||||
**Symptoms**:
|
||||
- `auth_otps` table has new row
|
||||
- Network request shows 200 OK
|
||||
- But no email in inbox
|
||||
|
||||
**Possible Causes**:
|
||||
1. Mailketing API issue
|
||||
2. Wrong API token
|
||||
3. Email in spam folder
|
||||
4. Template not active
|
||||
|
||||
**Solution**:
|
||||
```sql
|
||||
-- Check notification_logs table
|
||||
SELECT * FROM notification_logs
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- Check if template is active
|
||||
SELECT * FROM notification_templates
|
||||
WHERE key = 'auth_email_verification';
|
||||
```
|
||||
|
||||
If `status = 'failed'`, check `error_message` column.
|
||||
|
||||
### Issue 6: Email Received But Wrong Code
|
||||
|
||||
**Symptoms**: Enter code from email but verification fails
|
||||
|
||||
**Solution**:
|
||||
```sql
|
||||
-- Check OTP in database
|
||||
SELECT otp_code, expires_at, used_at
|
||||
FROM auth_otps
|
||||
WHERE email = 'your@email.com'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
Compare `otp_code` in database with code in email. They should match.
|
||||
|
||||
## Environment Variables Checklist
|
||||
|
||||
Make sure these are set in `.env`:
|
||||
|
||||
```bash
|
||||
VITE_SUPABASE_URL=https://lovable.backoffice.biz.id
|
||||
VITE_SUPABASE_ANON_KEY=your_anon_key_here
|
||||
```
|
||||
|
||||
Check:
|
||||
```bash
|
||||
# View env vars (without showing secrets)
|
||||
grep VITE_SUPABASE .env
|
||||
```
|
||||
|
||||
## Edge Functions Deployment Status
|
||||
|
||||
Check if functions are deployed:
|
||||
|
||||
```bash
|
||||
supabase functions list
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
send-auth-otp ...
|
||||
verify-auth-otp ...
|
||||
send-email-v2 ...
|
||||
```
|
||||
|
||||
## Quick Test Script
|
||||
|
||||
Save this as `test-otp.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
echo "📧 OTP Email Verification Test Script"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
echo "1. Checking environment variables..."
|
||||
if [ -z "$VITE_SUPABASE_URL" ]; then
|
||||
echo "❌ VITE_SUPABASE_URL not set"
|
||||
else
|
||||
echo "✅ VITE_SUPABASE_URL=$VITE_SUPABASE_URL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "2. Checking database connection..."
|
||||
# Add your DB check here
|
||||
|
||||
echo ""
|
||||
echo "3. Checking edge functions..."
|
||||
supabase functions list
|
||||
|
||||
echo ""
|
||||
echo "4. Next steps:"
|
||||
echo " - Open browser to http://localhost:5173/auth"
|
||||
echo " - Open DevTools (F12) → Console tab"
|
||||
echo " - Try to register"
|
||||
echo " - Check console logs"
|
||||
echo " - Check email inbox"
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ All 5 console logs appear in order
|
||||
✅ Network request shows 200 OK
|
||||
✅ `auth_otps` table has new row
|
||||
✅ Email received with 6-digit code
|
||||
✅ OTP code verifies successfully
|
||||
✅ User email confirmed in `auth.users`
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you're still stuck, please provide:
|
||||
1. Screenshot of Console tab (all logs)
|
||||
2. Screenshot of Network tab (send-auth-otp request)
|
||||
3. Output of: `SELECT * FROM auth_otps ORDER BY created_at DESC LIMIT 1;`
|
||||
4. Output of: `SELECT * FROM notification_logs ORDER BY created_at DESC LIMIT 1;`
|
||||
5. Any error messages shown in red in Console
|
||||
@@ -107,20 +107,35 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const sendAuthOTP = async (userId: string, email: string) => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
const token = session?.access_token || import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||
|
||||
console.log('Sending OTP request', { userId, email, hasSession: !!session });
|
||||
|
||||
const response = await fetch(
|
||||
`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/send-auth-otp`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${session?.access_token || import.meta.env.VITE_SUPABASE_ANON_KEY}`,
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ user_id: userId, email }),
|
||||
}
|
||||
);
|
||||
|
||||
console.log('OTP response status:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('OTP request failed:', response.status, errorText);
|
||||
return {
|
||||
success: false,
|
||||
message: `HTTP ${response.status}: ${errorText}`
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('OTP result:', result);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
console.error('Error sending OTP:', error);
|
||||
|
||||
@@ -7,18 +7,22 @@ import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { z } from 'zod';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { ArrowLeft, Mail } from 'lucide-react';
|
||||
|
||||
const emailSchema = z.string().email('Invalid email address');
|
||||
const passwordSchema = z.string().min(6, 'Password must be at least 6 characters');
|
||||
|
||||
export default function Auth() {
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
const [showOTP, setShowOTP] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [otpCode, setOtpCode] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { signIn, signUp, user } = useAuth();
|
||||
const [pendingUserId, setPendingUserId] = useState<string | null>(null);
|
||||
const [resendCountdown, setResendCountdown] = useState(0);
|
||||
const { signIn, signUp, user, sendAuthOTP, verifyAuthOTP } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -27,9 +31,17 @@ export default function Auth() {
|
||||
}
|
||||
}, [user, navigate]);
|
||||
|
||||
// Countdown timer for resend OTP
|
||||
useEffect(() => {
|
||||
if (resendCountdown > 0) {
|
||||
const timer = setTimeout(() => setResendCountdown(resendCountdown - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [resendCountdown]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
try {
|
||||
emailSchema.parse(email);
|
||||
passwordSchema.parse(password);
|
||||
@@ -55,16 +67,98 @@ export default function Auth() {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const { error } = await signUp(email, password, name);
|
||||
|
||||
const { error, data } = await signUp(email, password, name);
|
||||
|
||||
console.log('SignUp result:', { error, data, hasUser: !!data?.user, hasSession: !!data?.session });
|
||||
|
||||
if (error) {
|
||||
if (error.message.includes('already registered')) {
|
||||
toast({ title: 'Error', description: 'This email is already registered. Please login instead.', variant: 'destructive' });
|
||||
} else {
|
||||
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||
}
|
||||
} else {
|
||||
toast({ title: 'Success', description: 'Check your email to confirm your account' });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data?.user) {
|
||||
toast({ title: 'Error', description: 'Failed to create user account. Please try again.', variant: 'destructive' });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// User created, now send OTP
|
||||
const userId = data.user.id;
|
||||
console.log('User created successfully:', { userId, email, session: data.session });
|
||||
|
||||
const result = await sendAuthOTP(userId, email);
|
||||
|
||||
console.log('OTP send result:', result);
|
||||
|
||||
if (result.success) {
|
||||
setPendingUserId(userId);
|
||||
setShowOTP(true);
|
||||
setResendCountdown(60); // 60 seconds cooldown
|
||||
toast({
|
||||
title: 'OTP Terkirim',
|
||||
description: 'Kode verifikasi telah dikirim ke email Anda. Silakan cek inbox.',
|
||||
});
|
||||
} else {
|
||||
toast({ title: 'Error', description: result.message, variant: 'destructive' });
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOTPSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!pendingUserId) {
|
||||
toast({ title: 'Error', description: 'Session expired. Please try again.', variant: 'destructive' });
|
||||
setShowOTP(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (otpCode.length !== 6) {
|
||||
toast({ title: 'Error', description: 'OTP harus 6 digit', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const result = await verifyAuthOTP(pendingUserId, otpCode);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Verifikasi Berhasil',
|
||||
description: 'Email Anda telah terverifikasi. Silakan login.',
|
||||
});
|
||||
setShowOTP(false);
|
||||
setIsLogin(true);
|
||||
// Reset form
|
||||
setName('');
|
||||
setOtpCode('');
|
||||
setPendingUserId(null);
|
||||
} else {
|
||||
toast({ title: 'Error', description: result.message, variant: 'destructive' });
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleResendOTP = async () => {
|
||||
if (resendCountdown > 0 || !pendingUserId) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const result = await sendAuthOTP(pendingUserId, email);
|
||||
|
||||
if (result.success) {
|
||||
setResendCountdown(60);
|
||||
toast({ title: 'OTP Terkirim Ulang', description: 'Kode verifikasi baru telah dikirim ke email Anda.' });
|
||||
} else {
|
||||
toast({ title: 'Error', description: result.message, variant: 'destructive' });
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@@ -81,66 +175,141 @@ export default function Auth() {
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Card className="border-2 border-border shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{isLogin ? 'Login' : 'Sign Up'}</CardTitle>
|
||||
<CardDescription>
|
||||
{isLogin ? 'Enter your credentials to access your account' : 'Create a new account to get started'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{!isLogin && (
|
||||
{!showOTP ? (
|
||||
<Card className="border-2 border-border shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{isLogin ? 'Login' : 'Daftar'}</CardTitle>
|
||||
<CardDescription>
|
||||
{isLogin ? 'Masuk untuk mengakses akun Anda' : 'Buat akun baru untuk memulai'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{!isLogin && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Nama</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Nama lengkap"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Your name"
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="email@anda.com"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full shadow-sm" disabled={loading}>
|
||||
{loading ? 'Loading...' : isLogin ? 'Login' : 'Sign Up'}
|
||||
</Button>
|
||||
</form>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="border-2"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full shadow-sm" disabled={loading}>
|
||||
{loading ? 'Memuat...' : isLogin ? 'Masuk' : 'Daftar'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsLogin(!isLogin)}
|
||||
className="text-sm text-muted-foreground hover:underline"
|
||||
>
|
||||
{isLogin ? "Don't have an account? Sign up" : 'Already have an account? Login'}
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsLogin(!isLogin)}
|
||||
className="text-sm text-muted-foreground hover:underline"
|
||||
>
|
||||
{isLogin ? 'Belum punya akun? Daftar' : 'Sudah punya akun? Masuk'}
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="border-2 border-border shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl flex items-center gap-2">
|
||||
<Mail className="w-6 h-6" />
|
||||
Verifikasi Email
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Masukkan kode 6 digit yang telah dikirim ke <strong>{email}</strong>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleOTPSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="otp">Kode OTP</Label>
|
||||
<Input
|
||||
id="otp"
|
||||
type="text"
|
||||
value={otpCode}
|
||||
onChange={(e) => {
|
||||
// Only allow numbers, max 6 digits
|
||||
const value = e.target.value.replace(/\D/g, '').slice(0, 6);
|
||||
setOtpCode(value);
|
||||
}}
|
||||
placeholder="123456"
|
||||
className="border-2 text-center text-2xl tracking-widest font-mono"
|
||||
maxLength={6}
|
||||
autoFocus
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Masukkan 6 digit kode dari email Anda
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full shadow-sm" disabled={loading || otpCode.length !== 6}>
|
||||
{loading ? 'Memverifikasi...' : 'Verifikasi'}
|
||||
</Button>
|
||||
|
||||
<div className="text-center space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Tidak menerima kode?
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
onClick={handleResendOTP}
|
||||
disabled={resendCountdown > 0 || loading}
|
||||
className="text-sm"
|
||||
>
|
||||
{resendCountdown > 0
|
||||
? `Kirim ulang dalam ${resendCountdown} detik`
|
||||
: 'Kirim ulang kode'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setShowOTP(false);
|
||||
setOtpCode('');
|
||||
setPendingUserId(null);
|
||||
setResendCountdown(0);
|
||||
}}
|
||||
className="w-full text-sm"
|
||||
>
|
||||
Kembali ke form pendaftaran
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,12 @@ serve(async (req: Request) => {
|
||||
// Initialize Supabase client with service role
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey, {
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
});
|
||||
|
||||
// Generate OTP code
|
||||
const otpCode = generateOTP();
|
||||
@@ -79,12 +84,16 @@ serve(async (req: Request) => {
|
||||
}
|
||||
|
||||
// Get email template
|
||||
console.log('Fetching email template with key: auth_email_verification');
|
||||
|
||||
const { data: template, error: templateError } = await supabase
|
||||
.from('notification_templates')
|
||||
.select('*')
|
||||
.eq('key', 'auth_email_verification')
|
||||
.single();
|
||||
|
||||
console.log('Template query result:', { template, templateError });
|
||||
|
||||
if (templateError || !template) {
|
||||
console.error('Error fetching email template:', templateError);
|
||||
throw new Error('Email template not found. Please create template with key: auth_email_verification');
|
||||
@@ -123,6 +132,30 @@ serve(async (req: Request) => {
|
||||
|
||||
// Send email via send-email-v2
|
||||
console.log(`Sending OTP email to ${email}`);
|
||||
console.log('Settings:', {
|
||||
hasMailketingToken: !!settings.mailketing_api_token,
|
||||
hasApiToken: !!settings.api_token,
|
||||
hasFromName: !!settings.from_name,
|
||||
hasFromEmail: !!settings.from_email,
|
||||
platformName: settings.platform_name,
|
||||
});
|
||||
|
||||
// Use api_token (not mailketing_api_token)
|
||||
const apiToken = settings.api_token || settings.mailketing_api_token;
|
||||
|
||||
if (!apiToken) {
|
||||
throw new Error('API token not found in notification_settings');
|
||||
}
|
||||
|
||||
// Log email details (truncate HTML body for readability)
|
||||
console.log('Email payload:', {
|
||||
to: email,
|
||||
from_name: settings.from_name || settings.platform_name || 'Admin',
|
||||
from_email: settings.from_email || 'noreply@example.com',
|
||||
subject: subject,
|
||||
html_body_length: htmlBody.length,
|
||||
html_body_preview: htmlBody.substring(0, 200),
|
||||
});
|
||||
|
||||
const emailResponse = await fetch(`${supabaseUrl}/functions/v1/send-email-v2`, {
|
||||
method: 'POST',
|
||||
@@ -132,9 +165,9 @@ serve(async (req: Request) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
to: email,
|
||||
api_token: settings.mailketing_api_token,
|
||||
from_name: settings.from_name,
|
||||
from_email: settings.from_email,
|
||||
api_token: apiToken,
|
||||
from_name: settings.from_name || settings.platform_name || 'Admin',
|
||||
from_email: settings.from_email || 'noreply@example.com',
|
||||
subject: subject,
|
||||
html_body: htmlBody,
|
||||
}),
|
||||
@@ -149,17 +182,7 @@ serve(async (req: Request) => {
|
||||
const emailResult = await emailResponse.json();
|
||||
console.log('Email sent successfully:', emailResult);
|
||||
|
||||
// Log notification
|
||||
await supabase
|
||||
.from('notification_logs')
|
||||
.insert({
|
||||
user_id,
|
||||
email: email,
|
||||
notification_type: 'auth_email_verification',
|
||||
status: 'sent',
|
||||
provider: 'mailketing',
|
||||
error_message: null,
|
||||
});
|
||||
// Note: notification_logs table doesn't exist, skipping logging
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
@@ -172,25 +195,7 @@ serve(async (req: Request) => {
|
||||
} catch (error: any) {
|
||||
console.error("Error sending OTP:", error);
|
||||
|
||||
// Try to log error notification
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
await supabase
|
||||
.from('notification_logs')
|
||||
.insert({
|
||||
user_id: null,
|
||||
email: null,
|
||||
notification_type: 'auth_email_verification',
|
||||
status: 'failed',
|
||||
provider: 'mailketing',
|
||||
error_message: error.message || 'Unknown error',
|
||||
});
|
||||
} catch (logError) {
|
||||
console.error('Failed to log error:', logError);
|
||||
}
|
||||
// Note: notification_logs table doesn't exist, skipping error logging
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
|
||||
26
supabase/migrations/20250102000003_fix_auth_otps_fk.sql
Normal file
26
supabase/migrations/20250102000003_fix_auth_otps_fk.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- ============================================================================
|
||||
-- Fix auth_otps foreign key constraint
|
||||
-- ============================================================================
|
||||
|
||||
-- Drop the foreign key constraint since unconfirmed users might not be fully accessible
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'auth_otps_user_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE auth_otps DROP CONSTRAINT auth_otps_user_id_fkey;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Add a check constraint to ensure user_id is a valid UUID
|
||||
ALTER TABLE auth_otps ADD CONSTRAINT auth_otps_user_id_valid
|
||||
CHECK (user_id::text ~ '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'::text);
|
||||
|
||||
COMMENT ON TABLE auth_otps IS 'Stores OTP codes for email verification. No FK constraint to handle unconfirmed users.';
|
||||
|
||||
-- Return success message
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Foreign key constraint removed from auth_otps table';
|
||||
END $$;
|
||||
145
supabase/migrations/20250102000004_fix_auth_email_template.sql
Normal file
145
supabase/migrations/20250102000004_fix_auth_email_template.sql
Normal file
@@ -0,0 +1,145 @@
|
||||
-- ============================================================================
|
||||
-- Fix Auth Email Verification Template - Remove YAML delimiters
|
||||
-- ============================================================================
|
||||
|
||||
-- Update the auth_email_verification template with proper HTML
|
||||
UPDATE notification_templates
|
||||
SET
|
||||
email_subject = 'Kode Verifikasi Email Anda - {platform_name}',
|
||||
email_body_html = '<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verifikasi Email</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border: 3px solid #000;
|
||||
box-shadow: 8px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.header {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
.greeting {
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 30px;
|
||||
color: #333;
|
||||
}
|
||||
.otp-container {
|
||||
background: #f9f9f9;
|
||||
border: 2px dashed #000;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
.otp-code {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 8px;
|
||||
color: #000;
|
||||
font-family: "Courier New", monospace;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.expiry {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.instructions {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid #f59e0b;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.footer {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
border-top: 2px solid #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔐 Verifikasi Email</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p class="greeting">Halo {nama},</p>
|
||||
|
||||
<p class="message">
|
||||
Terima kasih telah mendaftar di <strong>{platform_name}</strong>!
|
||||
Gunakan kode OTP berikut untuk memverifikasi alamat email Anda:
|
||||
</p>
|
||||
|
||||
<div class="otp-container">
|
||||
<div class="otp-code">{otp_code}</div>
|
||||
<div class="expiry">⏰ Berlaku selama {expiry_minutes} menit</div>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<strong>Cara menggunakan:</strong><br>
|
||||
1. Salin kode 6 digit di atas<br>
|
||||
2. Kembali ke halaman pendaftaran<br>
|
||||
3. Masukkan kode tersebut pada form verifikasi
|
||||
</div>
|
||||
|
||||
<p class="message" style="margin-top: 30px;">
|
||||
Jika Anda tidak merasa mendaftar di {platform_name},
|
||||
abaikan email ini dengan aman.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Email ini dikirim ke {email}</p>
|
||||
<p>© {year} {platform_name}. Semua hak dilindungi.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>'
|
||||
WHERE key = 'auth_email_verification';
|
||||
|
||||
-- Verify the update
|
||||
SELECT
|
||||
key,
|
||||
name,
|
||||
is_active,
|
||||
email_subject,
|
||||
LENGTH(email_body_html) as html_length,
|
||||
SUBSTRING(email_body_html, 1, 100) as html_start,
|
||||
CASE
|
||||
WHEN email_body_html LIKE '%<!DOCTYPE%' THEN '✓ Has DOCTYPE'
|
||||
WHEN email_body_html LIKE '%<html>%' THEN '✓ Has HTML tag'
|
||||
WHEN email_body_html LIKE '%---%' THEN '✗ Still has YAML delimiters'
|
||||
ELSE 'Unknown format'
|
||||
END as template_status
|
||||
FROM notification_templates
|
||||
WHERE key = 'auth_email_verification';
|
||||
157
supabase/migrations/20250102000004_insert_auth_template.sql
Normal file
157
supabase/migrations/20250102000004_insert_auth_template.sql
Normal file
@@ -0,0 +1,157 @@
|
||||
-- ============================================================================
|
||||
-- Insert Auth Email Verification Template (Fixed)
|
||||
-- ============================================================================
|
||||
|
||||
-- First, let's see what columns the table actually has
|
||||
-- This will help us insert correctly
|
||||
|
||||
-- Check if template exists
|
||||
DO $$
|
||||
DECLARE
|
||||
template_count int;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO template_count
|
||||
FROM notification_templates
|
||||
WHERE key = 'auth_email_verification';
|
||||
|
||||
IF template_count = 0 THEN
|
||||
-- Template doesn't exist, insert it
|
||||
INSERT INTO notification_templates (
|
||||
key,
|
||||
name,
|
||||
is_active,
|
||||
email_subject,
|
||||
email_body_html
|
||||
) VALUES (
|
||||
'auth_email_verification',
|
||||
'Verifikasi Email - OTP',
|
||||
true,
|
||||
'Kode Verifikasi Email Anda - {platform_name}',
|
||||
'---
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verifikasi Email</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border: 3px solid #000;
|
||||
box-shadow: 8px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.header {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
.greeting {
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 30px;
|
||||
color: #333;
|
||||
}
|
||||
.otp-container {
|
||||
background: #f9f9f9;
|
||||
border: 2px dashed #000;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
.otp-code {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 8px;
|
||||
color: #000;
|
||||
font-family: "Courier New", monospace;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.expiry {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.instructions {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid #f59e0b;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.footer {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
border-top: 2px solid #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔐 Verifikasi Email</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p class="greeting">Halo {nama},</p>
|
||||
|
||||
<p class="message">
|
||||
Terima kasih telah mendaftar di <strong>{platform_name}</strong>!
|
||||
Gunakan kode OTP berikut untuk memverifikasi alamat email Anda:
|
||||
</p>
|
||||
|
||||
<div class="otp-container">
|
||||
<div class="otp-code">{otp_code}</div>
|
||||
<div class="expiry">⏰ Berlaku selama {expiry_minutes} menit</div>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<strong>Cara menggunakan:</strong><br>
|
||||
1. Salin kode 6 digit di atas<br>
|
||||
2. Kembali ke halaman pendaftaran<br>
|
||||
3. Masukkan kode tersebut pada form verifikasi
|
||||
</div>
|
||||
|
||||
<p class="message" style="margin-top: 30px;">
|
||||
Jika Anda tidak merasa mendaftar di {platform_name},
|
||||
abaikan email ini dengan aman.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Email ini dikirim ke {email}</p>
|
||||
<p>© {year} {platform_name}. Semua hak dilindungi.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
---'
|
||||
);
|
||||
|
||||
RAISE NOTICE 'Auth email verification template inserted successfully';
|
||||
ELSE
|
||||
RAISE NOTICE 'Template already exists, skipping insertion';
|
||||
END IF;
|
||||
END $$;
|
||||
104
test-otp-flow.sh
Executable file
104
test-otp-flow.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test OTP Email Flow
|
||||
# This script tests the complete OTP email verification flow
|
||||
|
||||
echo "🔐 Testing OTP Email Verification Flow"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Configuration
|
||||
SUPABASE_URL="https://lovable.backoffice.biz.id"
|
||||
# You need to get your service role key from Coolify/Docker
|
||||
SERVICE_KEY="YOUR_SERVICE_ROLE_KEY_HERE"
|
||||
|
||||
echo "⚠️ IMPORTANT: Replace YOUR_SERVICE_ROLE_KEY_HERE with your actual service role key"
|
||||
echo " You can find it in Coolify → Supabase service → Environment variables"
|
||||
echo ""
|
||||
|
||||
# Step 1: Register a test user (using curl)
|
||||
echo "Step 1: Creating test user..."
|
||||
echo "Run this in Supabase SQL Editor first:"
|
||||
echo ""
|
||||
echo "-- Clean up any existing test user"
|
||||
echo "DELETE FROM auth_otps WHERE email = 'test@example.com';"
|
||||
echo "DELETE FROM auth.users WHERE email = 'test@example.com';"
|
||||
echo ""
|
||||
|
||||
# Step 2: Test the send-auth-otp edge function
|
||||
echo "Step 2: Testing send-auth-otp edge function..."
|
||||
echo ""
|
||||
echo "Replace USER_ID with the actual UUID from step 1, then run:"
|
||||
echo ""
|
||||
echo "curl -X POST ${SUPABASE_URL}/functions/v1/send-auth-otp \\"
|
||||
echo " -H \"Authorization: Bearer \${SERVICE_KEY}\" \\"
|
||||
echo " -H \"Content-Type: application/json\" \\"
|
||||
echo " -d '{"
|
||||
echo " \"user_id\": \"USER_UUID_HERE\","
|
||||
echo " \"email\": \"test@example.com\""
|
||||
echo " }'"
|
||||
echo ""
|
||||
|
||||
# Step 3: Check if OTP was created
|
||||
echo "Step 3: Check if OTP was created in database..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " id,"
|
||||
echo " user_id,"
|
||||
echo " email,"
|
||||
echo " otp_code,"
|
||||
echo " expires_at,"
|
||||
echo " used_at,"
|
||||
echo " created_at"
|
||||
echo "FROM auth_otps"
|
||||
echo "WHERE email = 'test@example.com'"
|
||||
echo "ORDER BY created_at DESC"
|
||||
echo "LIMIT 1;"
|
||||
echo ""
|
||||
|
||||
# Step 4: Check notification settings
|
||||
echo "Step 4: Check notification settings..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " id,"
|
||||
echo " platform_name,"
|
||||
echo " from_name,"
|
||||
echo " from_email,"
|
||||
echo " api_token,"
|
||||
echo " mailketing_api_token"
|
||||
echo "FROM notification_settings"
|
||||
echo "LIMIT 1;"
|
||||
echo ""
|
||||
|
||||
# Step 5: Check email template
|
||||
echo "Step 5: Check email template..."
|
||||
echo "Run this in Supabase SQL Editor:"
|
||||
echo ""
|
||||
echo "SELECT"
|
||||
echo " key,"
|
||||
echo " name,"
|
||||
echo " is_active,"
|
||||
echo " email_subject,"
|
||||
echo " LENGTH(email_body_html) as html_length"
|
||||
echo "FROM notification_templates"
|
||||
echo "WHERE key = 'auth_email_verification';"
|
||||
echo ""
|
||||
|
||||
echo "========================================"
|
||||
echo "Common Issues:"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "1. If curl returns 404: Edge function not deployed"
|
||||
echo " Solution: Deploy the function from your server"
|
||||
echo ""
|
||||
echo "2. If curl returns 401: Invalid service role key"
|
||||
echo " Solution: Check SERVICE_KEY is correct"
|
||||
echo ""
|
||||
echo "3. If curl returns 500: Check edge function logs"
|
||||
echo " Solution: SSH into server and check Docker logs"
|
||||
echo ""
|
||||
echo "4. If OTP created but no email: Mailketing API issue"
|
||||
echo " Solution: Check api_token in notification_settings"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user