npm package: @kurto/payload-access
Description: Payload 3.0 Basic Access Control Utilities
Latest version: 1.0.3

# payload-access

A Role-Based Access Control (RBAC) plugin for [Payload CMS](https://payloadcms.com) with a 3-tier permission system.

## Features

- **3-Tier Role System**: Developer, Admin, and Editor roles with hierarchical permissions
- **First User Protection**: Automatically assigns the first user as a Developer
- **Developer Email Enforcement**: Hardcode specific emails to always have Developer role
- **Granular Access Control**: Prevents privilege escalation and unauthorized access
- **Helper Functions**: Export `isDeveloper`, `isAdmin`, `isEditor` for use in your collections

## Installation

```bash
pnpm add payload-access
```

## Usage

Add the plugin to your Payload config:

```typescript
import { payloadAccess } from 'payload-access'
import { buildConfig } from 'payload'

export default buildConfig({
  collections: [
    {
      slug: 'users',
      auth: true,
      fields: [
        // Your user fields
      ],
    },
  ],
  plugins: [
    payloadAccess({
      developerEmails: ['admin@example.com', 'dev@example.com'],
    }),
  ],
})
```

## Role Hierarchy

### Developer

- Full access to all users
- Can create, read, update, and delete any user
- Cannot be modified by Admins or Editors

### Admin

- Can create and manage users
- Cannot modify or delete users with Developer role
- Cannot grant Developer role to any user

### Editor

- Can only update their own profile
- Cannot create, update, or delete other users

## Configuration

### Options

```typescript
type PayloadAccessConfig = {
  developerEmails?: string[] // Array of emails that should always have Developer role
  disabled?: boolean // Disable the plugin
}
```

### Example

```typescript
payloadAccess({
  developerEmails: ['founder@company.com', 'cto@company.com'],
})
```

## Helper Functions

The plugin exports helper functions you can use in your own collections:

### Basic Role Checks

```typescript
import { isDeveloper, isAdmin, isEditor } from 'payload-access'

// Check individual roles
isDeveloper(user) // Returns true if user has developer role
isAdmin(user) // Returns true if user has admin role
isEditor(user) // Returns true if user has editor role
```

### Advanced Helpers

```typescript
import {
  hasElevatedAccess,
  hasMinimumRole,
  hasAnyRole,
  hasAllRoles,
  getUserRoles,
  canManageUser,
  isOwner,
  canEdit,
} from 'payload-access'

// Check for elevated access (developer OR admin)
hasElevatedAccess(user)

// Check role hierarchy (developer > admin > editor)
hasMinimumRole(user, 'admin') // True if developer or admin
hasMinimumRole(user, 'editor') // True if any role

// Check for specific role combinations
hasAnyRole(user, ['developer', 'admin']) // Has at least one
hasAllRoles(user, ['admin', 'editor']) // Has all specified

// Get all user roles as array
const roles = getUserRoles(user) // ['developer', 'admin']

// Check if user can manage another user
canManageUser(currentUser, targetUser) // Respects hierarchy

// Check document ownership
isOwner(user, documentUserId) // Compares user IDs

// Check edit permissions (owner OR elevated access)
canEdit(user, documentUserId)
```

### Usage Examples

**Simple access control:**

```typescript
{
  slug: 'posts',
  access: {
    create: ({ req: { user } }) => hasElevatedAccess(user),
    update: ({ req: { user } }) => {
      if (hasElevatedAccess(user)) return true
      return { author: { equals: user.id } }
    },
  },
}
```

**Role-based field access:**

```typescript
{
  name: 'salary',
  type: 'number',
  access: {
    read: ({ req: { user }, doc }) => {
      return isOwner(user, doc.id) || hasMinimumRole(user, 'admin')
    },
    update: ({ req: { user } }) => hasMinimumRole(user, 'admin'),
  },
}
```

**Complex permissions:**

```typescript
{
  slug: 'projects',
  access: {
    delete: ({ req: { user } }) => {
      // Only developers can delete, or admins for non-critical projects
      if (isDeveloper(user)) return true
      if (isAdmin(user)) {
        return { critical: { equals: false } }
      }
      return false
    },
  },
}
```

## How It Works

### First User Rule

When the first user is created (total user count === 0), they are automatically assigned the Developer role, regardless of what role is specified.

### Developer Email Enforcement

If a user's email matches any email in the `developerEmails` array, their role is automatically set to Developer on create or update.

### Access Control

The plugin modifies the Users collection to add:

- A multi-select `role` field with options: Developer, Admin, Editor
- `beforeValidate` hooks to enforce first user and developer email rules
- Access control functions to prevent privilege escalation

## Development

This plugin is part of a monorepo. To develop locally:

```bash
# Install dependencies
pnpm install

# Start dev server
pnpm dev
```

## License

MIT