auth-setup

📁 chloezhu010/vibe-ship 📅 4 days ago
2
总安装量
2
周安装量
#74832
全站排名
安装命令
npx skills add https://github.com/chloezhu010/vibe-ship --skill auth-setup

Agent 安装分布

amp 2
github-copilot 2
codex 2
kimi-cli 2
gemini-cli 2
cursor 2

Skill 文档

Auth Setup

Add Supabase Auth (magic link) and configure Resend for production email delivery.

This skill is called by the vibe-ship orchestrator. FRAMEWORK = nextjs or vite.

lib/supabase.ts (Next.js) or src/lib/supabase.ts (Vite) and all Supabase env vars were created in the previous supabase-setup step.

1. Review Supabase Auth approach

This skill uses Supabase Auth (built into @supabase/supabase-js) — not Better Auth or any third-party auth library. Key patterns:

  • Magic link OTP via supabase.auth.signInWithOtp()
  • Session management via supabase.auth.onAuthStateChange()
  • Always pass emailRedirectTo: window.location.origin so links work in both local dev and production

2. Enable Supabase Auth providers

Email is enabled by default on new Supabase projects. No dashboard action needed.

3. Add auth to the codebase

If FRAMEWORK = nextjs:

Install: npm install @supabase/auth-helpers-nextjs

Create middleware.ts at the project root:

import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  await supabase.auth.getSession()
  return res
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}

Create app/login/page.tsx if it doesn’t exist:

'use client'
import { useState } from 'react'
import { supabase } from '@/lib/supabase'

export default function LoginPage() {
  const [email, setEmail] = useState('')
  const [sent, setSent] = useState(false)

  async function handleLogin(e: React.FormEvent) {
    e.preventDefault()
    await supabase.auth.signInWithOtp({
      email,
      options: { emailRedirectTo: window.location.origin },
    })
    setSent(true)
  }

  if (sent) return <p>Check your email for a magic link.</p>

  return (
    <form onSubmit={handleLogin}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} required />
      <button type="submit">Send magic link</button>
    </form>
  )
}

If FRAMEWORK = vite:

Create src/contexts/AuthContext.tsx:

import { createContext, useContext, useEffect, useState } from 'react'
import { Session } from '@supabase/supabase-js'
import { supabase } from '@/lib/supabase'

const AuthContext = createContext<{ session: Session | null }>({ session: null })

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [session, setSession] = useState<Session | null>(null)

  useEffect(() => {
    supabase.auth.getSession().then(({ data }) => setSession(data.session))
    const { data: { subscription } } = supabase.auth.onAuthStateChange((_e, s) => setSession(s))
    return () => subscription.unsubscribe()
  }, [])

  return <AuthContext.Provider value={{ session }}>{children}</AuthContext.Provider>
}

export const useAuth = () => useContext(AuthContext)

Wrap <App /> in src/main.tsx with <AuthProvider>.

Update src/main.tsx to wrap your app:

// Before
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(<App />)

// After
import App from './App'
import { AuthProvider } from './contexts/AuthContext'
ReactDOM.createRoot(document.getElementById('root')!).render(
  <AuthProvider><App /></AuthProvider>
)

Create src/pages/Login.tsx:

import { useState } from 'react'
import { supabase } from '@/lib/supabase'

export default function Login() {
  const [email, setEmail] = useState('')
  const [sent, setSent] = useState(false)

  async function handleLogin(e: React.FormEvent) {
    e.preventDefault()
    await supabase.auth.signInWithOtp({
      email,
      options: { emailRedirectTo: window.location.origin },
    })
    setSent(true)
  }

  if (sent) return <p>Check your email for a magic link.</p>

  return (
    <form onSubmit={handleLogin}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} required />
      <button type="submit">Send magic link</button>
    </form>
  )
}

Wire the auth gate into the root component. Scan for App.tsx or App.jsx and add at the top of the component body:

import { useAuth } from './contexts/AuthContext'
import Login from './pages/Login'

export default function App() {
  const { session } = useAuth()

  if (!session) return <Login />

  // existing app content...
}

Also add a sign-out button. Scan for a header, nav, or avatar area and add:

<button onClick={() => supabase.auth.signOut()}>Sign out</button>

If no clear header exists, add it at the top of the main app content area.

4. Add row-level security policies

For each table created in supabase-setup, write RLS policies to supabase/migrations/002_rls_policies.sql. Replace items with each actual table name:

-- Users can read their own rows
create policy "Users can read own items"
  on items for select
  using (auth.uid() = user_id);

-- Users can insert their own rows
create policy "Users can insert own items"
  on items for insert
  with check (auth.uid() = user_id);

-- Users can update their own rows
create policy "Users can update own items"
  on items for update
  using (auth.uid() = user_id);

-- Users can delete their own rows
create policy "Users can delete own items"
  on items for delete
  using (auth.uid() = user_id);

Repeat for every table that has a user_id column, then push:

supabase db push

No dashboard visit needed.

5. Configure Resend for production email

Apply resend/email-best-practices.

Ask: “Please create a free account at resend.com, verify your domain (Resend will walk you through adding a DNS record — takes ~5 minutes), then add your API key to .env.local:

RESEND_API_KEY=<your key here>

Let me know when it’s saved.”

Wait for the user to confirm before continuing.

Add to supabase/config.toml — these are two separate top-level sections, not nested:

[auth.email.smtp]
enabled = true
host = "smtp.resend.com"
port = 465
user = "resend"
pass = "env(RESEND_API_KEY)"
admin_email = "noreply@resend.dev"
sender_name = "<app name>"

Find the existing [auth.rate_limit] section in config.toml and update email_sent there (do not add a duplicate section):

[auth.rate_limit]
email_sent = 100

Ask the user to push to the hosted project by running this in their terminal:

supabase config push

No dashboard visit needed.

6. Validate

Ask user to:

  1. Run npm run dev
  2. Navigate to the login page
  3. Enter their own email and wait for the magic link email (check spam if not in inbox)

Only report success after the user confirms the email arrived:

Report: “✓ Auth setup complete. Magic link email delivered via Resend.”