nuxt-supabase-team-auth
Version:
Drop-in Nuxt 3 module for team-based authentication with Supabase
1,305 lines (1,005 loc) • 38.4 kB
Markdown
# Nuxt Supabase Team Auth
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
> **📚 Documentation Notice**
> This README is for **developers using this module in their applications**. If you're looking to contribute to the module itself, please see:
> - [MODULE_DEVELOPMENT.md](./MODULE_DEVELOPMENT.md) - Guide for module contributors
> - [CONTRIBUTING.md](./docs/CONTRIBUTING.md) - Contribution guidelines and commit conventions
Drop-in Nuxt 3 module for team-based authentication with Supabase.
- ✨ **Team-based authentication** - Built-in support for multi-user teams
- 🔐 **Role-based access control** - Owner, admin, member roles with
fine-grained permissions
- 👥 **User impersonation** - Super admin impersonation with audit logging
- 📧 **Native invitations** - Supabase-native invitation system
- 🚀 **Zero configuration** - Works out of the box with sensible defaults
- 🎨 **Nuxt UI components** - Beautiful pre-built auth components
- 📱 **SSR ready** - Full server-side rendering support
- 🔒 **Security first** - Built-in RLS policies and secure session management
## Requirements
This module requires the following peer dependencies:
- **Nuxt 3** (`^3.0.0`)
- **@nuxt/ui** (`^3.1.0`) - UI component framework
- **@nuxt/icon** (`^1.0.0`) - Icon framework (required by Nuxt UI)
The module automatically installs and configures **@nuxtjs/supabase** (`^1.5.0`) if not already present.
## Quick Setup
### Prerequisites
This module requires a **working Nuxt UI application**. If you don't have one,
create it first:
```bash
# Create a new Nuxt UI app (recommended)
pnpm create nuxt@latest my-app -t ui
# Or add Nuxt UI to existing Nuxt app
pnpm add @nuxt/ui
```
### 1. Install the Module
```bash
# Add our module to your existing Nuxt UI app
pnpm add nuxt-supabase-team-auth
```
#### Required: Cookie Package Override
Due to a [known dependency conflict](https://github.com/supabase/ssr/issues/62) between Nuxt and Supabase, you need to add a package override to your `package.json`:
```json
{
"pnpm": {
"overrides": {
"cookie": "0.7.2"
}
}
}
```
**Why is this needed?** Nuxt/Nitro uses `cookie@1.x` while `@supabase/ssr` expects `cookie@0.7.x`. This override ensures compatibility until Supabase resolves the upstream dependency conflict.
### 2. Configure Nuxt
Add our module to your `nuxt.config.ts`. The module automatically registers and configures `@nuxtjs/supabase`:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/ui', // Should already be here
'nuxt-supabase-team-auth' // Add our module (auto-configures @nuxtjs/supabase)
],
// Configure team-auth module
teamAuth: {
redirectTo: '/dashboard', // Where to go after login
loginPage: '/signin', // Your sign-in page route
defaultProtection: 'public', // Most routes are public by default
protectedRoutes: ['/dashboard'], // Only these routes require auth
publicRoutes: ['/about'], // Additional public routes
socialProviders: {
google: { enabled: true } // Configure social providers
},
debug: true // Enable debug logging (optional)
}
})
```
#### Configuration Options
**Team Auth Module Configuration (`teamAuth` key):**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `redirectTo` | `string` | `'/dashboard'` | Where to redirect after successful auth |
| `loginPage` | `string` | `'/signin'` | Your sign-in page route |
| `defaultProtection` | `'public'\|'protected'` | `'public'` | Default route protection mode |
| `protectedRoutes` | `string[]` | `['/dashboard']` | Routes that require authentication (used with `defaultProtection: 'public'`) |
| `publicRoutes` | `string[]` | `[]` | Routes that don't require authentication (used with `defaultProtection: 'protected'`) |
| `socialProviders.google.enabled` | `boolean` | `true` | Enable Google OAuth |
| `passwordPolicy` | `object` | See below | Customize password requirements |
| `debug` | `boolean` | Auto-detected | Enable debug logging |
#### Route Protection Modes
**Public by Default (Recommended)**
```typescript
teamAuth: {
defaultProtection: 'public', // Most routes are public
protectedRoutes: ['/dashboard'], // Only specific routes need auth
}
```
**Protected by Default**
```typescript
teamAuth: {
defaultProtection: 'protected', // All routes require auth
publicRoutes: ['/', '/about'], // Except these specific routes
}
```
**Important Notes:**
- The module automatically installs and configures `@nuxtjs/supabase` using the `installModule()` pattern
- Route protection is configured in `teamAuth.defaultProtection` - no need to configure `@nuxtjs/supabase` separately
- Environment variables are automatically detected from your `.env` file
#### Password Policy Configuration
Customize password requirements to match your security needs:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
teamAuth: {
passwordPolicy: {
minLength: 8, // Minimum password length (default: 8)
requireUppercase: false, // Require uppercase letters (default: false)
requireLowercase: false, // Require lowercase letters (default: false)
requireNumbers: false, // Require numbers (default: false)
requireSpecialChars: false,// Require special characters (default: false)
helpText: 'Custom help' // Override default help text
}
}
})
```
**Password Policy Options:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `minLength` | `number` | `8` | Minimum password length |
| `requireUppercase` | `boolean` | `false` | Require at least one uppercase letter |
| `requireLowercase` | `boolean` | `false` | Require at least one lowercase letter |
| `requireNumbers` | `boolean` | `false` | Require at least one number |
| `requireSpecialChars` | `boolean` | `false` | Require at least one special character |
| `specialChars` | `string` | `!@#$%^&*()_+-=[]{}|;:,.<>?` | Define allowed special characters |
| `helpText` | `string` | Auto-generated | Custom help text to display |
| `customValidator` | `function` | `undefined` | Custom validation function |
**Example Configurations:**
```typescript
// Default (NIST-aligned) - length over complexity
// No configuration needed, uses defaults
// Traditional complexity requirements
passwordPolicy: {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
helpText: "Must be at least 8 characters with uppercase, lowercase, and numbers"
}
// High security for enterprise
passwordPolicy: {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
customValidator: (pwd) => {
const commonPasswords = ['password123', 'admin123']
return !commonPasswords.includes(pwd.toLowerCase()) || "This password is too common"
}
}
// Extra long passphrases
passwordPolicy: {
minLength: 20,
helpText: "Use a long passphrase (20+ characters)"
}
// Development/testing only
passwordPolicy: {
minLength: 4,
helpText: "Min 4 characters (dev only)"
}
```
**Important:** Remember to also configure your Supabase project's password policy in `supabase/config.toml` to match:
```toml
[auth]
minimum_password_length = 8 # Should match your minLength
```
### 3. Add Required App Structure
Ensure your `app.vue` has the `<UApp>` wrapper (should already exist in Nuxt UI apps):
```vue
<!-- app.vue -->
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
```
### 4. Environment Variables
Set up your Supabase environment variables:
```bash
# .env
SUPABASE_URL=http://127.0.0.1:54321 # Your Supabase URL
SUPABASE_ANON_KEY=your-anon-key # Your Supabase anon key
SUPABASE_SERVICE_KEY=your-service-key # For server operations
```
**Environment Variables:**
- `SUPABASE_URL` - Supabase project URL (used by both client and server)
- `SUPABASE_ANON_KEY` - Supabase anon key (used by both client and server)
- `SUPABASE_SERVICE_KEY` - Server-side service role key
The module automatically reads these variables and configures them for both client and server usage.
### 5. Create Your Pages
Create your authentication pages with proper error handling:
```vue
<!-- pages/signin.vue -->
<template>
<div class="min-h-screen flex items-center justify-center">
<AuthSignIn
@success="handleSignIn"
@error="handleError"
/>
</div>
</template>
<script setup>
// Redirect authenticated users away from sign-in
definePageMeta({
middleware: 'redirect-authenticated'
})
const router = useRouter()
const toast = useToast()
const handleSignIn = () => {
router.push('/dashboard')
}
const handleError = (error) => {
toast.add({
title: 'Sign In Failed',
description: error,
color: 'red'
})
}
</script>
```
```vue
<!-- pages/dashboard.vue -->
<template>
<div>
<SignedIn>
<h1>Welcome to Dashboard</h1>
<UserButton :show-name="true" />
<p>Your role: <RoleBadge :role="currentRole" /></p>
</SignedIn>
</div>
</template>
<script setup>
// Require authentication for this page
definePageMeta({
middleware: 'require-auth'
})
const { currentRole } = useTeamAuth()
</script>
```
## Components Reference
### Authentication Components
| Component | Description | Key Props | Purpose |
|-----------|-------------|-----------|---------|
| `<AuthSignIn />` | Email/password sign-in form | `title`, `subtitle`, `showSocialLogin` | User authentication |
| `<AuthSignUpWithTeam />` | Sign-up form with team creation | `title`, `subtitle`, `showSocialLogin` | New user onboarding |
| `<ForgotPasswordForm />` | Password reset request form | `title`, `subtitle` | Initiate password reset |
| `<ResetPasswordForm />` | New password entry form | `title`, `subtitle` | Complete password reset |
| `<PasswordSetupForm />` | Initial password setup for invites | `title`, `subtitle` | Team invitation completion |
| `<UserButton />` | User avatar with dropdown menu | `size`, `showName`, `customItems`, `customItemsPosition` | User menu and settings |
### Conditional Rendering Components
| Component | Description | Key Props | Purpose |
|-----------|-------------|-----------|---------|
| `<SignedIn>` | Shows content only when authenticated | None (slots only) | Conditional auth content |
| `<SignedOut>` | Shows content only when not authenticated | None (slots only) | Login/public content |
### User Interface Components
| Component | Description | Key Props | Purpose |
|-----------|-------------|-----------|---------|
| `<ImpersonationBanner />` | Warning banner during impersonation | None (auto-shows) | Impersonation indicator |
| `<RoleBadge />` | Visual role indicator | `role`, `size`, `variant` | Display user permissions |
| `<UserCard />` | User profile display card | User data props | Team member listings |
### Team Management Components
| Component | Description | Key Props | Purpose |
|-----------|-------------|-----------|---------|
| `<TeamMembersDialog />` | Team member management modal | `modelValue` | Team member management |
| `<TeamForm />` | Team creation/editing form | Team data props | Team settings management |
| `<ProfileForm />` | User profile editing form | Profile data props | User account management |
### Dialog & Modal Components
| Component | Description | Key Props | Purpose |
|-----------|-------------|-----------|---------|
| `<DialogBox />` | Base modal component | `modelValue`, `title`, `subtitle` | Custom dialogs |
| `<FormDialog />` | Modal with form controls | `modelValue`, `title`, `hasChanges` | Form-based dialogs |
| `<ConfirmDialog />` | Confirmation modal | `modelValue`, `title`, `message` | User confirmations |
| `<SettingsModal />` | User/team settings modal | `modelValue`, `tab` | Settings management |
### Specialized Components
| Component | Description | Key Props | Purpose |
|-----------|-------------|-----------|---------|
| `<TeamAuthConfirmation />` | Email confirmation handler | Confirmation data | Email verification |
| `<SuperAdminImpersonationContent />` | Impersonation control panel | None | Super admin controls |
### Component Usage Examples
```vue
<!-- Basic authentication -->
<SignedOut>
<AuthSignIn @success="handleSignIn" />
</SignedOut>
<SignedIn>
<UserButton :show-name="true" size="md" />
</SignedIn>
<!-- UserButton with custom menu items -->
<UserButton
:show-name="true"
:custom-items="customMenuItems"
custom-items-position="before-signout"
/>
<!-- Team management -->
<TeamMembersDialog v-model="showTeamDialog" />
<!-- Role display -->
<RoleBadge :role="user.role" size="sm" />
<!-- Impersonation indicator (auto-shows) -->
<ImpersonationBanner />
<!-- Custom dialogs -->
<FormDialog
v-model="showForm"
title="Edit Settings"
:has-changes="formChanged"
@save="handleSave"
>
<!-- Form content -->
</FormDialog>
```
#### UserButton Custom Menu Items
The `UserButton` component supports custom menu items that integrate seamlessly with the built-in user menu:
```typescript
// Define your custom menu items
import type { CustomMenuItem } from 'nuxt-supabase-team-auth'
const customMenuItems: CustomMenuItem[] = [
{
label: 'Billing & Plans',
icon: 'i-lucide-credit-card',
to: '/billing',
requiredRole: 'admin' // Only show for admins and owners
},
{
label: 'Help & Support',
icon: 'i-lucide-help-circle',
href: 'https://docs.example.com',
target: '_blank'
},
{
label: 'Send Feedback',
icon: 'i-lucide-message-circle',
onSelect: () => openFeedbackModal(),
addSeparator: true // Add separator after this item
}
]
```
```vue
<template>
<UserButton
:custom-items="customMenuItems"
custom-items-position="before-signout"
:show-name="true"
/>
</template>
```
**CustomMenuItem Properties:**
| Property | Type | Description |
|----------|------|-------------|
| `label` | `string` | Display text for the menu item |
| `icon` | `string` | Icon name (e.g., `'i-lucide-settings'`) |
| `to` | `string` | Internal route to navigate to |
| `href` | `string` | External URL to navigate to |
| `target` | `string` | Link target (e.g., `'_blank'`) |
| `onSelect` | `function` | Custom click handler (overrides navigation) |
| `disabled` | `boolean` | Disable the menu item |
| `requiredRole` | `string` | Required role: `'member'`, `'admin'`, `'owner'`, `'super_admin'` |
| `addSeparator` | `boolean` | Add a separator line after this item |
**Positioning Options:**
- `after-user-info` - After user name/email header
- `after-main-actions` - After Settings/Company/Team items
- `before-signout` - Before sign out section *(default)*
Custom items are automatically filtered based on user roles and integrate seamlessly with the existing menu structure.
## Built-in Pages
The module automatically provides several authentication pages that you can use without creating them manually:
| Route | Component | Purpose | Usage |
|-------|-----------|---------|-------|
| `/auth/forgot-password` | `<ForgotPasswordForm />` | Password reset request | Link from sign-in page |
| `/auth/confirm` | Email confirmation handler | Email verification | Automatic redirect from email |
| `/auth/callback` | OAuth callback handler | Social login completion | Automatic OAuth redirect |
| `/accept-invite` | `<PasswordSetupForm />` | Team invitation acceptance | Email invitation links |
### Password Reset Flow
The module provides a complete password reset flow:
1. **User clicks "Forgot Password"** on your sign-in page
2. **Module navigates to** `/auth/forgot-password`
3. **User enters email** using `<ForgotPasswordForm />`
4. **User receives email** with reset link
5. **Reset link goes to** `/auth/confirm?type=recovery&...`
6. **User sets new password** using `<ResetPasswordForm />`
### Example: Adding Forgot Password to Sign-in
```vue
<!-- pages/signin.vue -->
<template>
<AuthSignIn
@forgot-password="handleForgotPassword"
/>
</template>
<script setup>
const router = useRouter()
const handleForgotPassword = () => {
// Module provides this route automatically
router.push('/auth/forgot-password')
}
</script>
```
The `AuthSignIn` component includes a "Forgot password?" button by default that emits the `@forgot-password` event when clicked.
### Team Invitation Flow
1. **Admin invites user** via `<TeamMembersDialog />`
2. **User receives email** with invitation link
3. **Link goes to** `/accept-invite?token=...`
4. **User sets password** using `<PasswordSetupForm />`
5. **User joins team** automatically
## Composables API
### `useTeamAuth()`
The primary composable for team-based authentication and management:
```typescript
const {
// Authentication state
currentUser, // Ref<User | null>
currentProfile, // Ref<Profile | null>
currentTeam, // Ref<Team | null>
currentRole, // Ref<Role | null>
teamMembers, // Ref<TeamMember[]>
isLoading, // Ref<boolean>
// Impersonation state
isImpersonating, // Ref<boolean>
impersonatedUser, // Ref<User | null>
originalUser, // Ref<User | null>
impersonationExpiresAt, // Ref<Date | null>
justStartedImpersonation, // Ref<boolean> - UI flag for modal dismissal
// Authentication methods
signIn, // (email: string, password: string) => Promise<void>
signOut, // () => Promise<void>
signUpWithTeam, // (email: string, password: string, teamName: string) => Promise<void>
// Profile management
getProfile, // () => Promise<Profile | null>
updateProfile, // (updates: Partial<Profile>) => Promise<void>
// Team management
updateTeam, // (updates: Partial<Team>) => Promise<void>
renameTeam, // (name: string) => Promise<void>
deleteTeam, // () => Promise<void>
// Member management
inviteMember, // (email: string, role: string) => Promise<void>
updateMemberRole, // (userId: string, newRole: string) => Promise<void>
removeMember, // (userId: string) => Promise<void>
promote, // (userId: string) => Promise<void> - Promote to admin
demote, // (userId: string) => Promise<void> - Demote to member
transferOwnership, // (userId: string) => Promise<void>
getTeamMembers, // () => Promise<TeamMember[]>
// Invitation management
getPendingInvitations, // () => Promise<Invitation[]>
revokeInvite, // (userId: string) => Promise<void>
resendInvite, // (userId: string) => Promise<void>
// Member profiles
getTeamMemberProfile, // (userId: string) => Promise<Profile | null>
updateTeamMemberProfile, // (userId: string, updates: Partial<Profile>) => Promise<void>
// Impersonation (Super Admin only)
startImpersonation, // (targetUserId: string, reason: string) => Promise<void>
stopImpersonation, // () => Promise<void>
// Session management
sessionHealth, // () => SessionHealthCheck
triggerSessionRecovery, // () => void
refreshAuthState, // () => Promise<void>
// Utility methods
getAvatarFallback, // (overrides?: {fullName?: string, email?: string}) => string
clearSuccessFlag // () => void - Clear justStartedImpersonation flag
} = useTeamAuth()
```
### Role Hierarchy
```typescript
type Role = 'super_admin' | 'owner' | 'admin' | 'member'
```
- **Super Admin** - Platform-wide access and impersonation
- **Owner** - Full team control, can manage all members
- **Admin** - Can invite members and manage team settings
- **Member** - Basic team access
### Error Handling
All methods throw structured errors:
```typescript
interface AuthError {
code: string
message: string
}
```
Common error codes: `SIGNIN_FAILED`, `SIGNUP_FAILED`, `PERMISSION_DENIED`,
`TEAM_NOT_FOUND`, `USER_NOT_FOUND`
## Middleware
The module provides middleware for route protection:
### Available Middleware
| Middleware | Purpose | Usage |
|------------|---------|-------|
| `require-auth` | Requires any authenticated user | Basic protected routes |
| `require-team` | Requires team membership | Team-specific pages |
| `require-role` | Requires specific roles | Admin/owner only pages |
| `redirect-authenticated` | Redirects authenticated users | Login pages |
| `impersonation` | Handles impersonation sessions | Super admin features |
### Middleware Examples
```vue
<!-- Require authentication -->
<script setup>
definePageMeta({
middleware: 'require-auth'
})
</script>
<!-- Require specific role -->
<script setup>
definePageMeta({
middleware: 'require-role',
requireRole: ['admin', 'owner']
})
</script>
<!-- Redirect if already signed in -->
<script setup>
definePageMeta({
middleware: 'redirect-authenticated'
})
</script>
```
## Server API
The module automatically provides server API endpoints:
### Authentication Endpoints
| Endpoint | Method | Purpose | Authorization |
|----------|--------|---------|---------------|
| `/api/signup-with-team` | POST | Create user + team | None |
| `/api/invite-member` | POST | Invite team member | Owner/Admin |
| `/api/accept-invite` | POST | Accept invitation | None |
### Impersonation Endpoints
| Endpoint | Method | Purpose | Authorization |
|----------|--------|---------|---------------|
| `/api/impersonate` | POST | Start impersonation | Super Admin |
| `/api/stop-impersonation` | POST | End impersonation | Super Admin |
### Example API Usage
```typescript
// Sign up with team
const response = await $fetch('/api/signup-with-team', {
method: 'POST',
body: {
email: 'user@example.com',
password: 'securepassword',
teamName: 'My Team'
}
})
// Invite member (requires auth headers)
await $fetch('/api/invite-member', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
body: {
email: 'newmember@example.com',
role: 'member',
teamId: 'team-id'
}
})
```
## User Impersonation
Super admins can temporarily act as other users for support and debugging:
### Impersonation Features
- **Audit Logging** - All sessions logged with reason and timestamps
- **Time Limits** - Automatic session expiry (30 minutes default)
- **Visual Indicators** - Clear banner when impersonating
- **Session Isolation** - Original session preserved and restored
### Impersonation Usage
```vue
<script setup>
const {
startImpersonation,
stopImpersonation,
isImpersonating,
currentRole
} = useTeamAuth()
// Check permission
const canImpersonate = computed(() =>
currentRole.value === 'super_admin'
)
// Start impersonation
const impersonateUser = async (userId) => {
await startImpersonation(userId, 'Customer support request')
}
// Stop impersonation
const endImpersonation = async () => {
await stopImpersonation()
}
</script>
<template>
<!-- Impersonation banner shows automatically -->
<ImpersonationBanner />
<!-- Impersonation controls for super admins -->
<SuperAdminImpersonationContent v-if="canImpersonate" />
</template>
```
### ImpersonationBanner Component
The `ImpersonationBanner` component provides a visual indicator when a super admin is impersonating another user. It automatically handles content displacement to ensure the banner remains visible without overlapping page content.
#### Features
- **Automatic positioning**: Fixed at the top of the viewport with proper z-index
- **Content displacement**: Automatically pushes page content down to remain visible
- **Responsive layout**: Adjusts fixed elements (sidebars, navigation) to respect banner height
- **Clean lifecycle**: Properly manages styles when banner appears/disappears
- **Real-time countdown**: Shows remaining impersonation time
- **Quick exit**: One-click "Stop Impersonation" button
#### Usage
```vue
<template>
<!-- Add to your app layout - usually in app.vue or layout file -->
<ImpersonationBanner />
<!-- Your app content -->
<div>
<!-- Content automatically pushed down when banner is active -->
</div>
</template>
```
#### Styling Integration
The banner automatically applies the following CSS when active:
```css
/* Applied to <body> when impersonating */
body.has-impersonation-banner {
padding-top: var(--impersonation-banner-height, 64px);
}
/* Fixed elements are adjusted to respect banner height */
body.has-impersonation-banner .fixed,
body.has-impersonation-banner aside.fixed,
body.has-impersonation-banner main.fixed {
top: var(--impersonation-banner-height, 64px) !important;
height: calc(100vh - var(--impersonation-banner-height, 64px)) !important;
}
```
#### Dashboard Integration
For applications using fixed layouts (like Nuxt UI Dashboard), the banner automatically:
- Calculates its actual height dynamically (not hardcoded)
- Sets CSS custom property `--impersonation-banner-height`
- Adjusts dashboard sidebars and main content areas
- Ensures proper viewport calculations
#### Properties
The component doesn't accept props - it automatically detects impersonation state from `useTeamAuth()`:
| State | Source | Description |
|-------|--------|-------------|
| `isImpersonating` | `useTeamAuth()` | Controls banner visibility |
| `impersonatedUser` | `useTeamAuth()` | User being impersonated |
| `impersonationExpiresAt` | `useTeamAuth()` | Session expiry time |
#### Layout Compatibility
The banner works with various layout patterns:
- **Static layouts**: Uses body padding to push content down
- **Fixed sidebars**: Adjusts `top` and `height` properties
- **Dashboard layouts**: Handles Nuxt UI Dashboard components specifically
- **Custom layouts**: Generic `.fixed` class handling for custom components
**No manual styling required** - the component handles all layout adjustments automatically.
## Database & Edge Functions Setup
The module requires both database schema and Edge Functions to be deployed to your Supabase project:
### Required Database Tables
- `teams` - Team information
- `team_members` - Team membership and roles
- `profiles` - User profile data
- `impersonation_sessions` - Audit log for impersonation
### Required Edge Functions
The module depends on 7 Edge Functions for team management and authentication:
- `create-team-and-owner` - Team creation and OAuth signup
- `accept-invite` - Team invitation acceptance
- `invite-member` - Send team invitations
- `revoke-invitation` - Cancel invitations
- `get-pending-invitations` - List pending invites
- `transfer-ownership` - Transfer team ownership
- `stop-impersonation` - Super admin impersonation
### Setup Commands
**For Local Development:**
```bash
# Start Supabase locally
supabase start
# Apply database migrations
supabase db reset
```
**For Production/Cloud Deployment:**
```bash
# Link to your Supabase project
supabase link --project-ref your-project-ref
# Apply database migrations
supabase db push
# Deploy ALL Edge Functions (required!)
supabase functions deploy
# Or deploy individual functions:
supabase functions deploy create-team-and-owner
supabase functions deploy accept-invite
supabase functions deploy invite-member
# ... (deploy all 7 functions)
```
**⚠️ Important:** Both database migrations AND Edge Functions must be deployed for the module to work correctly. Missing Edge Functions will result in 404 errors when using team management features.
## CLI Commands
The module includes a CLI to simplify database setup and development tasks. After installing the module, the CLI is available in multiple ways:
**For projects with the module installed:**
```bash
# Via node_modules (direct)
./node_modules/.bin/team-auth init
# Via package manager exec
pnpm exec team-auth init
npm exec team-auth init
# Via package.json scripts (recommended)
{
"scripts": {
"setup": "team-auth init"
}
}
```
**For one-time usage or without local install:**
```bash
# Via npx (downloads and runs)
npx nuxt-supabase-team-auth init
```
### Installation & Setup Commands
#### `npx nuxt-supabase-team-auth init`
Initialize the module in your Supabase project by copying migrations and Edge Functions:
```bash
# Initialize in a new Supabase project
npx nuxt-supabase-team-auth init
# Force overwrite existing files
npx nuxt-supabase-team-auth init --force
```
**What it does:**
- Copies all required database migrations to `supabase/migrations/`
- Copies all Edge Functions to `supabase/functions/`
- Detects conflicting tables and warns before proceeding
- Sets up version tracking for future updates
- Adds npm script shortcuts to your `package.json`
**Prerequisites:**
- Must be run from a Supabase project directory (with `supabase/config.toml`)
- Run `supabase init` first if starting fresh
#### `npx nuxt-supabase-team-auth migrate`
Apply new migrations when updating the module:
```bash
# Check and apply new migrations
npx nuxt-supabase-team-auth migrate
# Preview what would be applied without making changes
npx nuxt-supabase-team-auth migrate --dry-run
```
**What it does:**
- Compares your current module version with installed version
- Copies only new migration files and Edge Functions
- Updates version tracking to prevent duplicate applications
- Automatically applies migrations to local database if Supabase is running
### Development & Debugging Commands
#### `npx nuxt-supabase-team-auth cleanup`
Clean up test data and manage teams during development:
```bash
# Reset entire database (like supabase db reset)
npx nuxt-supabase-team-auth cleanup --all
# Clean only test users (emails ending with @example.com)
npx nuxt-supabase-team-auth cleanup --test-data
# Delete a specific team by ID
npx nuxt-supabase-team-auth cleanup --team 12345678-1234-1234-1234-123456789abc
```
**Safety features:**
- Confirmation prompts for destructive operations
- UUID validation for team IDs
- Uses specialized Edge Functions to bypass RLS constraints
#### `npx nuxt-supabase-team-auth db`
Inspect your database and Supabase services:
```bash
# Show Supabase services status
npx nuxt-supabase-team-auth db --status
# List all teams in the database
npx nuxt-supabase-team-auth db --teams
# List all users (limited to 50 for performance)
npx nuxt-supabase-team-auth db --users
```
### Usage Examples
**Setting up a new project:**
```bash
# 1. Create Supabase project
supabase init
# 2. Initialize team-auth
npx nuxt-supabase-team-auth init
# 3. Start local development
supabase start
# 4. Verify setup
npx nuxt-supabase-team-auth db --status
```
**Updating to a new module version:**
```bash
# 1. Update the npm package
pnpm update nuxt-supabase-team-auth
# 2. Apply new migrations
npx nuxt-supabase-team-auth migrate
# 3. Deploy to production when ready
supabase db push
supabase functions deploy
```
**Development workflow:**
```bash
# Clean test data between tests
npx nuxt-supabase-team-auth cleanup --test-data
# Check what's in the database
npx nuxt-supabase-team-auth db --teams
npx nuxt-supabase-team-auth db --users
# Delete problematic test team
npx nuxt-supabase-team-auth cleanup --team <team-id>
```
### Integration with package.json
After running the init command, you can add convenience scripts to your `package.json`:
```json
{
"scripts": {
"setup": "team-auth init",
"migrate": "team-auth migrate",
"db:clean": "team-auth cleanup --test-data"
}
}
```
This allows team members to easily run commands:
```bash
pnpm run setup # Initialize team-auth
pnpm run migrate # Apply new migrations
pnpm run db:clean # Clean test data
```
## Team Management Examples
### Basic Team Operations
```vue
<script setup>
const {
currentTeam,
currentRole,
teamMembers,
inviteMember,
updateMemberRole,
removeMember
} = useTeamAuth()
// Permission checks
const canInviteMembers = computed(() =>
['owner', 'admin'].includes(currentRole.value)
)
// Invite new member
const handleInvite = async (email, role) => {
try {
await inviteMember(email, role)
console.log('Invitation sent successfully')
} catch (error) {
console.error('Failed to invite:', error.message)
}
}
// Promote member
const promoteMember = async (userId) => {
await updateMemberRole(userId, 'admin')
}
// Remove member
const handleRemove = async (userId) => {
const confirmed = confirm('Remove this member?')
if (confirmed) {
await removeMember(userId)
}
}
</script>
<template>
<div>
<h2>{{ currentTeam?.name }}</h2>
<p>Your role: <RoleBadge :role="currentRole" /></p>
<!-- Team member list -->
<div v-for="member in teamMembers" :key="member.user_id">
<UserCard :user="member.user" :role="member.role" />
<UButton
v-if="canInviteMembers && member.role === 'member'"
@click="promoteMember(member.user_id)"
>
Promote to Admin
</UButton>
</div>
<!-- Invite new member -->
<TeamMembersDialog v-if="canInviteMembers" />
</div>
</template>
```
## Troubleshooting
### Common Integration Issues
#### Module Order Doesn't Matter
Module order in your `nuxt.config.ts` does **not** affect functionality:
```typescript
// Both work the same way
modules: ['@nuxt/ui', 'nuxt-supabase-team-auth']
modules: ['nuxt-supabase-team-auth', '@nuxt/ui']
```
#### Missing Nuxt UI Setup
If you see errors about missing components:
```bash
# Ensure you started with a Nuxt UI app
pnpm create nuxt@latest my-app -t ui
# Verify app.vue has UApp wrapper:
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
```
#### Server API Errors
If you see errors like `undefined/functions/v1/...`:
1. **Check environment variables** - Ensure `.env` has correct Supabase values
2. **Restart dev server** - Environment changes require restart
3. **Verify Supabase is running** - Local development needs `supabase start`
#### Middleware Errors
If you see `Unknown route middleware: 'require-auth'`:
1. **Restart dev server** - Middleware changes require full restart
2. **Verify module installation** - Run `pnpm install`
#### SSR Hydration Issues
Hydration mismatches are handled automatically by our components using `ClientOnly` wrappers where needed.
### Version Compatibility
This module is tested with:
- **Nuxt**: `^3.17.5`
- **@nuxt/ui**: `^3.1.3`
- **@nuxt/icon**: `^1.2.49`
### Common Patterns
#### Starting Fresh
```bash
# Create new Nuxt UI app
pnpm create nuxt@latest my-team-app -t ui
cd my-team-app
# Add our module
pnpm add nuxt-supabase-team-auth
# Configure in nuxt.config.ts (see configuration section above)
# Add environment variables
# Start building!
```
#### Integration Testing
Use our minimal test app as reference:
- `test-projects/minimal-nuxt-ui-app/` - Working integration example
- Shows proper component usage and middleware setup
### Migration from Previous Versions
If you're updating from a previous version of this module (< v0.3.6), you need to update your configuration:
#### Required Changes
1. **Remove separate @nuxtjs/supabase configuration:**
```typescript
// REMOVE: No longer needed - module handles this automatically
supabase: {
url: process.env.SUPABASE_URL, // Remove
key: process.env.SUPABASE_ANON_KEY, // Remove
redirectOptions: { ... } // Remove
},
```
2. **Update teamAuth configuration:**
```typescript
// BEFORE: Mixed configuration
teamAuth: {
supabaseUrl: process.env.SUPABASE_URL, // Remove
supabaseKey: process.env.SUPABASE_ANON_KEY, // Remove
redirectTo: '/dashboard',
loginPage: '/signin',
}
// AFTER: Unified configuration with route protection
teamAuth: {
redirectTo: '/dashboard',
loginPage: '/signin',
defaultProtection: 'public', // NEW: Route protection mode
protectedRoutes: ['/dashboard'], // NEW: Specific protected routes
socialProviders: {
google: { enabled: true }
}
}
```
3. **Environment variables remain the same:**
```bash
# The module reads these standard env vars:
SUPABASE_URL=...
SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_KEY=...
```
4. **Add error handling to auth pages:**
```vue
<!-- BEFORE: Only success handler -->
<AuthSignIn @success="handleSignIn" />
<!-- AFTER: Add error handler for user feedback -->
<AuthSignIn
@success="handleSignIn"
@error="handleError"
/>
<script setup>
const toast = useToast()
const handleError = (error) => {
toast.add({ title: 'Sign In Failed', description: error, color: 'red' })
}
</script>
```
```
#### Benefits of New Architecture
- **Simplified Configuration** - Single `teamAuth` config block, no separate Supabase setup
- **Automatic Dependency Management** - Uses `installModule()` pattern for proper integration
- **Flexible Route Protection** - Choose between public-by-default or protected-by-default modes
- **Standard Environment Variables** - Uses standard Supabase env var names
- **Improved Error Handling** - Components emit errors for proper user feedback
## Development
```bash
# Install dependencies
pnpm install
# Generate types
pnpm run types:generate
# Start the playground
pnpm run dev
# Run tests
pnpm run test
# Build the module
pnpm run build
```
## Contributing
Contributions are welcome! Please read our [contributing guidelines]
(CONTRIBUTING.md) before submitting a PR.
## License
[MIT](./LICENSE) License © 2024
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/nuxt-supabase-team-auth/latest.svg?style=flat&colorA=020420&colorB=00DC82
[npm-version-href]: https://npmjs.com/package/nuxt-supabase-team-auth
[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-supabase-team-auth.svg?style=flat&colorA=020420&colorB=00DC82
[npm-downloads-href]: https://npmjs.com/package/nuxt-supabase-team-auth
[license-src]: https://img.shields.io/npm/l/nuxt-supabase-team-auth.svg?style=flat&colorA=020420&colorB=00DC82
[license-href]: https://npmjs.com/package/nuxt-supabase-team-auth
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
[nuxt-href]: https://nuxt.com