+6

RBAC - Kiểm soát Truy cập Dựa trên Vai trò với Angular Templates

Các bạn có đang triển khai Kiểm soát Truy cập Dựa trên Vai trò (Role Based Access Control - RBAC) trong Angular templates không? Một cách để làm điều này là sử dụng *ngIf, nhưng mình không chọn cách đó đâu, vì nó sẽ bao gồm các hàm tùy chỉnh trong template của Angular và sau này rất khó bảo trì. Cách đúng để làm là sử dụng các directive trong Angular 🚀.

RBAC là gì?

Kiểm soát truy cập dựa trên vai trò (Role-based access control - RBAC) đề cập đến ý tưởng gán quyền truy cập cho người dùng dựa trên vai trò của họ trong tổ chức. Nó mang lại một phương pháp đơn giản, dễ quản lý cho việc quản lý truy cập, tránh sai sót hơn so với việc gán quyền truy cập cho từng người dùng một cách riêng lẻ.

Triển khai

Giả sử chúng ta có một interface tài khoản với 3 thuộc tính: id, nameroles. Các roles là một mảng của enum types, đại diện cho các vai trò khác nhau mà tài khoản của chúng ta có thể có, bao gồm ADMIN, USER, EDITOR, VIEWER.

export interface IAccount {
    id: number;
    name: string;
    roles: ERoles[]
}

export enum ERoles {
    admin = 'ADMIN',
    user = 'USER',
    editor = 'EDITOR',
    viewer = 'VIEWER'
}

Thay vì sử dụng *ngIf, chúng ta muốn tạo một directive tùy chỉnh, chấp nhận các roles mà template sẽ được hiển thị và tài khoản mà các roles có thể được kiểm tra.

import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { ERoles, IAccount } from '../../interfaces/account.interface';

@Directive({
    selector: '[hasRoles]'
})
export class HasRolesDirective implements OnChanges {
    private visible: boolean;
    private roles: ERoles[];
    private account: IAccount;

    @Input() set hasRoles(roles: ERoles[]) {
        this.roles = roles;
    }

    @Input('hasRolesFor') set hasRolesFor(account: IAccount) {
        this.account = account;
    };

    constructor(private templateRef: TemplateRef<unknown>, private viewContainer: ViewContainerRef) {}

    ngOnChanges(): void {
        if (!this.roles?.length || !this.account) {
            return;
        }

        if (this.visible) {
            return;
        }

        // kiểm tra xem vai trò tài khoản có bao gồm ít nhất một trong các vai trò đã thiết lập không
        if (this.account.roles.some(role => this.roles.includes(role))) {
            this.viewContainer.clear();
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.visible = true;

            return;
        }

        this.viewContainer.clear();
        this.visible = false;
    }

}

Cách sử dụng?

Đầu tiên, trong file typescript của component, bạn cần có tài khoản và mảng roles (để đảm bảo kiểu dữ liệu an toàn), như ví dụ dưới đây:

account: IAccount = {
    id: 1,
    name: 'Klajdi',
    roles: [ERoles.editor],
};

roles: typeof ERoles = ERoles;

Sau đó, bạn có thể sử dụng nó trong thẻ HTML của bất kỳ template nào bạn muốn. Dưới đây là hai ví dụ:

<div *hasRoles="[roles.editor]; for: account">Editor only</div>
<div *hasRoles="[roles.user, roles.viewer]; for: account">User and viewer only</div>

Và đây là kết quả hiển thị trên trình duyệt:

image.png

Vì mình chỉ gán vai trò EDITOR cho tài khoản của mình, nên chỉ có thể thấy được div dành cho Editor 🚀🚀.

Directive này có thể mở rộng cho bất kỳ logic nào bạn muốn áp dụng. Bạn cũng có thể viết unit tests để đảm bảo rằng các new member không làm hỏng logic khi viết code mới.😀


English Version

Are you currently implementing Role-Based Access Control (RBAC) in Angular templates? One way to do this is by using *ngIf, but I don't recommend that approach because it would involve custom functions in the Angular template, making it difficult to maintain in the future. The correct way to do it is by using directives in Angular. 🚀

What is RBAC?

Role-Based Access Control (RBAC) refers to the idea of assigning access rights to users based on their roles within the organization. It provides a simple and manageable approach to access control, avoiding more errors compared to assigning access rights to individual users separately.

Implementation

Let's assume we have an account interface with three properties: id, name, and roles. Roles are an array of enum types, representing different roles that our account can have, including ADMIN, USER, EDITOR, and VIEWER.

export interface IAccount {
    id: number;
    name: string;
    roles: ERoles[];
}

export enum ERoles {
    admin = 'ADMIN',
    user = 'USER',
    editor = 'EDITOR',
    viewer = 'VIEWER'
}

Instead of using *ngIf, we want to create a custom directive that accepts the roles for which the template will be displayed and the account against which the roles can be checked.

import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { ERoles, IAccount } from '../../interfaces/account.interface';

@Directive({
    selector: '[hasRoles]'
})
export class HasRolesDirective implements OnChanges {
    private visible: boolean;
    private roles: ERoles[];
    private account: IAccount;

    @Input() set hasRoles(roles: ERoles[]) {
        this.roles = roles;
    }

    @Input('hasRolesFor') set hasRolesFor(account: IAccount) {
        this.account = account;
    };

    constructor(private templateRef: TemplateRef<unknown>, private viewContainer: ViewContainerRef) {}

    ngOnChanges(): void {
        if (!this.roles?.length || !this.account) {
            return;
        }

        if (this.visible) {
            return;
        }

        // Check if the account's roles include at least one of the set roles
        if (this.account.roles.some(role => this.roles.includes(role))) {
            this.viewContainer.clear();
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.visible = true;

            return;
        }

        this.viewContainer.clear();
        this.visible = false;
    }

}

How to use it?

First, in the component's TypeScript file, you need to have an account and an array of roles (to ensure type safety), as shown in the example below:

account: IAccount = {
    id: 1,
    name: 'Klajdi',
    roles: [ERoles.editor],
};

roles: typeof ERoles = ERoles;

Then, you can use it in the HTML tag of any template you want. Here are two examples:

<div *hasRoles="[roles.editor]; hasRolesFor: account">Editor only</div>
<div *hasRoles="[roles.user, roles.viewer]; hasRolesFor: account">User and viewer only</div>

And here's the result displayed in the browser:

image.png

Since I only assigned the EDITOR role to my account, I can only see the div intended for Editors. 🚀🚀

This directive can be extended for any logic you want to apply. You can also write unit tests to ensure that new members don't break the logic when writing new code. 😀


日本語版

ベースのアクセス制御(RBAC)をAngularテンプレートで実装していますか?一つの方法は*ngIfを使用することですが、将来のメンテナンスが困難になる可能性があるため、このアプローチはお勧めしません。正しい方法は、Angularでディレクティブを使用することです。🚀

RBACとは?

ロールベースのアクセス制御(RBAC)とは、組織内のユーザーに対して役割に基づいてアクセス権限を割り当てる考え方を指します。これは、アクセス制御において個々のユーザーにアクセス権限を個別に割り当てるよりも、シンプルで管理しやすいアプローチを提供し、エラーの発生を回避します。

実装方法

まず、idnamerolesの3つのプロパティを持つアカウントインターフェースがあると仮定しましょう。Rolesは、ADMINUSEREDITORVIEWERを含む、アカウントが持つさまざまな役割を表す列挙型の配列です。

export interface IAccount {
    id: number;
    name: string;
    roles: ERoles[];
}

export enum ERoles {
    admin = 'ADMIN',
    user = 'USER',
    editor = 'EDITOR',
    viewer = 'VIEWER'
}

*ngIfの代わりに、テンプレートを表示するための役割と役割をチェックするアカウントを受け入れるカスタムディレクティブを作成したいと思います。

import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { ERoles, IAccount } from '../../interfaces/account.interface';

@Directive({
    selector: '[hasRoles]'
})
export class HasRolesDirective implements OnChanges {
    private visible: boolean;
    private roles: ERoles[];
    private account: IAccount;

    @Input() set hasRoles(roles: ERoles[]) {
        this.roles = roles;
    }

    @Input('hasRolesFor') set hasRolesFor(account: IAccount) {
        this.account = account;
    };

    constructor(private templateRef: TemplateRef<unknown>, private viewContainer: ViewContainerRef) {}

    ngOnChanges(): void {
        if (!this.roles?.length || !this.account) {
            return;
        }

        if (this.visible) {
            return;
        }

        // アカウントのロールがセットされたロールのいずれかを含んでいるかをチェックします
        if (this.account.roles.some(role => this.roles.includes(role))) {
            this.viewContainer.clear();
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.visible = true;

            return;
        }

        this.viewContainer.clear();
        this.visible = false;
    }

}

使い方

まず、コンポーネントのTypeScriptファイルで、以下の例のようにアカウントと役割の配列(Typeの安全性を確保するため)を持つ必要があります。

account: IAccount = {
    id: 1,
    name: 'Klajdi',
    roles: [ERoles.editor],
};

roles: typeof ERoles = ERoles;

次に、任意のテンプレートのHTMLタグ内でそれを使用することができます。以下に2つの例を示します。

<div *hasRoles="[roles.editor]; hasRolesFor: account">Editor only</div>
<div *hasRoles="[roles.user, roles.viewer]; hasRolesFor: account">User and viewer only</div>

そして、ブラウザで表示される結果は次のとおりです。

image.png

私のアカウントにはEDITORの役割しか割り当てられていないため、エディター専用のdivしか表示されません。🚀🚀

このディレクティブは、適用する任意のロジックに拡張することができます。また、新しいコードを書く際に新しいメンバーがロジックを壊さないようにするために、ユニットテストを書くこともできます。😀

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí