bknd-assign-permissions

bknd-assign-permissions

Use when assigning permissions to roles in Bknd. Covers permission syntax (simple strings, extended format), permission effects (allow/deny), policies with conditions, entity-specific permissions, and fine-grained access control patterns.

1bintang
0fork
Diperbarui 1/21/2026
SKILL.md
readonlyread-only
name
bknd-assign-permissions
description

Use when assigning permissions to roles in Bknd. Covers permission syntax (simple strings, extended format), permission effects (allow/deny), policies with conditions, entity-specific permissions, and fine-grained access control patterns.

Assign Permissions

Configure detailed permissions for roles using simple strings, extended format with effects, and conditional policies.

Prerequisites

  • Bknd project with code-first configuration
  • Auth enabled (auth: { enabled: true })
  • Guard enabled (guard: { enabled: true })
  • At least one role defined (see bknd-create-role)

When to Use UI Mode

  • Viewing current role permissions
  • Quick permission checks

UI steps: Admin Panel > Auth > Roles > Select role

Note: Permission assignment requires code mode. UI is read-only.

When to Use Code Mode

  • Assigning permissions to roles
  • Adding permission effects (allow/deny)
  • Creating conditional policies
  • Entity-specific permission rules

Code Approach

Step 1: Simple Permission Strings

Assign basic permissions as string array:

import { serve } from "bknd/adapter/bun";
import { em, entity, text } from "bknd";

const schema = em({
  posts: entity("posts", { title: text().required() }),
});

serve({
  connection: { url: "file:data.db" },
  config: {
    data: schema.toJSON(),
    auth: {
      enabled: true,
      guard: { enabled: true },
      roles: {
        editor: {
          implicit_allow: false,
          permissions: [
            "data.entity.read",    // Read any entity
            "data.entity.create",  // Create in any entity
            "data.entity.update",  // Update any entity
            // No delete permission
          ],
        },
      },
    },
  },
});

Available Permissions

Permission Filterable Description
data.entity.read Yes Read entity records
data.entity.create Yes Create new records
data.entity.update Yes Update existing records
data.entity.delete Yes Delete records
data.database.sync No Sync database schema
data.raw.query No Execute raw SELECT
data.raw.mutate No Execute raw INSERT/UPDATE/DELETE

Filterable means you can add conditions/filters via policies.

Step 2: Extended Permission Format

Use objects for explicit allow/deny effects:

{
  roles: {
    moderator: {
      implicit_allow: false,
      permissions: [
        { permission: "data.entity.read", effect: "allow" },
        { permission: "data.entity.update", effect: "allow" },
        { permission: "data.entity.delete", effect: "deny" },  // Explicit deny
      ],
    },
  },
}

Permission Effects

Effect Description
allow Grant the permission (default)
deny Explicitly block the permission

Deny overrides allow - useful when implicit_allow: true but you want to block specific actions.

Step 3: Conditional Policies

Add policies for fine-grained control:

{
  roles: {
    content_editor: {
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            {
              description: "Only read posts and comments",
              condition: { entity: { $in: ["posts", "comments"] } },
              effect: "allow",
            },
          ],
        },
        {
          permission: "data.entity.create",
          effect: "allow",
          policies: [
            {
              condition: { entity: { $in: ["posts", "comments"] } },
              effect: "allow",
            },
          ],
        },
      ],
    },
  },
}

Policy Structure

{
  description?: string,      // Human-readable (optional)
  condition?: ObjectQuery,   // When policy applies
  effect: "allow" | "deny" | "filter",
  filter?: ObjectQuery,      // Row filter (for effect: "filter")
}

Policy Effects

Effect Purpose
allow Grant when condition met
deny Block when condition met
filter Apply row-level filter to results

Condition Operators

Operator Description Example
$eq Equal { entity: { $eq: "posts" } }
$ne Not equal { entity: { $ne: "users" } }
$in In array { entity: { $in: ["posts", "comments"] } }
$nin Not in array { entity: { $nin: ["users", "secrets"] } }
$gt Greater than { age: { $gt: 18 } }
$gte Greater or equal { level: { $gte: 5 } }
$lt Less than { count: { $lt: 100 } }
$lte Less or equal { priority: { $lte: 3 } }

Step 4: Variable Placeholders

Reference runtime context with @variable:

Placeholder Description
@user.id Current user's ID
@user.email Current user's email
@user.role Current user's role
@entity Current entity name
@id Current record ID

Example - user can only update their own profile:

{
  permissions: [
    {
      permission: "data.entity.update",
      effect: "allow",
      policies: [
        {
          condition: { entity: "users", "@id": "@user.id" },
          effect: "allow",
        },
      ],
    },
  ],
}

Step 5: Entity-Specific Permissions

Grant different permissions per entity:

{
  roles: {
    blog_author: {
      implicit_allow: false,
      permissions: [
        // Full CRUD on posts
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{ condition: { entity: "posts" }, effect: "allow" }],
        },
        {
          permission: "data.entity.create",
          effect: "allow",
          policies: [{ condition: { entity: "posts" }, effect: "allow" }],
        },
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{ condition: { entity: "posts" }, effect: "allow" }],
        },
        {
          permission: "data.entity.delete",
          effect: "allow",
          policies: [{ condition: { entity: "posts" }, effect: "allow" }],
        },

        // Read-only on categories
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{ condition: { entity: "categories" }, effect: "allow" }],
        },
      ],
    },
  },
}

Common Patterns

Read-Only Role

{
  roles: {
    viewer: {
      implicit_allow: false,
      permissions: ["data.entity.read"],
    },
  },
}

CRUD Without Delete

{
  roles: {
    contributor: {
      implicit_allow: false,
      permissions: [
        "data.entity.read",
        "data.entity.create",
        "data.entity.update",
        { permission: "data.entity.delete", effect: "deny" },
      ],
    },
  },
}

Admin with Restricted Raw Access

{
  roles: {
    admin: {
      implicit_allow: true,  // Allow all by default
      permissions: [
        // But deny raw database access
        { permission: "data.raw.query", effect: "deny" },
        { permission: "data.raw.mutate", effect: "deny" },
      ],
    },
  },
}

Multi-Entity Role

{
  roles: {
    content_manager: {
      implicit_allow: false,
      permissions: [
        // Content entities: full CRUD
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
            effect: "allow",
          }],
        },
        {
          permission: "data.entity.create",
          effect: "allow",
          policies: [{
            condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
            effect: "allow",
          }],
        },
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{
            condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
            effect: "allow",
          }],
        },
        {
          permission: "data.entity.delete",
          effect: "allow",
          policies: [{
            condition: { entity: { $in: ["posts", "pages", "comments"] } },  // No media delete
            effect: "allow",
          }],
        },
      ],
    },
  },
}

Deny Specific Entity

{
  roles: {
    user: {
      implicit_allow: false,
      permissions: [
        // Can read most entities
        "data.entity.read",
        // But never access secrets entity
        {
          permission: "data.entity.read",
          effect: "deny",
          policies: [{
            condition: { entity: "secrets" },
            effect: "deny",
          }],
        },
      ],
    },
  },
}

Create Helper Function

For complex role definitions:

// helpers/permissions.ts
type EntityPermission = "read" | "create" | "update" | "delete";

function entityPermissions(
  entities: string[],
  actions: EntityPermission[]
) {
  const permMap: Record<EntityPermission, string> = {
    read: "data.entity.read",
    create: "data.entity.create",
    update: "data.entity.update",
    delete: "data.entity.delete",
  };

  return actions.map((action) => ({
    permission: permMap[action],
    effect: "allow" as const,
    policies: [{
      condition: { entity: { $in: entities } },
      effect: "allow" as const,
    }],
  }));
}

// Usage
{
  roles: {
    blog_author: {
      implicit_allow: false,
      permissions: [
        ...entityPermissions(["posts", "comments"], ["read", "create", "update"]),
        ...entityPermissions(["categories", "tags"], ["read"]),
      ],
    },
  },
}

Verification

Test permission assignments:

1. Login as user with role:

curl -X POST http://localhost:7654/api/auth/password/login \
  -H "Content-Type: application/json" \
  -d '{"email": "editor@example.com", "password": "password123"}'

2. Test allowed permission:

curl http://localhost:7654/api/data/posts \
  -H "Authorization: Bearer <token>"
# Should return 200 with data

3. Test denied permission:

curl -X DELETE http://localhost:7654/api/data/posts/1 \
  -H "Authorization: Bearer <token>"
# Should return 403 Forbidden

4. Test entity-specific permission:

# If only posts/comments allowed:
curl http://localhost:7654/api/data/users \
  -H "Authorization: Bearer <token>"
# Should return 403 if users entity not in allowed list

Common Pitfalls

Permission Not Taking Effect

Problem: Changed permissions but old behavior persists

Fix: Restart server - role config is loaded at startup:

# Stop and restart
bknd run

Deny Not Overriding

Problem: Deny effect not blocking access

Fix: Check policy condition - deny only applies when condition matches:

// WRONG - no condition, may not match
{ permission: "data.entity.delete", effect: "deny" }

// CORRECT - simple deny at permission level
{
  permissions: [
    "data.entity.read",
    "data.entity.create",
    // Don't include delete at all
  ],
}

Entity Condition Not Matching

Problem: Entity-specific permission not working

Fix: Verify entity name matches exactly:

// WRONG - entity name case matters
{ condition: { entity: "Posts" } }

// CORRECT - use exact entity name
{ condition: { entity: "posts" } }

Multiple Policies Conflict

Problem: Confusing behavior with multiple policies

Fix: Understand evaluation order - first matching policy wins:

{
  policies: [
    // More specific first
    { condition: { entity: "secrets" }, effect: "deny" },
    // General fallback last
    { effect: "allow" },
  ],
}

Variable Placeholder Not Resolving

Problem: @user.id appearing literally in filter

Fix: Variables only work in filter and condition fields:

// CORRECT usage
{
  condition: { "@id": "@user.id" },  // Works
  filter: { user_id: "@user.id" },   // Works
}

DOs and DON'Ts

DO:

  • Start with minimal permissions, add as needed
  • Use $in operator for multiple entities
  • Test each permission after adding
  • Use descriptive policy descriptions
  • Prefer explicit permissions over implicit_allow

DON'T:

  • Grant data.raw.* to non-admin roles (SQL injection risk)
  • Use implicit_allow: true with deny policies (confusing)
  • Forget to restart server after config changes
  • Mix simple strings and extended format unnecessarily
  • Over-complicate with too many nested policies

Related Skills

  • bknd-create-role - Define new roles
  • bknd-row-level-security - Filter data by user ownership
  • bknd-protect-endpoint - Secure specific endpoints
  • bknd-public-vs-auth - Configure public vs authenticated access
  • bknd-setup-auth - Initialize authentication system

You Might Also Like

Related Skills

coding-agent

coding-agent

179Kdev-codegen

Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control.

openclaw avataropenclaw
Ambil
add-uint-support

add-uint-support

97Kdev-codegen

Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.

pytorch avatarpytorch
Ambil
at-dispatch-v2

at-dispatch-v2

97Kdev-codegen

Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.

pytorch avatarpytorch
Ambil
skill-writer

skill-writer

97Kdev-codegen

Guide users through creating Agent Skills for Claude Code. Use when the user wants to create, write, author, or design a new Skill, or needs help with SKILL.md files, frontmatter, or skill structure.

pytorch avatarpytorch
Ambil

Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.

oven-sh avataroven-sh
Ambil

Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.

oven-sh avataroven-sh
Ambil