All files index.ts

51.22% Statements 21/41
46.43% Branches 13/28
54.55% Functions 6/11
50% Lines 19/38

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    1x 1x   1x       4x 4x 4x   4x   4x                                                                     1x   1x   1x 1x     1x 1x 1x                       1x 1x     1x         1x  
import { Union } from './types';
 
export class AccessControl<T, R, P, C = unknown> {
  #roles: Union<T, R, P, C>;
  constructor(roles: Union<T, R, P, C>) {
    this.#roles = roles;
  }
 
  can(role: keyof T, action: keyof P, resource: keyof R) {
    const selectedRole = this.#roles[role as string];
    const hasResource = !!selectedRole && !!selectedRole[resource];
    const hasAction = hasResource && !!selectedRole[resource][action];
    const isActionActive =
      hasAction && !!selectedRole[resource][action].enabled;
 
    return !!selectedRole && hasResource && hasAction && isActionActive;
  }
 
  getRoles(role: keyof T, action: keyof P, resource: keyof R) {
    return (
      this.can(role, action, resource) &&
      this.#roles[role as string][resource][action]
    );
  }
 
  validate(role: keyof T, action: keyof P, resource: keyof R) {
    return async (args: unknown, context: C) => {
      const can = this.can(role, action, resource);
      if (!can) {
        return false;
      }
      const roles: {
        validators?: ((
          args: unknown,
          context: C,
        ) => boolean | Promise<boolean>)[];
      } = this.getRoles(role, action, resource);
      for (const v of roles.validators || []) {
        if (typeof v === 'function') {
          const c = await v(args, context);
          if (!c) {
            return false;
          }
        }
      }
      return true;
    };
  }
 
  filter(role: keyof T, action: keyof P, resource: keyof R) {
    const can = this.can(role, action, resource);
    const attributes =
      (this.#roles[role as string][resource][action].attributes as string[]) ||
      [];
    return async <T>(res: T | Promise<T>): Promise<T> => {
      Iif (!can) {
        return res;
      }
      let data = (await res) as T | T[];
      const attrArray = Object.keys(attributes).filter((a) => !attributes[a]);
      Iif (Array.isArray(data)) {
        data = data.map((a) => {
          for (const attr of attrArray) {
            if (Array.isArray(a[attr])) {
              a[attr] = [];
            } else {
              delete a[attr];
            }
          }
          return a;
        });
      } else {
        for (const attr of attrArray) {
          delete data[attr];
        }
      }
      return data as T;
    };
  }
}
 
export * from './types';