All files index.ts

97.5% Statements 78/80
92.5% Branches 37/40
100% Functions 11/11
97.05% Lines 66/68

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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169                                                              1x 25x   25x     25x   25x   125x 125x   100x           1x 3x 1x     2x 1x   1x       1x 11x 1x     10x       20x 14x 13x 4x 4x     9x     1x           1x 5x 1x     4x 3x 1x   4x 2x   2x 1x         1x         6x 6x 1x     5x   5x 5x   1x       1x 4x       4x 1x     3x 4x 2x 1x   1x       1x       1x 3x 1x 1x     2x 1x   1x       1x 14x 2x   14x   1x   1x  
interface IUsers {
    [userId: string]: string[]
}
 
interface IRoles {
    [permissionId: string]: string[];
}
 
export interface IRolesConfig {
    roles: string[],
    permissions: string[]
}
 
export interface IMiddleware {
    (params: { userId: string; permissionId: string; }, error: () => void, success: () => void): void | Error;
}
 
export interface IRBAC {
    getUserRoles: (userId: string) => string[] | Error;
    removeUserRoles: (userId: string, role?: string[]) => void | Error;
    addUserRoles: (userId: string, role: string[]) => void | Error;
    isAllowed: (userId: string, permissionId: string) => boolean | Error;
    extendRole: (role: string, extendingRoles: string[]) => void | Error;
    middleware: IMiddleware;
}
 
export interface IConfig {
    rolesConfig: IRolesConfig[];
    debug?: boolean;
}
 
export class RBAC implements IRBAC {
    private users: IUsers = {};
    private readonly debug: boolean;
    private readonly roles: IRoles = {};
 
    constructor(config: IConfig) {
        this.debug = (typeof config.debug !== 'undefined') ? config.debug : false;
 
        this.roles = config.rolesConfig.reduce(
            (accumulator: IRoles, item: IRolesConfig): IRoles => {
                for (let role of item.roles) {
                    accumulator[role] = item.permissions;
                }
                return accumulator;
            },
            {}
        );
    }
 
    public getUserRoles(userId: string): string[] | Error {
        if (!userId) {
            return this.generateError('userId is not defined, expected 1 arguments');
        }
 
        if (this.users[userId]) {
            return this.users[userId];
        } else {
            return this.generateError(userId + ' userId is not defined, please add a user to the RBAC using addUserRoles method');
        }
    }
 
    public addUserRoles(userId: string, roles: string[]): void | Error {
        if (!userId || !roles) {
            return this.generateError('userId or roles is not defined, expected 2 arguments');
        }
 
        Iif (roles.length === 0) {
            return this.generateError('roles length is 0, expected at least 1');
        }
 
        for (let role of roles) {
            if (this.roles[role]) {
                if (this.users[userId]) {
                    if (!this.users[userId].includes(role)) {
                        this.users[userId].push(role);
                    }
                } else {
                    this.users[userId] = [role];
                }
            } else {
                return this.generateError(role + ' role is not defined in initial config');
            }
        }
    }
 
 
    public removeUserRoles(userId: string, roles?: string[]): void | Error {
        if (!userId) {
            return this.generateError('userId is not defined, expected 1 arguments');
        }
 
        if (this.users[userId]) {
            if (!roles) {
                delete this.users[userId];
            } else {
                for (let role of roles) {
                    const roleIndex = this.users[userId].indexOf(role);
 
                    if (roleIndex + 1) {
                        this.users[userId].splice(roleIndex, 1);
                    }
                }
            }
        } else {
            return this.generateError(userId + ' userId is not defined, please add a user to the RBAC using addUserRoles method');
        }
 
    }
 
    public isAllowed(userId: string, permissionId: string): boolean | Error {
        if (!userId || !permissionId) {
            return this.generateError('userId or permissionId is not defined, expected 2 arguments');
        }
 
        const user = this.users[userId];
 
        if (user) {
            return user.some(userRole => this.roles[userRole].includes(permissionId));
        } else {
            return this.generateError(userId + ' userId is nor defined, please add user to the RBAC using addUserRoles method');
        }
    }
 
    public extendRole(role: string, extendingRoles: string[]): void | Error {
        Iif (!role || !extendingRoles) {
            return this.generateError('role or extendingRoles is not defined, expected 2 arguments');
        }
 
        if (extendingRoles.length === 0) {
            return this.generateError('extendingRoles length is 0, expected at least 1');
        }
 
        if (this.roles[role]) {
            for (let extendingRole of extendingRoles) {
                if (this.roles[extendingRole]) {
                    this.roles[role] = this.roles[role].concat(this.roles[extendingRole]);
                } else {
                    return this.generateError(role + ' role is not defined in the initial config');
                }
            }
        } else {
            return this.generateError(role + ' role is not defined in the initial config');
        }
    }
 
    public middleware(params: { userId: string; permissionId: string; }, error: () => void, success: () => void): void | Error {
        if (!params || !error || !success) {
            error();
            return this.generateError('one of the incoming parameters is not defined, expected 3 arguments');
        }
 
        if (this.isAllowed(params.userId, params.permissionId)) {
            success();
        } else {
            error();
        }
    }
 
    private generateError(message: any): Error {
        if (this.debug) {
            console.warn(message);
        }
        return new Error(message);
    }
}
 
export default RBAC;