العديد من المشاريع لديها عمليات المصادقة (إلى درجة أو أخرى). تمت كتابة العديد من أفضل الممارسات في جميع التقنيات المعروفة ، إلخ. الخ
لكن المستخدم قدم تسجيل الدخول و؟ بعد كل شيء ، لا يستطيع أن يفعل كل شيء. كيفية تحديد ما يمكن أن يرى وما لا. الأزرار التي لها الحق في الضغط عليها ، والتي يمكن أن تغير ما لإنشاء أو حذف.
في هذه المقالة ، أود التفكير في طريقة لحل هذه المشكلات على تطبيقات الويب.

بادئ ذي بدء ، يمكن أن يحدث إذن حقيقي / فعال على الخادم فقط. في Front End ، يمكننا فقط تحسين واجهة المستخدم (هم ، اللغة الروسية قوية ، ولكن ليس دائمًا ...) ، بل هي أيضًا تجربة المستخدم خارج UX. يمكننا إخفاء الأزرار التي لا يتمتع المستخدم بحق النقر عليها أو منعه من الوصول إلى الصفحة أو إظهار رسالة بأنه لا يملك حقوقًا في هذا الإجراء أو ذاك.
وهنا السؤال الذي يطرح نفسه ، كيف نفعل ذلك بشكل صحيح قدر الإمكان.
لنبدأ بتحديد المشكلة.
أنشأنا تطبيق Todo ولديه أنواع مختلفة من المستخدمين:
USER - يمكنه رؤية جميع المهام وتغييرها (تعيين V وإزالتها) ، ولكن لا يمكن حذفها أو إنشاؤها ولا ترى إحصائيات.
ADMIN - يمكنه رؤية جميع المهام وإنشاء مهام جديدة ، لكن لا يرى إحصائيات.
SUPER_ADMIN - يمكنه رؤية جميع المهام ، وإنشاء جديد وحذف ، كما يمكنه مشاهدة الإحصاءات.
| عرض المهمة | إنشاء مهمة | فحص / إلغاء مهمة (تحديث) | حذف المهمة | عرض احصائيات |
---|
المستخدم | الخامس | اكس | الخامس | اكس | اكس |
المسؤول | الخامس | الخامس | الخامس | اكس | اكس |
SUPER_ADMIN | الخامس | الخامس | الخامس | الخامس | الخامس |
في مثل هذه الحالة ، يمكننا بسهولة الحصول على الأدوار. ومع ذلك ، يمكن أن يتغير الوضع كثيرا. تخيل أن هناك مستخدمًا يجب أن يتمتع بنفس حقوق ADMIN بالإضافة إلى حذف المهام. أو مجرد مستخدم لديه القدرة على رؤية الإحصاءات.
حل بسيط هو خلق أدوار جديدة.
ولكن على الأنظمة الكبيرة ، مع وجود عدد كبير نسبيًا من المستخدمين ، سرعان ما نضيع في عدد كبير من الأدوار ... ونذكر هنا أذونات "حقوق المستخدم". لتسهيل الإدارة ، يمكننا إنشاء مجموعات من العديد من الأذونات وإرفاقها للمستخدم. هناك دائمًا فرصة لإضافة إذن محدد لمستخدم معين.
يمكن إيجاد حلول مماثلة في العديد من الخدمات الرائعة. AWS ، Google Cloud ، SalesForce ، إلخ.
أيضا ، يتم تنفيذ حلول مماثلة بالفعل في العديد من الأطر ، على سبيل المثال ، Django (الثعبان).
أريد أن أعطي مثالا على تطبيق للتطبيقات الزاوي. (على سبيل المثال نفس تطبيق تودو).
تحتاج أولاً إلى تحديد جميع الأذونات الممكنة.
مقسمة إلى ميزات
- لدينا المهام والإحصاءات.
- نحدد الإجراءات الممكنة مع كل منهم.
- المهمة: إنشاء ، قراءة ، تحديث ، حذف
- إحصائيات: قراءة (في مثالنا ، العرض فقط)
- قم بإنشاء خريطة (MAP) للأدوار والأذونات
export const permissionsMap = { todos:{ create:'*', read:'*', update:'*', delete:'*' }, stats:'*' }
في رأيي ، هذا هو الخيار الأفضل ، لكن في معظم الحالات سيعود الخادم بشيء من هذا القبيل:
export permissions = [ 'todos_create', 'todos_read', 'todos_update', 'todos_delete', 'stas' ]
أقل قراءة ، ولكن أيضا جيدة جدا.
هذا هو ما يبدو عليه تطبيقنا إذا كان لدى المستخدم جميع الأذونات الممكنة.

لنبدأ باستخدام USER ، هذا ما تبدو عليه أذوناته:
export const USERpermissionsMap = { todos:{ read:'*', update:'*', } }
1) لا يمكن لـ USER الاطلاع على إحصاءات ، أي من حيث المبدأ ، لا يمكنه الانتقال إلى صفحة الإحصائيات.
لمثل هذه الحالات ، يحتوي Angular على حراس ، يتم تسجيلهم على مستوى الطرق ( الوثائق ).
في حالتنا ، يبدو كما يلي:
const routes: Routes = [
يجب الانتباه إلى الكائن في البيانات.
peremissions = 'stats' ، هذه هي الأذونات التي يجب أن يتمتع بها المستخدم من أجل الوصول إلى هذه الصفحة.
استنادًا إلى data.permissions المطلوبة والأذونات التي منحها لنا خادم PermissionsGuardService ، ستقرر ما إذا كنت ستسمح لـ USER بالانتقال إلى صفحة "/ الإحصائيات" أم لا.
@Injectable({ providedIn: 'root' }) export class PermissionsGuardService implements CanActivate { constructor( private store: Store<any>) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
تحتوي هذه الوظيفة على منطق القرار - هل يوجد إذن غير قابل للاستبدال (مطلوب) بين جميع أذونات USER (userPerms).
export function checkPermissions(required: string, userPerms) {

و كذلك. لا يمكن للمستخدم الوصول إلى صفحة الإحصائيات. ومع ذلك ، لا يزال بإمكانه إنشاء المهام وحذفها.
لكي لا يتمكن USER من إزالة المهمة ، ستكون كافية لإزالة علامة (X) الحمراء من سطر المهمة.
لهذا الغرض سوف نستخدم التوجيه الهيكلي .
<button *appPermissions="'todos_delete'" class="delete-button" (click)="removeSingleTodo()"> X </button>
@Directive({ selector: '[appPermissions]' }) export class PermissionsDirective { private _required: string; private _viewRef: EmbeddedViewRef<any> | null = null; private _templateRef: TemplateRef<any> | null = null; @Input() set appPermissions(permission: string) { this._required = permission; this._viewRef = null; this.init(); } constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { this._templateRef = templateRef; } init() { const isPermitted = checkPermissions(this._required, this.getPermissions()); if (isPermitted) { this._viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); } else { console.log('PERMISSIONS DIRECTIVE says \n You don\'t have permissions to see it'); } } getPermissions() { localStorage.get('userPermissions') } }

الآن لا يرى USER زر DELETE ولكن لا يزال بإمكانه إضافة مهام جديدة.
لإزالة حقل الإدخال - سوف تفسد كل نوع من طلبنا. الحل الصحيح في هذه الحالة هو disable
حقول الإدخال.
سوف نستخدم الأنابيب.
<!-- as a value `permissions` pipe will get required permissions `permissions` pipe return true or false, that's why we have !('todos_create' | permissions) to set disable=true if pipe returns false --> <input class="centered-block" [disabled]="!('todos_create' | permissions)" placeholder="What needs to be done?" autofocus/>
@Pipe({ name: 'permissions' }) export class PermissionsPipe implements PipeTransform { constructor(private store: Store<any>) { } transform(required: any, args?: any): any { const isPermitted = checkPermissions(required, this.getPermissions()); if (isPermitted) { return true; } else { console.log('[PERMISSIONS PIPE] You don\'t have permissions'); return false; } } getPermissions() { return localStorage.get('userPermissions') } }
والآن يستطيع المستخدم فقط رؤية المهام وتغييرها (V / X). ومع ذلك ، لا يزال لدينا زر واحد آخر يسمى "مسح مكتمل".
افترض أن لدينا متطلبات إدارة المنتج التالية:
- يجب أن يكون الزر "مسح مكتمل" مرئيًا للجميع ودائمًا.
- يجب أن يكون أيضًا قابل للنقر.
- إذا ضغط USER بدون الأذونات المقابلة على الزر ، فستظهر رسالة.
التوجيه البناء لن يساعدنا ، وكذلك الأنابيب.
كما أنها ليست مريحة للغاية لكتابة أذونات التدقيق في وظائف المكون.
كل ما نحتاج إليه هو إجراء فحص أذونات بين النقر وتنفيذ الوظيفة المنضمة.
في رأيي ، الأمر يستحق استخدام الديكور.
export function Permissions(required) { return (classProto, propertyKey, descriptor) => { const originalFunction = descriptor.value; descriptor.value = function (...args: any[]) { const userPerms = localStorage.get('userPermissions') const isPermitted = checkPermissions(required, userPerms); if (isPermitted) { originalFunction.apply(this, args); } else { alert('you have no permissions \n [PERMISSIONS DECORATOR]'); } }; return descriptor; }; }
@Component({ selector: 'app-actions', templateUrl: './actions.component.html', styleUrls: ['./actions.component.css'] }) export class ActionsComponent implements OnInit { @Output() deleteCompleted = new EventEmitter(); constructor() { } @Permissions('todos_delete') public deleteCompleted() { this.deleteCompleted.emit(); } }
المتحدثون يستحقون مقالا منفصلا.
النتيجة النهائية لدينا:

النتيجة:
يسمح لنا هذا النهج بتكييف UX الخاص بنا بسهولة وبشكل حيوي وفقًا للأذونات التي يتمتع بها المستخدم.
والأهم من ذلك ، نحن لسنا بحاجة إلى تحويل الخدمات إلى جميع المكونات أو المطرقة في قوالب "* ngIf".
تطبيق كامل اكتمال .
سأكون سعيدا للتعليق.