dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

acl.ts (2774B)


      1 import { AccessTokenPayload } from '@/lib/security/token'
      2 
      3 export interface ACLItem {
      4   cond: string
      5   allow: boolean
      6 }
      7 
      8 export interface ACL {
      9   [action: string]: ACLItem[]
     10 }
     11 
     12 export const ACL_ACTION_READ = 'read'
     13 export const ACL_ACTION_WRITE = 'write'
     14 export const ACL_ACTION_DELETE = 'delete'
     15 export const ACL_ACTION_MOVE = 'move'
     16 export const ACL_ACTION_MANAGE = 'manage'
     17 export const ACL_ACTION_TALK = 'talk'
     18 export const ACL_ACTION_CREATE_THREAD = 'create_thread'
     19 
     20 type ArrayOrElement<T> = T | T[]
     21 
     22 export function resolveACL (
     23   token: AccessTokenPayload | null,
     24   acl: ArrayOrElement<ACL | null | undefined> | null | undefined,
     25   action: string
     26 ): boolean {
     27   const acls = acl == null
     28     ? null
     29     : Array.isArray(acl)
     30       ? acl
     31       : [acl]
     32 
     33   if (acls == null || acls.length === 0) {
     34     return true
     35   }
     36 
     37   for (const acl of acls) {
     38     if (!resolveACLInternal(token, acl, action)) {
     39       return false
     40     }
     41   }
     42 
     43   return true
     44 }
     45 
     46 export function resolveACLInternal (
     47   token: AccessTokenPayload | null,
     48   acl: ACL | null | undefined,
     49   action: string
     50 ): boolean {
     51   const items = acl?.[action]
     52   if (items == null || items.length === 0) {
     53     return true
     54   }
     55 
     56   let allow = false
     57   for (const item of items) {
     58     allow = resolveACLItem(token, item) ?? allow
     59   }
     60 
     61   return allow
     62 }
     63 
     64 function resolveACLItem (
     65   token: AccessTokenPayload | null,
     66   item: ACLItem
     67 ): boolean | null {
     68   const [prefix, suffix] = item.cond.split(':')
     69 
     70   switch (prefix) {
     71     case 'special':
     72       return resolveSpecialACLItem(token, suffix, item)
     73 
     74     case 'user':
     75       return token?.uid === parseInt(suffix) ? item.allow : null
     76 
     77     case 'group':
     78       return token?.aclGroup?.includes(suffix) ? item.allow : null
     79 
     80     default:
     81       return null
     82   }
     83 }
     84 
     85 function resolveSpecialACLItem (token: AccessTokenPayload | null, suffix: string, item: ACLItem): boolean | null {
     86   switch (suffix) {
     87     case 'all':
     88       return item.allow
     89     case 'member':
     90       return token != null ? item.allow : null
     91     case 'anon':
     92       return token == null ? item.allow : null
     93     default:
     94       return null
     95   }
     96 }
     97 
     98 export function validateACL (acl: any): acl is ACL {
     99   if (typeof acl !== 'object' || acl == null) {
    100     return false
    101   }
    102 
    103   for (const key in acl) {
    104     if (!validateACLItems(acl[key])) {
    105       return false
    106     }
    107   }
    108 
    109   return true
    110 }
    111 
    112 function validateACLItems (aclItems: any): aclItems is ACLItem[] {
    113   if (!Array.isArray(aclItems)) {
    114     return false
    115   }
    116 
    117   for (const aclItem of aclItems) {
    118     if (!validateACLItem(aclItem)) {
    119       return false
    120     }
    121   }
    122 
    123   return true
    124 }
    125 
    126 function validateACLItem (aclItem: any): aclItem is ACLItem {
    127   return typeof aclItem === 'object' && aclItem != null &&
    128     typeof aclItem.cond === 'string' &&
    129     typeof aclItem.allow === 'boolean'
    130 }