nextjs-rewrite (#5)
Reviewed-on: #5 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
This commit was merged in pull request #5.
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
# Authentication, Authorization, and Accounting (AAA) Design
|
||||
|
||||
## Overview
|
||||
|
||||
Implement a comprehensive AAA system for EuchreCamp with user authentication, role-based authorization, and activity logging using Next.js and Better Auth.
|
||||
|
||||
## Authentication (Who are you?)
|
||||
|
||||
### User Model (Prisma Schema)
|
||||
|
||||
```prisma
|
||||
// prisma/schema.prisma
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
emailVerified Boolean @default(false)
|
||||
password String?
|
||||
name String?
|
||||
role Role @default(PLAYER)
|
||||
|
||||
// Relationships
|
||||
player Player? @relation(fields: [playerId], references: [id])
|
||||
playerId String?
|
||||
|
||||
// Better Auth fields
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Player {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
rating Int @default(1000)
|
||||
user User?
|
||||
userId String?
|
||||
|
||||
// Relationships
|
||||
matches Match[]
|
||||
partnerships Partnership[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
enum Role {
|
||||
PLAYER
|
||||
TOURNAMENT_ADMIN
|
||||
CLUB_ADMIN
|
||||
}
|
||||
```
|
||||
|
||||
### Authentication Flow (Better Auth)
|
||||
|
||||
1. User enters email and password
|
||||
2. Better Auth verifies email exists
|
||||
3. Password verification using secure hashing (scrypt by default)
|
||||
4. If successful:
|
||||
- Create session token
|
||||
- Set secure HTTP-only cookie
|
||||
- Redirect to dashboard
|
||||
5. If failed:
|
||||
- Rate limiting applied (Better Auth built-in)
|
||||
- Account lockout after multiple failed attempts
|
||||
|
||||
### Session Management (Better Auth)
|
||||
|
||||
- **Session Storage**: Secure HTTP-only cookies
|
||||
- **Session Expiration**: Configurable (default 30 days)
|
||||
- **Session Invalidation**: On password change
|
||||
- **Concurrent Sessions**: Supported with session tracking
|
||||
|
||||
### Password Requirements
|
||||
|
||||
- Minimum 8 characters
|
||||
- Configurable via Better Auth settings
|
||||
- Hashed with scrypt (server-side)
|
||||
|
||||
### Registration Flow (Next.js + Better Auth)
|
||||
|
||||
1. User registers with email and password via `/auth/register`
|
||||
2. System creates user record with `emailVerified: false`
|
||||
3. Email confirmation flow (if enabled)
|
||||
4. User can log in after verification
|
||||
|
||||
### Password Reset Flow
|
||||
|
||||
1. User requests password reset via `/auth/reset-password`
|
||||
2. System generates unique token
|
||||
3. Email reset link to user
|
||||
4. User enters new password
|
||||
5. System validates and updates password hash
|
||||
6. Invalidate all existing sessions
|
||||
|
||||
## Authorization (What can you do?)
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
#### Roles
|
||||
|
||||
```typescript
|
||||
// src/lib/auth.ts
|
||||
enum Role {
|
||||
PLAYER = 0, // Default user - can view rankings, own profile
|
||||
TOURNAMENT_ADMIN = 1, // Can manage specific tournaments
|
||||
CLUB_ADMIN = 2 // Superuser - full access
|
||||
}
|
||||
```
|
||||
|
||||
#### Permission Matrix
|
||||
|
||||
| Permission | Player | Tournament Admin | Club Admin |
|
||||
|------------|--------|------------------|------------|
|
||||
| View rankings | ✅ | ✅ | ✅ |
|
||||
| View own profile | ✅ | ✅ | ✅ |
|
||||
| Edit own profile | ✅ | ✅ | ✅ |
|
||||
| View other profiles | ✅ | ✅ | ✅ |
|
||||
| Record own matches | ✅ | ✅ | ✅ |
|
||||
| Create tournaments | ❌ | ✅ | ✅ |
|
||||
| Manage own tournaments | ❌ | ✅ | ✅ |
|
||||
| Manage all tournaments | ❌ | ❌ | ✅ |
|
||||
| Manage all players | ❌ | ❌ | ✅ |
|
||||
| Club settings | ❌ | ❌ | ✅ |
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
// src/lib/auth.ts
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prisma,
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [
|
||||
// Additional plugins as needed
|
||||
],
|
||||
});
|
||||
|
||||
// Authorization helper
|
||||
export function can(user: User, action: string, resource?: any): boolean {
|
||||
switch (action) {
|
||||
case "view_rankings":
|
||||
return true; // All users can view rankings
|
||||
case "edit_profile":
|
||||
return !resource || resource.id === user.playerId;
|
||||
case "create_tournament":
|
||||
return user.role === "CLUB_ADMIN";
|
||||
case "manage_tournament":
|
||||
return user.role === "CLUB_ADMIN" ||
|
||||
(user.role === "TOURNAMENT_ADMIN" && resource.adminId === user.playerId);
|
||||
case "manage_players":
|
||||
return user.role === "CLUB_ADMIN";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
```typescript
|
||||
// src/middleware.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { getSession } from "@/lib/auth";
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const session = await getSession(request);
|
||||
|
||||
// Protect admin routes
|
||||
if (request.nextUrl.pathname.startsWith("/admin")) {
|
||||
if (!session) {
|
||||
return NextResponse.redirect(new URL("/auth/login", request.url));
|
||||
}
|
||||
if (session.user.role !== "CLUB_ADMIN") {
|
||||
return NextResponse.redirect(new URL("/unauthorized", request.url));
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/admin/:path*"],
|
||||
};
|
||||
```
|
||||
|
||||
## Accounting (What did you do?)
|
||||
|
||||
### Activity Logging (Prisma)
|
||||
|
||||
```prisma
|
||||
// prisma/schema.prisma
|
||||
model ActivityLog {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
action String // e.g., 'login', 'create_tournament', 'record_match'
|
||||
resourceType String? // e.g., 'tournament', 'match', 'player'
|
||||
resourceId String?
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
details Json? // Additional context
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId, createdAt])
|
||||
@@index([resourceType, resourceId])
|
||||
}
|
||||
```
|
||||
|
||||
### Tracked Events
|
||||
|
||||
- Authentication events (login, logout, password change)
|
||||
- Tournament management (create, update, delete, schedule)
|
||||
- Match recording (create, update, delete)
|
||||
- Player management (create, update)
|
||||
- Settings changes
|
||||
|
||||
### Audit Reports
|
||||
|
||||
- User activity timeline
|
||||
- Tournament lifecycle audit
|
||||
- Match result verification log
|
||||
- System changes overview
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
// src/lib/activity.ts
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
export async function logActivity(
|
||||
userId: string,
|
||||
action: string,
|
||||
resourceType?: string,
|
||||
resourceId?: string,
|
||||
details?: any
|
||||
) {
|
||||
await prisma.activityLog.create({
|
||||
data: {
|
||||
userId,
|
||||
action,
|
||||
resourceType,
|
||||
resourceId,
|
||||
details,
|
||||
ipAddress: "0.0.0.0", // Get from request
|
||||
userAgent: "unknown", // Get from request
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Authentication (Week 1-2)
|
||||
- [x] Set up Better Auth with Prisma
|
||||
- [x] Create login page (`/auth/login`)
|
||||
- [x] Create registration page (`/auth/register`)
|
||||
- [x] Implement session management
|
||||
- [x] Add navigation for logged-in users
|
||||
- [ ] Password reset functionality
|
||||
- [ ] Email confirmation system
|
||||
|
||||
### Phase 2: Authorization (Week 2-3)
|
||||
- [x] Define roles in Prisma schema
|
||||
- [x] Implement RBAC middleware
|
||||
- [ ] Add role assignment UI (for club admins)
|
||||
- [ ] Protect routes based on roles
|
||||
- [ ] Add permission checks to API routes
|
||||
|
||||
### Phase 3: Accounting (Week 3-4)
|
||||
- [ ] Create activity logging system
|
||||
- [ ] Track key events
|
||||
- [ ] Build audit reports UI
|
||||
- [ ] Add activity feed to dashboard
|
||||
|
||||
### Phase 4: Security Hardening (Week 4-5)
|
||||
- [x] Rate limiting (Better Auth built-in)
|
||||
- [ ] IP-based lockout
|
||||
- [ ] Secure cookie settings
|
||||
- [ ] CSRF protection (Next.js built-in)
|
||||
- [ ] Security headers
|
||||
- [ ] Session fixation prevention
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Password Security
|
||||
- Use scrypt hashing (Better Auth default)
|
||||
- Never store plaintext passwords
|
||||
- Enforce strong password policy (via Better Auth)
|
||||
- Rate limit password attempts
|
||||
|
||||
### Session Security
|
||||
- Use signed, encrypted cookies
|
||||
- Regenerate session ID on privilege escalation
|
||||
- Set appropriate expiration times
|
||||
- Invalidate on password change
|
||||
|
||||
### Access Control
|
||||
- Principle of least privilege
|
||||
- Defense in depth
|
||||
- Fail securely (deny by default)
|
||||
- Log all access attempts
|
||||
|
||||
### Data Protection
|
||||
- Secure transmission (HTTPS only)
|
||||
- Regular security audits
|
||||
- Compliance with privacy regulations
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication (Better Auth)
|
||||
```
|
||||
POST /api/auth/sign-up/email # Register new account
|
||||
POST /api/auth/sign-in/email # Login with email/password
|
||||
POST /api/auth/sign-out # Logout current session
|
||||
POST /api/auth/forgot-password # Request password reset
|
||||
POST /api/auth/reset-password # Reset password with token
|
||||
```
|
||||
|
||||
### Users
|
||||
```
|
||||
GET /api/users/me # Get current user profile
|
||||
PATCH /api/users/me # Update own profile
|
||||
GET /api/users/:id # Get user profile (public)
|
||||
```
|
||||
|
||||
### Admin
|
||||
```
|
||||
GET /api/admin/users # List users (club admin only)
|
||||
PATCH /api/admin/users/:id # Update user roles (club admin only)
|
||||
GET /api/admin/activity # View audit logs (club admin only)
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Vitest)
|
||||
- Password hashing verification
|
||||
- Permission calculations
|
||||
- Session management
|
||||
- Activity logging
|
||||
|
||||
### Integration Tests
|
||||
- Login/logout flow
|
||||
- Registration process
|
||||
- Password reset
|
||||
- Role-based access
|
||||
|
||||
### Security Tests
|
||||
- SQL injection prevention (Prisma)
|
||||
- XSS prevention (Next.js)
|
||||
- CSRF protection (Next.js)
|
||||
- Rate limiting
|
||||
- Session security
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. Set up Better Auth with Prisma
|
||||
2. Create users table and related models
|
||||
3. Add foreign key from users to players
|
||||
4. Update navigation to show login/logout links
|
||||
5. Add authentication checks to existing routes
|
||||
6. Implement role assignment for existing admins
|
||||
7. Set up activity logging for new features
|
||||
Reference in New Issue
Block a user