All files / web/src/lib/auth casbin-adapter.ts

21.21% Statements 21/99
100% Branches 0/0
0% Functions 0/5
21.21% Lines 21/99

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 1001x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                 1x 1x                                                   1x 1x                     1x 1x                                 1x 1x                                       1x  
import { type Adapter, Helper, type Model } from 'casbin'
import { eq } from 'drizzle-orm'
import { db, schema } from '@/db'
 
/**
 * Custom Casbin adapter backed by Drizzle ORM.
 *
 * Reads and writes Casbin policies to the casbin_rules SQLite table.
 * Avoids the fragile casbin-drizzle-adapter package (68 downloads/week).
 */
export class DrizzleCasbinAdapter implements Adapter {
  async loadPolicy(model: Model): Promise<void> {
    const rules = await db.select().from(schema.casbinRules).all()

    for (const rule of rules) {
      const values = [rule.v0, rule.v1, rule.v2, rule.v3, rule.v4, rule.v5].filter((v) => v !== '')
      const line = `${rule.ptype}, ${values.join(', ')}`
      Helper.loadPolicyLine(line, model)
    }
  }
 
  async savePolicy(model: Model): Promise<boolean> {
    // Clear existing rules
    await db.delete(schema.casbinRules)

    // Save all policy rules
    // model.model is a Map<string, Map<string, Assertion>>, not a plain object
    const astMap = model.model as Map<string, Map<string, { policy: string[][] }>>
    for (const [, sectionMap] of astMap) {
      for (const [key, assertion] of sectionMap) {
        if (!assertion.policy) continue
        for (const rule of assertion.policy) {
          await db.insert(schema.casbinRules).values({
            ptype: key,
            v0: rule[0] ?? '',
            v1: rule[1] ?? '',
            v2: rule[2] ?? '',
            v3: rule[3] ?? '',
            v4: rule[4] ?? '',
            v5: rule[5] ?? '',
          })
        }
      }
    }

    return true
  }
 
  async addPolicy(_sec: string, ptype: string, rule: string[]): Promise<void> {
    await db.insert(schema.casbinRules).values({
      ptype,
      v0: rule[0] ?? '',
      v1: rule[1] ?? '',
      v2: rule[2] ?? '',
      v3: rule[3] ?? '',
      v4: rule[4] ?? '',
      v5: rule[5] ?? '',
    })
  }
 
  async removePolicy(_sec: string, ptype: string, rule: string[]): Promise<void> {
    // Build conditions for matching
    const rows = await db
      .select()
      .from(schema.casbinRules)
      .where(eq(schema.casbinRules.ptype, ptype))
      .all()

    for (const row of rows) {
      const values = [row.v0, row.v1, row.v2, row.v3, row.v4, row.v5]
      const matches = rule.every((v, i) => values[i] === v)
      if (matches) {
        await db.delete(schema.casbinRules).where(eq(schema.casbinRules.id, row.id))
        return
      }
    }
  }
 
  async removeFilteredPolicy(
    _sec: string,
    ptype: string,
    fieldIndex: number,
    ...fieldValues: string[]
  ): Promise<void> {
    const rows = await db
      .select()
      .from(schema.casbinRules)
      .where(eq(schema.casbinRules.ptype, ptype))
      .all()

    for (const row of rows) {
      const values = [row.v0, row.v1, row.v2, row.v3, row.v4, row.v5]
      const matches = fieldValues.every((fv, i) => fv === '' || values[fieldIndex + i] === fv)
      if (matches) {
        await db.delete(schema.casbinRules).where(eq(schema.casbinRules.id, row.id))
      }
    }
  }
}