Skip to main content

Autenticación (features/auth)

Pantallas públicas de acceso: el login con email y contraseña y la pantalla de reconexión que se muestra cuando el CRM corre dentro de un iframe y la sesión caduca. La feature solo contiene UI; la lógica de sesión vive en NgRx y en AuthService (ver Auth, interceptores y RBAC y Frame bridge). Contraparte del backend: Auth.

Rutas​

AuthModule (features/auth/auth.module.ts) se carga con loadChildren bajo auth desde shared/routes/full.routes.ts, es decir dentro de FullComponent (layout sin header ni sidebar; ver Rutas).

RutaComponenteGuardPermiso
auth/loginLoginComponentLoggedInAuthGuardninguno
auth/reconnectReconnectComponentningunoninguno
auth (vacía)redirige a login (pathMatch: 'full')ningunoninguno

LoggedInAuthGuard (core/guards/loggedIn.guard.ts) hace lo inverso a AuthGuard: si AuthService.isLoggedIn() es verdadero navega a / y bloquea el login; si no, lo deja pasar. /reconnect no tiene guard, de modo que es alcanzable con o sin sesión.

Archivos​

ArchivoRol
auth.module.tsDeclara los dos componentes; importa ReactiveFormsModule, RouterModule.forChild y TranslateModule
pages/login/login.component.{ts,html,scss}Formulario de login y panel decorativo
pages/reconnect/reconnect.component.{ts,html,scss}Aviso de sesión expirada y botón para pedir un nuevo handshake al padre
pages/login/login.component.spec.tsPruebas del componente de login

Login​

Formulario y validaciones​

LoginComponent construye un FormGroup con FormBuilder:

CampoValidadoresMensaje (clave i18n)
emailrequired, emailauth.errorMessages.emailIsRequired / auth.errorMessages.invalidEmail
passwordrequiredauth.errorMessages.passwordIsRequired

Los mensajes se muestran solo cuando el control está invalid y touched. El botón de envío se deshabilita con form.invalid o mientras loading$ está activo (spinner y texto auth.loadingText). El botón del ojo (togglePassword) alterna el type del input entre password y text.

Flujo​

  1. onSubmit() no hace nada si el formulario es inválido; si es válido limpia el error local y despacha UserActions.userAuthenticationRequest({ request: this.form.value }). El payload es { email, password } (UserLoginRequest).
  2. El efecto loginUser$ (core/states/auth/auth.effects.ts) usa switchMap: llama AuthService.login (POST <apiUrl>/auth/login, con el header skip), guarda { token } en secureStorage bajo USER_SESSION, llama UserService.getMeUser (GET <apiUrl>/users/get-me), reescribe la sesión con { ...usuario, token }, navega a /whaticket/chat y emite userAuthenticationSuccess.
  3. Si cualquiera de las dos llamadas falla, emite userAuthenticationFailure con el mensaje traducido (error.error.message o error.message; si no hay, words.unknownError).
  4. El componente escucha userAuthenticationFailure con Actions + ofType y muestra el message en un bloque role="alert" encima del formulario. El error se limpia en el siguiente envío.

El 401 de /auth/login no pasa por el flujo de logout del ErrorInterceptor porque la petición lleva el header skip (ver tratamiento de errores).

Estado NgRx usado​

  • selectLoading (core/states/auth/auth.selectors.ts) alimenta loading$.
  • El reducer authReducer pone loading: true en userAuthenticationRequest y false en success o failure; message guarda textos en inglés ('Authenticating user...', 'Authentication failed: ...') que son de estado interno, no de UI.

Reconexión (iframe)​

Cuando el CRM está embebido y el backend responde 401, el ErrorInterceptor emite CRM_AUTH_EXPIRED al padre y navega a /auth/reconnect. También llega aquí cuando falla frameLogin$.

  • ReconnectComponent muestra un candado, el título "Sesión expirada" y el texto "Tu sesión ha caducado. Haz clic en reconectar para volver a ingresar." Estos textos y "Reconectar" / "Reconectando…" están escritos en duro en español en el HTML, no pasan por translate.
  • onReconnect() limpia error y llama FrameBridgeService.emit('CRM_AUTH_EXPIRED'): pide al padre un nuevo handshake postMessage. No navega ni llama al backend por sí mismo.
  • Cuando el padre responde, AuthEffects (suscrito a FrameBridgeService.auth$) despacha frameAuthenticateRequest; el efecto frameLogin$ llama AuthService.frameLogin (POST <apiUrl>/auth/frame-login, header skip), guarda sesión, navega a /whaticket/chat y emite CRM_AUTHENTICATED al padre. Si falla, vuelve a navegar a /auth/reconnect.
  • El componente también escucha userAuthenticationFailure y pinta el mensaje en reconnect__alert; loading$ deshabilita el botón y muestra "Reconectando…".

Servicios y endpoints​

ServicioMétodoEndpointNota
AuthService (core/services/api/auth.service.ts)login(request)POST /auth/loginRespuesta ApiResponse<{ token }>; header skip
AuthServiceframeLogin(payload)POST /auth/frame-loginBody { token, type: 'user', data: { externalId, email?, username?, firstname?, lastName?, userType?, roleId? } }; respuesta { token, identifier, externalId }
UserService (core/services/api/user.service.ts)getMeUser()GET /users/get-mePerfil con permisos y feature flags

AuthService además expone logout() (borra USER_SESSION y navega a /auth/login), isLoggedIn() / isLoggedOut() (comparan exp del JWT con la hora actual) y utilidades getDeviceInfo, isMobile, getCurrentPosition que el login no usa.

Modelos​

UserLoginRequest (core/interfaces/api/user.interface.ts): email, password y los opcionales lastLogin, lastSessionIp, TFA, device. El login solo envía email y password. FrameAuthPayload está definido en frame-bridge.service.ts.

Permisos​

Ninguno: son rutas públicas. Tras el login, los permisos se cargan con get-me y gobiernan el resto de la aplicación.

Gotchas e inconsistencias​

  • "¿Olvidó su contraseña?" (auth.losePassword) es un <a href="#"> sin handler: no hay flujo de recuperación en el frontend.
  • "Recordarme": el checkbox #remember no está ligado al formulario ni a ninguna lógica; no cambia la persistencia de la sesión (siempre secureStorage).
  • Las tarjetas del panel derecho ("Crecimiento Mensual +24.8%", "Mensajes Activos 1,204") son maquetas con datos fijos, no métricas reales.
  • Las acciones enableTwoFactorAuthentication, disableTwoFactorAuthentication y los estados preAuth existen en auth.actions.ts y auth.reducers.ts, pero esta feature no ofrece pantallas de 2FA.
  • loginUser$ usa switchMap: un segundo envío mientras hay uno en vuelo cancela el primero (el botón ya se deshabilita con loading$, así que es solo defensivo).
  • El mensaje de fallo de login proviene del backend; si trae una clave i18n se traduce con TranslateService.instant, si no se muestra tal cual.

Sockets y utilidades​

Esta feature no abre sockets: los sockets de chat se conectan en ContentComponent una vez hay perfil (ver Sockets). Las utilidades que usa están en core: StorageService.secureStorage y parseJwt (ver Servicios), FrameBridgeService e IframeDetectionService (ver Frame bridge).

Pruebas​

SpecCubre
features/auth/pages/login/login.component.spec.tsConstrucción del formulario, validez con email inválido o contraseña vacía, no despacha si es inválido, despacha userAuthenticationRequest con los valores, limpieza del error al enviar, togglePassword, error por userAuthenticationFailure, reflejo de loading$ y destrucción limpia
core/guards/loggedIn.guard.spec.tsPermite el login sin sesión; con sesión bloquea y redirige a /
core/states/auth/auth.{actions,effects,reducers,selectors}.spec.tsAcciones, efectos, reducer y selectores del slice auth

ReconnectComponent no tiene spec propio.