Back to list
cameronapak

bknd-assign-permissions

by cameronapak

A no-build, un-bloated stack built upon Web Standards that feels freeing to use and can be deployed anywhere.

23🍴 2📅 Jan 21, 2026

SKILL.md


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

PermissionFilterableDescription
data.entity.readYesRead entity records
data.entity.createYesCreate new records
data.entity.updateYesUpdate existing records
data.entity.deleteYesDelete records
data.database.syncNoSync database schema
data.raw.queryNoExecute raw SELECT
data.raw.mutateNoExecute 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

EffectDescription
allowGrant the permission (default)
denyExplicitly 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

EffectPurpose
allowGrant when condition met
denyBlock when condition met
filterApply row-level filter to results

Condition Operators

OperatorDescriptionExample
$eqEqual{ entity: { $eq: "posts" } }
$neNot equal{ entity: { $ne: "users" } }
$inIn array{ entity: { $in: ["posts", "comments"] } }
$ninNot in array{ entity: { $nin: ["users", "secrets"] } }
$gtGreater than{ age: { $gt: 18 } }
$gteGreater or equal{ level: { $gte: 5 } }
$ltLess than{ count: { $lt: 100 } }
$lteLess or equal{ priority: { $lte: 3 } }

Step 4: Variable Placeholders

Reference runtime context with @variable:

PlaceholderDescription
@user.idCurrent user's ID
@user.emailCurrent user's email
@user.roleCurrent user's role
@entityCurrent entity name
@idCurrent 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
  • 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

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon