npm package: @laqus/platform-auth-angular
Description: Biblioteca Angular para autenticação e autorização com o Laqus Platform
Author: Laqus
Latest version: 1.0.1

# Laqus Platform Auth Angular

Este guia explica como integrar e utilizar as bibliotecas `@laqus/platform-auth-angular` e `@laqus/auth-permissions` em aplicações Angular.

## Instalação

```bash
# Instalar a biblioteca de autenticação para Angular
npm install @laqus/platform-auth-angular --save

# Instalar a biblioteca de permissões
npm install @laqus/auth-permissions --save
```

## Configuração Básica

### 1. Configurar o Módulo de Autenticação

No seu `app.module.ts` ou em um módulo específico de autenticação:

```typescript
import { NgModule } from '@angular/core';
import { PlatformAuthModule } from '@laqus/platform-auth-angular';
import { AUTH_CONFIG } from '@laqus/platform-auth-angular';

@NgModule({
  imports: [
    // ... outros módulos
    PlatformAuthModule
  ],
  providers: [
    {
      provide: AUTH_CONFIG,
      useValue: {
        authMiddlewareApiUrlBase: 'https://sua-api-auth.com',
        cache: {
          authCacheKeysPrefix: 'app_auth_',
          expirationTimeInMs: 1800000 // 30 minutos em milissegundos (opcional, padrão: 30 minutos)
        }
      }
    }
  ]
})
export class AppModule { }
```

### 2. Inicialização Automática

A biblioteca `@laqus/platform-auth-angular` inicializa automaticamente o serviço de autenticação quando é carregada, verificando se existe um token salvo no localStorage.

## Uso do AuthService

### Login

```typescript
import { Component } from '@angular/core';
import { AuthService } from '@laqus/platform-auth-angular';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  template: `...`
})
export class LoginComponent {
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  login(username: string, password: string) {
    this.authService.loginWithCredentials(username, password).subscribe({
      next: (result) => {
        if (result.success) {
          this.router.navigate(['/dashboard']);
        }
      },
      error: (error) => {
        console.error('Login failed', error);
        // Tratar erro de login
      }
    });
  }
}
```

### Verificar Autenticação

```typescript
import { Component, OnInit } from '@angular/core';
import { AuthService } from '@laqus/platform-auth-angular';
import { Router } from '@angular/router';

@Component({
  selector: 'app-navbar',
  template: `
    <nav>
      <div *ngIf="isAuthenticated">
        Bem-vindo, {{ username }}
        <button (click)="logout()">Sair</button>
      </div>
      <div *ngIf="!isAuthenticated">
        <a routerLink="/login">Login</a>
      </div>
    </nav>
  `
})
export class NavbarComponent implements OnInit {
  isAuthenticated = false;
  username = '';
  
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}
  
  ngOnInit() {
    // Verificar status de autenticação inicial
    this.authService.checkAuthStatus().subscribe();
    
    // Inscrever-se para mudanças no status de autenticação
    this.authService.isAuthenticated$.subscribe(isAuth => {
      this.isAuthenticated = isAuth;
    });
    
    // Inscrever-se para mudanças no usuário atual
    this.authService.currentUser$.subscribe(user => {
      this.username = user?.name || '';
    });
  }

  logout(): void {
    this.authService.logout().subscribe(() => {
      this.router.navigate(['/login']);
    });
  }
}
```

## Verificação de Permissões

### Usando o AuthService para Verificar Permissões

```typescript
import { Component, OnInit } from '@angular/core';
import { AuthService } from '@laqus/platform-auth-angular';
import { IResourceAction } from '@laqus/platform-auth-angular';
import { PlatformActions } from '@laqus/auth-permissions';

@Component({
  selector: 'app-user-management',
  template: `
    <div *ngIf="canListUsers">
      <!-- Conteúdo visível apenas para usuários com permissão -->
      <button *ngIf="canCreateUsers" (click)="createUser()">Novo Usuário</button>
    </div>
  `
})
export class UserManagementComponent implements OnInit {
  canListUsers = false;
  canCreateUsers = false;

  constructor(private authService: AuthService) {}

  ngOnInit() {
    // Verificar permissões específicas
    this.canListUsers = this.authService.hasAction(
      PlatformActions.PlatformManagement.Tenant.User.Users.List
    );
    
    this.canCreateUsers = this.authService.hasAction(
      PlatformActions.PlatformManagement.Tenant.User.Users.Create
    );
    
    // Verificar múltiplas permissões
    const hasAllPermissions = this.authService.hasAllActions([
      PlatformActions.PlatformManagement.Tenant.User.Users.List,
      PlatformActions.PlatformManagement.Tenant.User.Users.Create
    ]);
    
    // Verificar se tem pelo menos uma permissão
    const hasAnyPermission = this.authService.hasAnyAction([
      PlatformActions.PlatformManagement.Tenant.User.Users.List,
      PlatformActions.PlatformManagement.Tenant.User.Users.Create
    ]);
  }
}
```

## Usando Diretivas de Permissão

Para usar as diretivas de permissão, importe o `PlatformAuthModule` no seu módulo:

```typescript
import { NgModule } from '@angular/core';
import { PlatformAuthModule } from '@laqus/platform-auth-angular';

@NgModule({
  imports: [
    // ... outros módulos
    PlatformAuthModule
  ]
})
export class FeatureModule { }
```

Então, use a diretiva `hasAction` nos seus templates:

```html
<!-- Elemento visível apenas se o usuário tiver a permissão específica -->
<button *hasAction="PlatformActions.PlatformManagement.Tenant.User.Users.Create">
  Criar Usuário
</button>

<!-- Elemento visível apenas se o usuário tiver todas as permissões -->
<div *hasAllActions="[listAction, createAction, deleteAction]">
  Acesso completo
</div>

<!-- Elemento visível se o usuário tiver pelo menos uma das permissões -->
<div *hasAnyAction="[listAction, createAction]">
  Acesso parcial
</div>
```

## Proteção de Rotas

Use o `ActionAuthGuard` para proteger rotas com base em permissões:

```typescript
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ActionAuthGuard } from '@laqus/platform-auth-angular';
import { PlatformActions } from '@laqus/auth-permissions';

const routes: Routes = [
  {
    path: 'users',
    component: UserListComponent,
    canActivate: [ActionAuthGuard],
    data: {
      requiredAction: PlatformActions.PlatformManagement.Tenant.User.Users.List
    }
  },
  {
    path: 'users/create',
    component: UserCreateComponent,
    canActivate: [ActionAuthGuard],
    data: {
      requiredAction: PlatformActions.PlatformManagement.Tenant.User.Users.Create
    }
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class UsersRoutingModule { }
```

## Estrutura de Permissões

A biblioteca `@laqus/auth-permissions` fornece uma estrutura tipada para as permissões do sistema:

```typescript
import { PlatformActions } from '@laqus/auth-permissions';

// Exemplos de permissões disponíveis
const userListAction = PlatformActions.PlatformManagement.Tenant.User.Users.List;
const userCreateAction = PlatformActions.PlatformManagement.Tenant.User.Users.Create;
const userUnassignAction = PlatformActions.PlatformManagement.Tenant.User.Users.Unassign;
const allUserActions = PlatformActions.PlatformManagement.Tenant.User.Users.All;
```

## Boas Práticas

1. **Inicialização Automática**: A biblioteca gerencia automaticamente o token no localStorage.

2. **Verificação de Autenticação**: Use `authService.isAuthenticated()` para verificar se o usuário está autenticado.

3. **Verificação de Permissões**: Use os métodos `hasAction()`, `hasAllActions()` ou `hasAnyAction()` para verificar permissões.

4. **Diretivas**: Use as diretivas `*hasAction`, `*hasAllActions` e `*hasAnyAction` para controle de acesso no template.

5. **Proteção de Rotas**: Use o `ActionAuthGuard` para proteger rotas com base em permissões.

## Integração com Auth Middleware API Diretamente

Se sua aplicação já realiza autenticação diretamente com o endpoint `/api/v1/auth/user/sessions/credentials/login` do Auth Middleware API, você pode integrar facilmente com a biblioteca para usar os recursos de autorização. Este endpoint estabelece uma sessão de usuário que é utilizada pela biblioteca.

### 1. Autenticação Direta com Auth Middleware API

```typescript
import { HttpClient } from '@angular/common/http';
import { AuthService } from '@laqus/platform-auth-angular';
import { Router } from '@angular/router';

@Component({...})
export class LoginComponent {
  constructor(
    private http: HttpClient,
    private authService: AuthService,
    private router: Router
  ) {}

  async login(username: string, password: string) {
    try {
      // Chamada direta ao endpoint do Auth Middleware API que estabelece a sessão
      const response = await this.http.post<{
        success: boolean;
        // outros campos...
      }>('https://sua-api.com/api/v1/auth/user/sessions/credentials/login', {
        username,
        password
      }, {
        withCredentials: true // Importante para estabelecer a sessão
      }).toPromise();
      
      if (response && response.success) {
        // A sessão já foi estabelecida pelo endpoint
        // Apenas inicialize o AuthService para carregar as permissões
        await this.authService.initialize();
        this.router.navigate(['/dashboard']);
        return true;
      }
      return false;
    } catch (error) {
      console.error('Falha no login:', error);
      return false;
    }
  }
}
```

### 2. Sistema de Autenticação Baseado em Sessão

A biblioteca `@laqus/platform-auth-angular` utiliza autenticação baseada em sessão, de forma idêntica ao projeto `portal-monorepo`:

#### Autenticação via Sessão

O endpoint `/api/v1/auth/user/sessions/credentials/login` do auth-middleware-api estabelece uma sessão de usuário e define um cookie seguro (`__Secure-lqsid`) no navegador. Este cookie tem a flag `HttpOnly` e é gerenciado automaticamente pelo navegador e pelo servidor.

#### Fluxo de Autenticação e Carregamento de Permissões

A biblioteca utiliza um fluxo em duas etapas para autenticação e carregamento de permissões:

1. **Verificação de Status de Autenticação**:
   ```typescript
   // Verificar status de autenticação
   this.authMiddlewareApiService.getUserSessionStatus().pipe(
     switchMap((status: AuthStatus) => {
       // Se autenticado e tem token, busca as permissões completas
       if (status.authenticated && status.session?.accessToken) {
         return this.authMiddlewareApiService.getUserSelf(status.session.accessToken);
       }
       return of(null);
     })
   );
   ```

2. **Carregamento de Permissões**:
   ```typescript
   // Carregar permissões do usuário usando o token de acesso
   this.authMiddlewareApiService.getUserSelf(token).pipe(
     map(userSelfData => {
       // Define os dados de permissão no serviço de autorização
       this.authorizationContextService.setPermissionData(userSelfData);
       this.permissionsLoaded = true;
     })
   );
   ```

> **Nota**: A biblioteca não manipula diretamente os cookies de sessão, pois eles têm a flag `HttpOnly` e não são acessíveis via JavaScript. O navegador envia automaticamente os cookies de sessão em requisições para o mesmo domínio quando a opção `withCredentials: true` é usada.

#### Cache de Permissões

A biblioteca também armazena as permissões do usuário em cache para evitar requisições desnecessárias:

```typescript
// Verificar cache primeiro
const cachedPermissions = this.cacheService.get<IUserSelfDto>('user_permissions');
if (cachedPermissions) {
  // Usar permissões do cache
  this.authorizationContextService.setPermissionData(cachedPermissions);
  this.currentUserSubject.next(cachedPermissions);
  this.permissionsLoaded = true;
  return;
}

// Se não estiver em cache, buscar da API e armazenar
// A sessão é enviada automaticamente via cookies
const status = await firstValueFrom(this.http.get<AuthStatus>(`${apiUrl}/api/v1/auth/user/sessions/status`, {
  withCredentials: true
}));

// Processar dados e armazenar em cache
if (status.user) {
  this.cacheService.set('user_permissions', status.user);
}
```

#### Configuração do Cache

Você pode configurar o prefixo usado para as chaves de cache no localStorage e o tempo de expiração do cache:

```typescript
@NgModule({
  imports: [PlatformAuthModule],
  providers: [
    {
      provide: AUTH_CONFIG,
      useValue: {
        authMiddlewareApiUrlBase: 'https://sua-api.com',
        cache: {
          authCacheKeysPrefix: 'minha_app_auth_', // Prefixo personalizado
          expirationTimeInMs: 1800000 // 30 minutos em milissegundos (opcional)
        }
      }
    }
  ]
})
export class AppModule { }
```

#### Limpeza de Cache

Quando o usuário faz logout, todo o cache é limpo:

```typescript
clearAuth(): void {
  this._token = null;
  this.permissionsLoaded = false;
  this.currentUserSubject.next(null);
  this.cacheService.clear(); // Limpa todo o cache
  this.removeStoredToken(); // Remove o token
}
```

### 3. Como o `initialize()` Funciona

O método `initialize()` do AuthService verifica o status de autenticação e carrega as permissões do usuário:

```typescript
public async initialize(): Promise<boolean> {
  try {
    // Verifica o status de autenticação e carrega as permissões
    const status = await firstValueFrom(this.checkAuthStatus(true));
    return status.authenticated;
  } catch (error) {
    console.error('Failed to initialize auth service', error);
    return false;
  }
}
```

Internamente, o método `checkAuthStatus` realiza o fluxo completo de verificação de autenticação e carregamento de permissões descrito anteriormente.

## Arquitetura de Serviços

A biblioteca utiliza uma arquitetura de serviços bem definida para separar responsabilidades:

### AuthMiddlewareApiService

Responsável por todas as chamadas de API para o middleware de autenticação:

```typescript
@Injectable({ providedIn: 'root' })
export class AuthMiddlewareApiService {
  // Verifica o status da sessão do usuário
  getUserSessionStatus(): Observable<AuthStatus> { ... }
  
  // Obtém os dados do usuário e suas permissões
  getUserSelf(token: string): Observable<IUserSelfDto> { ... }
  
  // Realiza o login do usuário usando credenciais
  loginWithCredentials(email: string, password: string): Observable<{ success: boolean }> { ... }
  
  // Realiza o logout do usuário
  logout(): Observable<{ success: boolean }> { ... }
  
  // Atualiza o token de acesso
  refreshToken(): Observable<{ success: boolean }> { ... }
}
```

### AuthService

Gerencia o estado de autenticação e autorização da aplicação:

```typescript
@Injectable({ providedIn: 'root' })
export class AuthService {
  // Observables para o estado de autenticação
  public readonly currentUser$ = this.currentUserSubject.asObservable();
  public readonly isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
  public readonly currentSessionStatus$ = this.currentSessionStatusSubject.asObservable();
  
  // Verifica o status de autenticação
  public checkAuthStatus(forceRefresh: boolean = false): Observable<AuthStatus> { ... }
  
  // Inicializa o serviço
  public async initialize(): Promise<boolean> { ... }
  
  // Verifica se o usuário está autenticado
  isAuthenticated(): boolean { ... }
  
  // Verifica permissões
  hasAction(action: IResourceAction): boolean { ... }
  hasAllActions(actions: IResourceAction[]): boolean { ... }
  hasAnyAction(actions: IResourceAction[]): boolean { ... }
}
```

### AuthorizationContextService

Responsável por gerenciar o contexto de autorização e verificar permissões:

```typescript
@Injectable({ providedIn: 'root' })
export class AuthorizationContextService {
  // Define os dados de permissões
  setPermissionData(data: any): void { ... }
  
  // Verifica permissões
  hasAction(action: IResourceAction): boolean { ... }
  hasAllRequiredActions(actions: IResourceAction[]): boolean { ... }
  hasAnyOfRequiredActions(actions: IResourceAction[]): boolean { ... }
}
```

## Solução de Problemas

### Token Expirado

A biblioteca verifica automaticamente se o token está expirado. Se estiver, o método `isAuthenticated()` retornará `false`.

### Permissões Não Carregadas

Se as permissões não estiverem carregadas, os métodos de verificação de permissão retornarão `false` e exibirão um aviso no console:

```typescript
hasAction(action: IResourceAction): boolean {
  if (!this.isAuthenticated() || !this.permissionsLoaded) {
    console.warn('User not authenticated or permissions not loaded yet');
    return false;
  }
  return this.authorizationContextService.hasAction(action);
}
```

### Atualização de Permissões

Para forçar uma atualização das permissões (por exemplo, após uma alteração de perfil):

```typescript
// Forçar atualização do status e permissões
authService.checkAuthStatus(true).subscribe();

// Ou limpar autenticação e redirecionar para login
authService.clearAuth();
router.navigate(['/login']);
```