# Authentication & Authorization System Guide

This guide explains how to configure API endpoint access control in Bangstack.

## Core Principle

**All authentication rules must be explicit.** If `auth: required`, you **must** specify who can access.

---

## Basic Auth Levels

### Public Endpoints (No Auth)

```yaml
api:
  endpoints:
    # Anyone can access, no login required
    - path: /public/catalog
      method: GET
      auth: none
      component: catalog
```

**Use cases**: Product catalogs, landing pages, public documentation

---

### Authenticated Endpoints (Role-Based)

```yaml
# User model must define roles
models:
  User:
    fields:
      role:
        type: enum
        enum_values: [superadmin, admin, user, viewer, guest]

api:
  endpoints:
    # Only admin and superadmin can access
    - path: /admin/users
      method: GET
      auth: required
      roles: [superadmin, admin]
      component: admin

    # Any authenticated user (except guests)
    - path: /tasks
      method: GET
      auth: required
      roles: [superadmin, admin, user, viewer]
      component: tasks

    # Or use exclude_roles for same effect
    - path: /tasks
      method: GET
      auth: required
      roles: []  # Empty = all authenticated users
      exclude_roles: [guest]  # Except guests
      component: tasks

    # Only regular users (not admins or guests)
    - path: /tasks
      method: POST
      auth: required
      roles: [user]
      component: tasks
```

**Validation**: Roles must match `enum_values` from User model's role field.

---

## Ownership-Based Access

For endpoints where users can only access their own data:

### Basic Ownership

```yaml
api:
  endpoints:
    # Users can only access their own profile
    - path: /users/:id
      method: GET
      auth: required
      roles: []  # Any authenticated user
      ownership:
        enabled: true
        # Checks: URL param 'id' matches authenticated user's id
      component: users
```

**How it works**:
```typescript
// Generated middleware checks:
if (req.params.id !== req.user.id) {
  return res.status(403).json({ error: 'Forbidden' });
}
```

### Ownership with Admin Override

```yaml
api:
  endpoints:
    # Users can only update their own profile
    # BUT admins and superadmins can update anyone's profile
    - path: /users/:id
      method: PATCH
      auth: required
      roles: []
      ownership:
        enabled: true
        override_roles: [superadmin, admin]
      component: users
```

**How it works**:
```typescript
// Generated middleware checks:
const isSuperUser = ['superadmin', 'admin'].includes(req.user.role);
const isOwner = req.params.id === req.user.id;

if (!isSuperUser && !isOwner) {
  return res.status(403).json({ error: 'Forbidden' });
}
```

### Custom Ownership Field

When ownership is based on a different field:

```yaml
models:
  Task:
    fields:
      assigned_to_id: { type: uuid, foreign_key: User }

api:
  endpoints:
    # Users can only access tasks assigned to them
    - path: /tasks/:id
      method: GET
      auth: required
      roles: []
      ownership:
        enabled: true
        field: assigned_to_id  # Check this field instead of id
        override_roles: [superadmin, admin]
      component: tasks
```

**How it works**:
```typescript
// Generated middleware:
const task = await Task.findById(req.params.id);
const isSuperUser = ['superadmin', 'admin'].includes(req.user.role);
const isAssigned = task.assigned_to_id === req.user.id;

if (!isSuperUser && !isAssigned) {
  return res.status(403).json({ error: 'Forbidden' });
}
```

---

## Complex Authorization Patterns

### Combining Roles + Ownership

```yaml
# Only admins and task owners can delete tasks
- path: /tasks/:id
  method: DELETE
  auth: required
  roles: [admin, user]  # Must be admin or user role
  ownership:
    enabled: true
    field: assigned_to_id
    override_roles: [admin]  # Admins bypass ownership check
  component: tasks
```

**Logic**:
- Must be `admin` OR `user` role (roles filter)
- If `user` role: must be assigned to the task (ownership check)
- If `admin` role: can delete any task (override)

### Multiple Roles, No Guests, Owner-Only

```yaml
# Users and viewers can read their own data
# Admins can read anyone's data
- path: /analytics/:userId
  method: GET
  auth: required
  roles: [admin, user, viewer]
  exclude_roles: [guest]  # Guests excluded even if they authenticate
  ownership:
    enabled: true
    field: userId  # URL param
    override_roles: [admin]
  component: analytics
```

---

## Complete Example: Task Manager API

```yaml
models:
  User:
    fields:
      role:
        type: enum
        enum_values: [superadmin, admin, user, guest]

api:
  endpoints:
    # =========================================================================
    # PUBLIC ENDPOINTS
    # =========================================================================

    # Anyone can view product catalog
    - path: /catalog
      method: GET
      auth: none
      component: catalog

    # =========================================================================
    # ADMIN-ONLY ENDPOINTS
    # =========================================================================

    # Only superadmin can manage users
    - path: /admin/users
      method: GET
      auth: required
      roles: [superadmin]
      component: admin

    - path: /admin/users/:id
      method: DELETE
      auth: required
      roles: [superadmin]
      component: admin

    # Admin and superadmin can view all tasks
    - path: /admin/tasks
      method: GET
      auth: required
      roles: [superadmin, admin]
      component: admin

    # =========================================================================
    # USER ENDPOINTS (WITH OWNERSHIP)
    # =========================================================================

    # Any authenticated user can list their own tasks
    - path: /tasks
      method: GET
      auth: required
      roles: []  # Any authenticated user
      exclude_roles: [guest]
      component: tasks
      # No ownership here - query filters by user_id

    # Create task (no ownership needed)
    - path: /tasks
      method: POST
      auth: required
      roles: [user]  # Only users can create
      component: tasks

    # View specific task - must be assigned to you (or admin)
    - path: /tasks/:id
      method: GET
      auth: required
      roles: []
      ownership:
        enabled: true
        field: assigned_to_id
        override_roles: [superadmin, admin]
      component: tasks

    # Update task - must be assigned to you (or admin)
    - path: /tasks/:id
      method: PATCH
      auth: required
      roles: [user]
      ownership:
        enabled: true
        field: assigned_to_id
        override_roles: [superadmin, admin]
      component: tasks

    # Delete task - only admins
    - path: /tasks/:id
      method: DELETE
      auth: required
      roles: [superadmin, admin]
      component: tasks

    # =========================================================================
    # PROFILE ENDPOINTS
    # =========================================================================

    # View own profile
    - path: /users/:id
      method: GET
      auth: required
      roles: []
      ownership:
        enabled: true
        override_roles: [superadmin, admin]
      component: users

    # Update own profile
    - path: /users/:id
      method: PATCH
      auth: required
      roles: []
      ownership:
        enabled: true
        override_roles: [superadmin, admin]
      component: users
```

---

## Generated Middleware

From the YAML above, Bangstack generates type-safe middleware:

```typescript
// src/middleware/auth.ts
export function requireRoles(allowedRoles: string[], excludeRoles?: string[]) {
  return (req: AuthRequest, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    // Check excluded roles
    if (excludeRoles?.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden - role not allowed' });
    }

    // Check allowed roles (empty array = all roles allowed)
    if (allowedRoles.length > 0 && !allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden - insufficient permissions' });
    }

    next();
  };
}

export function requireOwnership(options: OwnershipOptions) {
  return async (req: AuthRequest, res: Response, next: NextFunction) => {
    // Super users bypass ownership checks
    if (options.override_roles?.includes(req.user.role)) {
      return next();
    }

    const field = options.field || 'id';
    const resourceId = req.params[field];

    // Check ownership
    if (resourceId !== req.user.id) {
      return res.status(403).json({ error: 'Forbidden - not owner' });
    }

    next();
  };
}
```

**Usage in generated routes**:
```typescript
// Generated from YAML
router.patch(
  '/tasks/:id',
  authenticate,
  requireRoles(['user'], ['guest']),
  requireOwnership({
    field: 'assigned_to_id',
    override_roles: ['superadmin', 'admin']
  }),
  taskController.update
);
```

---

## Validation Rules

When you run `bang validate`, the system checks:

### 1. Roles Must Be Defined in User Model
```yaml
# ✅ Valid
models:
  User:
    fields:
      role: { type: enum, enum_values: [admin, user] }

api:
  endpoints:
    - roles: [admin, user]  # All roles exist

# ❌ Invalid
api:
  endpoints:
    - roles: [superadmin]  # Error: 'superadmin' not in User.role enum
```

### 2. Auth Required Endpoints Must Specify Roles
```yaml
# ❌ Invalid
- path: /tasks
  auth: required
  # Error: Must specify roles (even if empty array)

# ✅ Valid
- path: /tasks
  auth: required
  roles: []  # Explicitly allow all authenticated users
```

### 3. Ownership Field Must Exist in Model
```yaml
# ❌ Invalid
models:
  Task:
    fields:
      user_id: uuid

api:
  endpoints:
    - ownership:
        field: assigned_to_id  # Error: Task model has no assigned_to_id field

# ✅ Valid
models:
  Task:
    fields:
      assigned_to_id: uuid

api:
  endpoints:
    - ownership:
        field: assigned_to_id  # Valid
```

---

## Best Practices

### 1. Use Superadmin for System-Wide Access
```yaml
role: { type: enum, enum_values: [superadmin, admin, user] }
```
- `superadmin`: Full system access, bypasses all ownership
- `admin`: Organizational admin, can manage team data
- `user`: Regular user, owns their data

### 2. Always Exclude Guests from Sensitive Operations
```yaml
- path: /tasks
  method: POST
  auth: required
  roles: []
  exclude_roles: [guest]  # Guests can browse but not create
```

### 3. Use Ownership for User Data
```yaml
# Users can only access their own data
- path: /users/:id
  method: GET
  ownership:
    enabled: true
    override_roles: [superadmin, admin]
```

### 4. Be Explicit About Admin Overrides
```yaml
# Make it clear when admins have special access
- ownership:
    enabled: true
    override_roles: [superadmin, admin]  # Document why they can override
```

---

## Summary

**Required Fields**:
- `auth`: Must be `required` or `none`
- `roles`: Must be specified if `auth: required` (can be `[]` for all users)

**Optional Fields**:
- `exclude_roles`: Exclude specific roles even if authenticated
- `ownership`: Restrict access to resource owners (with admin overrides)

**Validation**:
- All roles must exist in User model
- Ownership fields must exist in resource model
- No ambiguous configurations allowed

This system ensures **explicit, type-safe authorization** for every endpoint.
