# rab-access

A TypeScript library for Role-Based Access Control (RBAC) that provides a flexible and powerful permission system with conditional grants, field-level access control, and validation capabilities.

## Features

- **Schema-based Permission Definition**: Define permissions using declarative schemas
- **Role-based Access Control**: Grant permissions based on user roles
- **Conditional Grants**: Support for conditional permissions based on field comparisons
- **Field-level Permissions**: Control access to specific fields/columns
- **Permission Inheritance**: Extend permissions from other grants
- **Custom Validation**: Support for custom validation functions
- **Filtering**: Built-in support for lookup filters and data filtering
- **Type Safety**: Full TypeScript support with type safety

## Installation

```bash
npm install rab-access
```

## Quick Start

```typescript
import { Rab } from 'rab-access';

// Define your roles
enum AppRoles {
  administrator = 'administrator',
  system_admin = 'system_admin',
  user = 'user',
}

// Define your permissions
enum AppAccess {
  canUpdateShopDetails = 'canUpdateShopDetails',
  canChangeShopApprovalStatus = 'canChangeShopApprovalStatus',
  canReadAllUsers = 'canReadAllUsers',
}

// Create a permission schema
const shopPermissions = Rab.schema({
  [AppAccess.canUpdateShopDetails]: [
    Rab.grant(AppRoles.administrator).ifEqual(
      Rab.auth('shopId'),
      Rab.params('shopId')
    ),
    Rab.grant(AppRoles.system_admin),
  ],
  [AppAccess.canChangeShopApprovalStatus]: [
    Rab.grant(AppRoles.system_admin),
  ],
});

// Check permissions
const grant = await shopPermissions.getGrant({
  permission: AppAccess.canUpdateShopDetails,
  role: AppRoles.administrator,
  request: {
    user: { shopId: '123' },
    params: { shopId: '123' },
  },
});

console.log(grant.isAuthorized); // true
```

## Core Concepts

### Permission Schema

A permission schema defines the relationship between permissions and roles:

```typescript
const permissions = Rab.schema({
  [AppAccess.canReadUser]: [
    Rab.grant(AppRoles.user).ifEqual(
      Rab.auth('id'),
      Rab.params('userId')
    ),
    Rab.grant(AppRoles.admin),
  ],
});
```

### Grant Types

#### Simple Role Grant
```typescript
Rab.grant(AppRoles.admin)
```

#### Conditional Grant
```typescript
Rab.grant(AppRoles.user).ifEqual(
  Rab.auth('organizationId'),
  Rab.params('organizationId')
)
```

#### Grant with Field Restrictions
```typescript
Rab.grant(AppRoles.user)
  .columns(['name', 'email'])
  .ifEqual(Rab.auth('id'), Rab.params('userId'))
```

#### Grant with Filters
```typescript
Rab.grant(AppRoles.manager)
  .filters({ active: true })
  .lookupFilters({
    department: {
      id: Rab.auth('departmentIds')
    }
  })
```

### Field References

Access different parts of the request context:

```typescript
// Reference user fields
Rab.auth('id')           // user.id
Rab.auth(['profile', 'organizationId'])  // user.profile.organizationId

// Reference request parameters
Rab.params('shopId')     // params.shopId
Rab.params(['nested', 'field'])  // params.nested.field

// Reference query parameters
Rab.query('status')      // query.status
```

## API Reference

### Rab Class

#### `Rab.schema(config)`
Creates a new permission schema.

**Parameters:**
- `config`: Record<string, RabGrant[]> - Permission configuration

**Returns:** `Rab` instance

#### `Rab.grant(role)`
Creates a new grant for a specific role.

**Parameters:**
- `role`: string - The role name

**Returns:** `RabGrant` instance

#### `getGrant(options)`
Evaluates permissions for a specific request.

**Parameters:**
- `options.permission`: string - Permission to check
- `options.role`: string - User's role
- `options.request`: object - Request context (user, params, query)
- `options.validations`: object - Custom validation functions

**Returns:** `Promise<PermissionGrant>`

### RabGrant Class

#### `ifEqual(fieldOne, fieldTwo)`
Adds an equality condition to the grant.

```typescript
Rab.grant('user').ifEqual(
  Rab.auth('organizationId'),
  Rab.params('organizationId')
)
```

#### `ifContains(fieldOne, fieldTwo)`
Adds a contains condition to the grant.

```typescript
Rab.grant('manager').ifContains(
  Rab.auth('departmentIds'),
  Rab.params('departmentId')
)
```

#### `columns(columns)`
Restricts access to specific fields.

```typescript
Rab.grant('user').columns(['name', 'email', 'createdAt'])
```

#### `filters(filters)`
Adds data filtering conditions.

```typescript
Rab.grant('user').filters({ active: true, deleted: false })
```

#### `lookupFilters(filters)`
Adds lookup-based filtering.

```typescript
Rab.grant('manager').lookupFilters({
  department: {
    id: ['departments', 'managedBy']
  }
})
```

#### `validator(method, variables)`
Adds custom validation.

```typescript
Rab.grant('user').validator('customValidation', {
  threshold: 100,
  context: 'user-action'
})
```

#### `extend(role, permission)`
Inherits permissions from another grant.

```typescript
Rab.grant('admin').extend('user', 'canReadProfile')
```

## Integration Examples

### With AtomAPI Framework

```typescript
import { Rab } from 'rab-access';

// Define permission bloc
@Injectable()
export class UserPermissionBloc implements PermissionAbstractBloc {
  getMetaData() {
    return {
      schema: Rab.schema({
        [AppAccess.canUpdateUser]: [
          Rab.grant(AppRoles.user).ifEqual(
            Rab.auth('id'),
            Rab.params('userId')
          ),
          Rab.grant(AppRoles.admin),
        ],
      }),
      validations: {
        customValidation: async (context) => {
          // Custom validation logic
          return context.user.verified === true;
        },
      },
    };
  }
}

// Use in controller
@Put('/users/:userId', {
  permission: AppAccess.canUpdateUser,
  bodySchema: updateUserSchema,
})
export class UpdateUserController implements AtomApiPut<T> {
  handler: T['request'] = async (request) => {
    // Access the resolved permission grant
    const grant = request.accessGrant;
    
    // Use grant information for business logic
    if (grant.columns) {
      // Filter response based on allowed columns
    }
    
    return this.userService.update(request.params.userId, request.body);
  };
}
```

### Custom Validation Functions

```typescript
const userPermissions = Rab.schema({
  [AppAccess.canDeleteUser]: [
    Rab.grant(AppRoles.admin).validator('canDeleteUser', {
      minAccountAge: 30,
    }),
  ],
});

// Validation function
const validations = {
  canDeleteUser: async (context) => {
    const { user, variables } = context;
    const accountAge = Date.now() - user.createdAt;
    const minAge = variables.minAccountAge * 24 * 60 * 60 * 1000;
    
    return accountAge >= minAge;
  },
};
```

## Permission Grant Response

The `getGrant` method returns a `PermissionGrant` object:

```typescript
interface PermissionGrant {
  isAuthorized: boolean;
  columns?: string[];
  filters?: Record<string, any>;
  lookupFilters?: Record<string, Record<string, string[]>>;
  // Additional grant metadata
}
```

## Advanced Usage

### Multi-level Inheritance

```typescript
const permissions = Rab.schema({
  [AppAccess.canReadBasicProfile]: [
    Rab.grant(AppRoles.user).columns(['name', 'email']),
  ],
  [AppAccess.canReadFullProfile]: [
    Rab.grant(AppRoles.admin)
      .extend(AppRoles.user, AppAccess.canReadBasicProfile)
      .columns(['*']),
  ],
});
```

### Complex Conditional Logic

```typescript
const permissions = Rab.schema({
  [AppAccess.canManageProject]: [
    Rab.grant(AppRoles.project_manager)
      .ifEqual(Rab.auth('departmentId'), Rab.params('departmentId'))
      .ifContains(Rab.auth('projectIds'), Rab.params('projectId'))
      .validator('hasActiveSubscription'),
  ],
});
```

## Error Handling

The library provides built-in error handling:

```typescript
try {
  const grant = await permissions.getGrant({
    permission: 'nonexistent',
    role: 'user',
    request: { user: {}, params: {} },
  });
} catch (error) {
  console.error('Permission evaluation failed:', error.message);
  // Grant will return { isAuthorized: false }
}
```

## Building

Run `nx build rab-access` to build the library.

## Running Tests

Run `nx test rab-access` to execute the unit tests via [Jest](https://jestjs.io).

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License.