Security audit workflow - vulnerability scan → verification
npx skills add TrenzaCR/trenzaos-config --skill "trenza-supabase-auth"
Install specific skill from multi-skill repository
# Description
>
# SKILL.md
name: trenza-supabase-auth
description: >
Autenticación y gestión de sesiones con Supabase Auth.
Trigger: Al implementar login, logout, registro, recuperación de contraseña, o sesiones.
license: MIT
metadata:
author: trenza
version: "1.0"
TrenzaOS Supabase Auth Skills
Purpose
Este skill enforce las mejores prácticas de autenticación y gestión de sesiones.
Core Rules
1. Autenticación - Flujo Completo
import { createClient } from '@/lib/supabase/server'
// 1. Inicio de sesión
async function signIn(email: string, password: string) {
const supabase = await createClient()
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) {
return { status: 'error', error_code: 'AUTH_INVALID_CREDENTIALS' }
}
// 2. Obtener tenant del usuario
const tenantId = await getTenantForUser(data.user.id)
if (!tenantId) {
// Usuario sin tenant asignado
await supabase.auth.signOut()
return { status: 'error', error_code: 'NO_TENANT_ASSIGNED' }
}
// 3. Crear cookie de tenant
await setTenantCookie(tenantId)
return {
status: 'success',
data: {
session: data.session,
tenantId
}
}
}
// 2. Cierre de sesión
async function signOut() {
const supabase = await createClient()
await supabase.auth.signOut()
await deleteTenantCookie()
}
2. Middleware de Autenticación
// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
let supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get: (key) => request.cookies.get(key)?.value,
set: (key, value, options) => {
request.cookies.set({ key, value, ...options })
},
remove: (key, options) => {
request.cookies.set({ key, value: '', ...options })
},
},
}
)
// Refrescar sesión
const { data: { session } } = await supabase.auth.getSession()
// Rutas protegidas
if (!session && isProtectedRoute(request.nextUrl.pathname)) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
3. Obtención de Usuario Actual
// lib/auth/user.ts
import { createClient } from '@/lib/supabase/server'
export async function getCurrentUser() {
const supabase = await createClient()
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return null
}
// Obtener datos del usuario desde la tabla users
const { data: userData } = await supabase
.from('users')
.select('*, tenants!inner(*)')
.eq('auth_id', user.id)
.single()
return userData
}
export async function getCurrentTenantId(): Promise<string> {
const cookieStore = await cookies()
const tenantId = cookieStore.get('tenant_id')?.value
if (!tenantId) {
throw new Error('TENANT_NOT_FOUND')
}
return tenantId
}
4. Roles y Permisos
-- Tabla de roles
CREATE TABLE user_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL, -- owner, admin, manager, member, viewer
permissions JSONB DEFAULT '[]' -- ['read:invoices', 'write:invoices', 'delete:invoices']
);
-- Asignar rol a usuario
ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'member';
ALTER TABLE users ADD COLUMN permissions JSONB DEFAULT '[]';
// Verificación de permisos
function hasPermission(user: User, permission: string): boolean {
const rolePermissions = getRolePermissions(user.role)
return rolePermissions.includes(permission)
}
// En Server Action
async function createInvoice(formData: FormData) {
const user = await getCurrentUser()
if (!hasPermission(user, 'write:invoices')) {
return { status: 'error', error_code: 'FORBIDDEN' }
}
// Continuar...
}
5. JWT y Claims
// Configurar JWT con tenant_id
// El tenant_id debe estar en el JWT para RLS
// hooks/use-auth.ts
export async function refreshSession() {
const supabase = await createClient()
const { data, error } = await supabase.auth.getSession()
if (data.session) {
// Asegurar que tenant_id está en el JWT
const tenantId = await getTenantForUser(data.session.user.id)
// Crear cookie con tenant
await setTenantCookie(tenantId)
}
}
6. Rate Limiting en Login
// lib/rate-limit.ts
const loginRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(5, '15m') // 5 intentos cada 15 min
})
export async function rateLimitLogin(ip: string): Promise<boolean> {
const { success } = await loginRateLimit.limit(ip)
return success
}
// En Server Action de login
export async function login(prevState, formData: FormData) {
const ip = headers().get('x-forwarded-for') || 'unknown'
if (!(await rateLimitLogin(ip))) {
return {
status: 'error',
error_code: 'RATE_LIMITED',
message: 'Demasiados intentos. Intenta en 15 minutos.'
}
}
// Continuar con login...
}
7. Recuperación de Contraseña
// Solicitar reset
async function requestPasswordReset(email: string) {
const supabase = await createClient()
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/reset-password`
})
// Siempre devolver éxito para evitar enumeration
return {
status: 'success',
message: 'Si el email existe, recibirás un enlace de recuperación.'
}
}
// Reset de contraseña
async function resetPassword(newPassword: string) {
const supabase = await createClient()
const { data, error } = await supabase.auth.updateUser({
password: newPassword
})
if (error) {
return { status: 'error', error_code: 'RESET_FAILED' }
}
return { status: 'success' }
}
8. Verificación de Email
// Forzar verificación de email antes de dar acceso
async function signUp(email: string, password: string) {
const supabase = await createClient()
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`
}
})
if (error) {
return { status: 'error', error_code: 'SIGNUP_FAILED' }
}
// Usuario creado pero necesita verificar email
return {
status: 'success',
data: { needsVerification: !data.session }
}
}
Security Checklist
- [ ] ¿Usas Supabase Auth (no credenciales propias)?
- [ ] ¿Tienes rate limiting en login?
- [ ] ¿El tenant_id está en el JWT?
- [ ] ¿Manejas correctamente el refresh de sesión?
- [ ] ¿Previenes enumeration de emails en login/signup?
- [ ] ¿Las rutas sensibles tienen middleware de auth?
References
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.