Skip to content

Commit 1246eff

Browse files
committed
fix(auth): resolve signup duplicate key & session conflicts
- fix(auth): use admin client to bypass RLS for profile upsert (fixes 42501/23505) - fix(auth): sign out existing session before callback exchange to prevent wrong-profile redirect - fix(ui): restore brand orange primary color in dark mode - fix(types): add missing frequency types in ScheduledPayrollClient - chore(deps): remove unused @stacks/stacking, @stacks/storage, @stacks/profile - refactor(stacks): remove dead Blockstack SDK fallbacks (authenticate, showBlockstackConnect) - refactor(auth): remove unused supabase client from login page - chore: update git remote to Quaddlab and bump Next.js README badge to 16 - test: add clear-all-users cleanup script
1 parent 586e5e1 commit 1246eff

10 files changed

Lines changed: 113 additions & 90 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<img src="https://img.shields.io/badge/NETWORK-STACKS_TESTNET-5546FF?style=for-the-badge&logo=stacks&logoColor=white" />
1717
<img src="https://img.shields.io/badge/ASSETS-BTC_%7C_STX-FF6B00?style=for-the-badge&logo=bitcoin&logoColor=white" />
1818
<img src="https://img.shields.io/badge/CONTRACT-CLARITY_2.1-71717A?style=for-the-badge&logo=docsdotrs&logoColor=white" />
19-
<img src="https://img.shields.io/badge/FRONTEND-NEXT.JS_15-000000?style=for-the-badge&logo=nextdotjs&logoColor=white" />
19+
<img src="https://img.shields.io/badge/FRONTEND-NEXT.JS_16-000000?style=for-the-badge&logo=nextdotjs&logoColor=white" />
2020
<img src="https://img.shields.io/badge/DATA-SUPABASE_POSTGRES-3FCF8E?style=for-the-badge&logo=supabase&logoColor=white" />
2121
</p>
2222

package-lock.json

Lines changed: 0 additions & 66 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424
"@stacks/connect-react": "^23.1.4",
2525
"@stacks/encryption": "^7.3.1",
2626
"@stacks/network": "^7.3.1",
27-
"@stacks/profile": "^7.3.1",
28-
"@stacks/stacking": "^7.3.1",
29-
"@stacks/storage": "^7.3.1",
3027
"@stacks/transactions": "^7.3.1",
3128
"@supabase/ssr": "^0.8.0",
3229
"@supabase/supabase-js": "^2.89.0",

scripts/clear-all-users.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* One-off script to clear all users from Supabase (auth + profiles).
3+
* Run with: npx tsx scripts/clear-all-users.ts
4+
*
5+
* ⚠️ This deletes ALL users and profiles. Use only in development.
6+
*/
7+
8+
import { createClient } from '@supabase/supabase-js'
9+
import { readFileSync } from 'fs'
10+
import { resolve } from 'path'
11+
12+
// Parse .env.local manually (no dotenv dependency needed)
13+
function loadEnv(): Record<string, string> {
14+
const envPath = resolve(__dirname, '..', '.env.local')
15+
const content = readFileSync(envPath, 'utf-8')
16+
const env: Record<string, string> = {}
17+
for (const line of content.split('\n')) {
18+
const trimmed = line.trim()
19+
if (!trimmed || trimmed.startsWith('#')) continue
20+
const eqIndex = trimmed.indexOf('=')
21+
if (eqIndex === -1) continue
22+
const key = trimmed.slice(0, eqIndex).trim()
23+
let value = trimmed.slice(eqIndex + 1).trim()
24+
// Strip surrounding quotes
25+
if ((value.startsWith('"') && value.endsWith('"')) ||
26+
(value.startsWith("'") && value.endsWith("'"))) {
27+
value = value.slice(1, -1)
28+
}
29+
env[key] = value
30+
}
31+
return env
32+
}
33+
34+
const env = loadEnv()
35+
const url = env.NEXT_PUBLIC_SUPABASE_URL
36+
const serviceKey = env.SUPABASE_SERVICE_ROLE_KEY
37+
38+
if (!url || !serviceKey) {
39+
console.error('❌ Missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY in .env.local')
40+
process.exit(1)
41+
}
42+
43+
const supabase = createClient(url, serviceKey, {
44+
auth: { autoRefreshToken: false, persistSession: false }
45+
})
46+
47+
async function clearAllUsers() {
48+
console.log('🗑️ Clearing all users from Supabase...\n')
49+
50+
// 1. Delete all profiles first (foreign key dependency)
51+
const { error: profilesError } = await supabase
52+
.from('profiles')
53+
.delete()
54+
.neq('id', '00000000-0000-0000-0000-000000000000') // deletes all rows
55+
56+
if (profilesError) {
57+
console.error('❌ Error deleting profiles:', profilesError.message)
58+
} else {
59+
console.log('✅ All profiles deleted')
60+
}
61+
62+
// 2. List all auth users
63+
const { data: usersData, error: listError } = await supabase.auth.admin.listUsers()
64+
65+
if (listError) {
66+
console.error('❌ Error listing users:', listError.message)
67+
return
68+
}
69+
70+
const users = usersData?.users || []
71+
console.log(`📋 Found ${users.length} auth user(s)\n`)
72+
73+
if (users.length === 0) {
74+
console.log('✅ No users to delete — database is already clean!')
75+
return
76+
}
77+
78+
// 3. Delete each auth user
79+
for (const user of users) {
80+
const { error: deleteError } = await supabase.auth.admin.deleteUser(user.id)
81+
if (deleteError) {
82+
console.error(`❌ Failed to delete ${user.email}: ${deleteError.message}`)
83+
} else {
84+
console.log(`✅ Deleted: ${user.email} (${user.id})`)
85+
}
86+
}
87+
88+
console.log('\n🎉 Done! All users and profiles have been cleared.')
89+
console.log(' You can now sign up with a fresh account.')
90+
}
91+
92+
clearAllUsers().catch(console.error)

src/app/(marketing)/login/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
88
import { Input } from "@/components/ui/input"
99
import { Label } from "@/components/ui/label"
1010

11-
import { createClient } from "@/lib/supabase"
1211
import { login } from "@/app/actions/auth"
1312
import { useRouter } from "next/navigation"
1413
import { Loader2 } from "lucide-react"
@@ -21,7 +20,6 @@ export default function LoginPage() {
2120
const [isLoading, setIsLoading] = React.useState(false)
2221
const { showNotification } = useNotification()
2322
const router = useRouter()
24-
const supabase = createClient()
2523
return (
2624
<div className="min-h-[calc(100vh-64px-137px)] flex items-center justify-center p-4">
2725
<div className="w-full max-w-md space-y-8 animate-in fade-in zoom-in duration-500">

src/app/actions/auth.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ export async function signUp(formData: {
3333
}
3434

3535
if (user) {
36-
// Insert into profiles table
37-
const { error: profileError } = await supabase
36+
// Use the admin client (service role key) to bypass RLS for profile creation.
37+
// The regular anon client can't write to the profiles table due to RLS policies.
38+
const adminClient = await createAdminClient()
39+
const { error: profileError } = await adminClient
3840
.from('profiles')
39-
.insert([
41+
.upsert(
4042
{
4143
id: user.id,
4244
role: formData.role,
@@ -46,13 +48,13 @@ export async function signUp(formData: {
4648
default_currency: formData.default_currency,
4749
organization_type: formData.organization_type,
4850
updated_at: new Date().toISOString(),
49-
}
50-
])
51+
},
52+
{ onConflict: 'id' }
53+
)
5154

5255
if (profileError) {
5356
console.error('Error creating profile:', profileError)
54-
// We don't necessarily want to fail the whole signup if profile creation fails,
55-
// but we should log it. In a production app, we might want more robust handling.
57+
return { error: `Account created but profile setup failed: ${profileError.message}. Please try logging in or contact support.` }
5658
}
5759
}
5860

src/app/auth/callback/route.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export async function GET(request: Request) {
2727
},
2828
}
2929
)
30+
// Sign out any existing session before exchanging the code.
31+
// This prevents the confirmation link from landing on a stale/wrong profile
32+
// when the user is already logged into a different account.
33+
await supabase.auth.signOut()
34+
3035
const { error } = await supabase.auth.exchangeCodeForSession(code)
3136
if (!error) {
3237
return NextResponse.redirect(`${origin}${next}`)

src/app/dashboard/payroll/scheduled/ScheduledPayrollClient.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ interface ScheduleItem {
5656
interface PayrollSchedule {
5757
id: string;
5858
name: string;
59-
frequency: "weekly" | "monthly";
59+
frequency: "minutes" | "hourly" | "daily" | "weekly" | "monthly";
6060
pay_day: number;
6161
status: "draft" | "ready" | "processing" | "paid";
6262
next_run_at: string | null;
@@ -398,12 +398,9 @@ export default function ScheduledPayrollPage({
398398
// First mark as ready
399399
await handleMarkReady(schedule.id);
400400
}
401-
// Then run payroll - note: this triggers wallet popup but doesn't wait for approval
402-
// Success notifications are handled in the onFinish callback of executeBatchPayroll
401+
403402
await handleRunPayroll(schedule);
404403
}
405-
// Don't show success here - the individual onFinish callbacks handle success notifications
406-
// after the user actually approves each transaction on-chain
407404
} catch (err: any) {
408405
showNotification(
409406
"error",

src/app/globals.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@
7373
--card-foreground: oklch(0.985 0 0);
7474
--popover: oklch(0.205 0 0);
7575
--popover-foreground: oklch(0.985 0 0);
76-
--primary: oklch(0.922 0 0);
77-
--primary-foreground: oklch(0.205 0 0);
76+
--primary: #ff6b00;
77+
--primary-foreground: #ffffff;
7878
--secondary: oklch(0.269 0 0);
7979
--secondary-foreground: oklch(0.985 0 0);
8080
--muted: oklch(0.269 0 0);

src/hooks/useStacks.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,8 @@ export function useStacks() {
5454
};
5555

5656
try {
57-
// Try multiple export names to handle different package versions/bundlers
58-
const connectFn = StacksConnect.showConnect ||
59-
(StacksConnect as any).authenticate ||
60-
(StacksConnect as any).showBlockstackConnect;
57+
// @stacks/connect v8 exports showConnect directly
58+
const connectFn = StacksConnect.showConnect;
6159

6260
if (typeof connectFn === 'function') {
6361
connectFn(authOptions);

0 commit comments

Comments
 (0)