Muchos proyectos tienen procesos de autenticación (en un grado u otro). Se han escrito muchas mejores prácticas en todas las tecnologías conocidas, etc. etc.
Pero el usuario hizo un inicio de sesión y? Después de todo, él no puede hacer todo. Cómo determinar qué puede ver y qué no. Qué botones tiene derecho a presionar, lo que puede cambiar qué crear o eliminar.
En este artículo quiero considerar un enfoque para resolver estos problemas en aplicaciones web.

Para empezar, la autorización real / efectiva solo puede ocurrir en el servidor. En Front End, solo podemos mejorar la interfaz de usuario (hmm, el idioma ruso es poderoso, pero no siempre ...), también es la experiencia del usuario más allá de UX. Podemos ocultar los botones en los que el usuario no tiene derecho a presionar, o evitar que acceda a la página, o mostrar un mensaje de que no tiene derechos para esta o aquella acción.
Y aquí surge la pregunta, cómo hacerlo tan correctamente como sea posible.
Comencemos definiendo el problema.
Creamos la aplicación Todo y tiene diferentes tipos de usuarios:
USUARIO : puede ver todas las tareas y cambiarlas (establecer y eliminar V), pero no puede eliminar ni crear y no ve estadísticas.
ADMINISTRADOR : puede ver todas las tareas y crear otras nuevas, pero no ve estadísticas.
SUPER_ADMIN : puede ver todas las tareas, crear nuevas y eliminar, también puede ver estadísticas.
| ver tarea | Crear tarea | marcar / desmarcar tarea (actualizar) | eliminar tarea | ver estadísticas |
---|
Usuario | V | X | V | X | X |
Administrador | V | V | V | X | X |
SUPER_ADMIN | V | V | V | V | V |
En tal situación, podemos pasar fácilmente con los roles. Sin embargo, la situación puede cambiar mucho. Imagine que hay un usuario que debe tener los mismos derechos que ADMIN además de eliminar tareas. O simplemente USUARIO con la capacidad de ver estadísticas.
Una solución simple es crear nuevos roles.
Pero en sistemas grandes, con un número relativamente grande de usuarios, nos perdemos rápidamente en una gran cantidad de roles ... Y aquí recordamos los permisos de "derechos de usuario". Para una administración más fácil, podemos crear grupos de varios permisos y adjuntarlos al usuario. Siempre existe la oportunidad de agregar permisos específicos a un usuario específico.
Se pueden encontrar soluciones similares en muchos servicios excelentes. AWS, Google Cloud, SalesForce, etc.
Además, soluciones similares ya están implementadas en muchos marcos, por ejemplo, Django (python).
Quiero dar un ejemplo de implementación para aplicaciones angulares. (En el ejemplo de la misma aplicación ToDo).
Primero debe determinar todos los permisos posibles.
Dividido en características
- Tenemos tareas y estadísticas.
- Determinamos las posibles acciones con cada uno de ellos.
- Tarea: crear, leer, actualizar, eliminar
- Estadísticas: lectura (en nuestro ejemplo, solo lectura)
- Crear un mapa (MAP) de roles y permisos
export const permissionsMap = { todos:{ create:'*', read:'*', update:'*', delete:'*' }, stats:'*' }
En mi opinión, esta es la mejor opción, pero en la mayoría de los casos el servidor devolverá algo como esto:
export permissions = [ 'todos_create', 'todos_read', 'todos_update', 'todos_delete', 'stas' ]
Menos legible, pero también muy bueno.
Así es como se ve nuestra aplicación si el usuario tiene todos los permisos posibles.

Comencemos con USER, así es como se ven sus permisos:
export const USERpermissionsMap = { todos:{ read:'*', update:'*', } }
1) El USUARIO no puede ver las estadísticas, es decir, en principio, no puede ir a la página de estadísticas.
Para tales situaciones, Angular tiene Guardias, que están registrados en el nivel de Rutas ( documentación ).
En nuestro caso, se ve así:
const routes: Routes = [
Debe prestar atención al objeto en los datos.
peremissions = 'stats', estos son los permisos que el usuario debe tener para tener acceso a esta página.
Según los permisos de datos requeridos y los permisos que nos ha otorgado el servidor PermissionsGuardService, decidirá si el USUARIO debe ir a la página '/ stats'.
@Injectable({ providedIn: 'root' }) export class PermissionsGuardService implements CanActivate { constructor( private store: Store<any>) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
Esta función contiene la lógica de decisión: ¿existe un permiso irremplazable (requerido) entre todos los permisos del USUARIO (userPerms).
export function checkPermissions(required: string, userPerms) {

Y asi. Nuestro USUARIO no puede acceder a la página de estadísticas. Sin embargo, aún puede crear tareas y eliminarlas.
Para que el USUARIO no pueda eliminar la tarea, será suficiente eliminar el rojo (X) de la línea de la tarea.
Para ello utilizaremos la Directiva estructural .
<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') } }

Ahora el USUARIO no ve el botón BORRAR pero aún puede agregar nuevas tareas.
Para eliminar un campo de entrada, estropeará todo tipo de nuestra aplicación. La solución correcta en esta situación es disable
los campos de entrada.
Usaremos pipa.
<!-- 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') } }
Y ahora USER solo puede ver tareas y cambiarlas (V / X). Sin embargo, todavía tenemos un botón más llamado 'Borrar completado'.
Supongamos que tenemos los siguientes requisitos de Product Manager:
- El botón 'Borrar completado' debe estar visible para todos y siempre.
- También debe ser cliqueable.
- Si el USUARIO sin los permisos correspondientes presiona el botón, debería aparecer un mensaje.
La directiva de construcción no nos ayudará, al igual que la tubería.
Tampoco es muy conveniente escribir permisos de verificación en funciones de componentes.
Todo lo que necesitamos es realizar una verificación de permisos entre el clic y la ejecución de la función enlazada.
En mi opinión, vale la pena usar decoradores.
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(); } }
Los oradores merecen un artículo separado.
Nuestro resultado final:

El resultado:
Este enfoque nos permite adaptar fácil y dinámicamente nuestra experiencia de usuario de acuerdo con los permisos que tiene el usuario.
Y lo más importante, para esto no necesitamos insertar servicios en todos los componentes o martillar en las plantillas '* ngIf'.
Toda la solicitud está completa .
Estaré encantado de comentar.