import { OnDestroy } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthStateService } from './auth-state.service'; import { TokenService } from './token.service'; import { AuthStorageService } from './storage.service'; import { AuthSyncService } from './sync.service'; import { SigninRequest, SigninResponse, SignupRequest, SignupResponse, VerifyEmailRequest, VerifyEmailResponse, ResendCodeRequest, ResendCodeResponse, MFAVerifyResponse, RefreshResponse, GetPermissionsResponse, GetProfileResponse, UpdateProfileRequest, UpdateProfileResponse, MFASetupResponse, MFAConfirmResponse, MFADisableRequest, MFADisableResponse, ForgotPasswordRequest, ForgotPasswordResponse, ResetPasswordRequest, ResetPasswordResponse, ChangePasswordResponse, DeleteAccountResponse, SendDeleteAccountCodeResponse, SwitchOrgResponse, MFAMethod, AuthError, ValtechAuthConfig, EnableNotificationsResult, NotificationPermissionState, RegisterDeviceResult, TOTPSetupResponse, TOTPVerifySetupResponse, TOTPDisableResponse, RegenerateBackupCodesResponse, BackupCodesCountResponse, OAuthProvider, LinkedProvider, HasPasswordResponse, UpdateHandleResponse, CheckHandleResponse, UpdateAvatarRequest, UpdateAvatarResponse, InitiateEmailChangeResponse, ConfirmEmailChangeStep1Response, ConfirmEmailChangeStep2Response } from './types'; import { OAuthService } from './oauth.service'; import { FirebaseService, MessagingService } from '../firebase'; import { I18nService } from '../i18n'; import { Firestore } from '@angular/fire/firestore'; import { ConfirmationDialogService } from '../confirmation-dialog/confirmation-dialog.service'; import * as i0 from "@angular/core"; /** * Servicio principal de autenticación. * * @example * ```typescript * import { AuthService } from 'valtech-components'; * * @Component({...}) * export class LoginComponent { * private auth = inject(AuthService); * * async login() { * await firstValueFrom(this.auth.signin({ email, password })); * if (this.auth.mfaPending().required) { * // Mostrar UI de MFA * } else { * this.router.navigate(['/']); * } * } * } * ``` */ export declare class AuthService implements OnDestroy { private config; private http; private router; private stateService; private tokenService; private storageService; private syncService; private firebaseService; private oauthService; private messagingService; private i18nService; private confirmationService; private firestoreInstance; private refreshTimerId; private syncSubscription; private firestoreRBACUnsubscribe; constructor(config: ValtechAuthConfig | null, http: HttpClient, router: Router, stateService: AuthStateService, tokenService: TokenService, storageService: AuthStorageService, syncService: AuthSyncService, firebaseService: FirebaseService, oauthService: OAuthService, messagingService: MessagingService | null, i18nService: I18nService | null, confirmationService: ConfirmationDialogService, firestoreInstance: Firestore | null); /** Estado completo de autenticación */ readonly state: import("@angular/core").Signal; /** Usuario está autenticado */ readonly isAuthenticated: import("@angular/core").Signal; /** Estado de carga */ readonly isLoading: import("@angular/core").Signal; /** Información del usuario */ readonly user: import("@angular/core").Signal; /** Token de acceso */ readonly accessToken: import("@angular/core").Signal; /** Roles del usuario */ readonly roles: import("@angular/core").Signal; /** Permisos del usuario */ readonly permissions: import("@angular/core").Signal; /** Usuario es super admin */ readonly isSuperAdmin: import("@angular/core").Signal; /** Estado de MFA pendiente */ readonly mfaPending: import("@angular/core").Signal; /** Error actual */ readonly error: import("@angular/core").Signal; /** * Inicializa el servicio de autenticación. * Llamado automáticamente por provideValtechAuth. */ initialize(): Promise; ngOnDestroy(): void; /** * Inicia sesión con email y contraseña. * Detecta automáticamente la plataforma para identificar dispositivos nuevos. */ signin(request: SigninRequest): Observable; /** * Inicia sesión con OAuth (Google, Apple, Microsoft). * Abre un popup para el flujo de autenticación. * * @param provider - Proveedor OAuth ('google', 'apple', 'microsoft') * @returns Observable que emite SigninResponse cuando se completa * * @example * ```typescript * this.auth.signinWithOAuth('google').subscribe({ * next: () => this.router.navigate(['/']), * error: (err) => console.error('OAuth failed:', err) * }); * ``` */ signinWithOAuth(provider: OAuthProvider): Observable; /** * Vincula un proveedor OAuth adicional a la cuenta actual. * Requiere que el usuario esté autenticado. * * @param provider - Proveedor OAuth a vincular */ linkOAuthProvider(provider: OAuthProvider): Observable<{ success: boolean; }>; /** * Obtiene los proveedores OAuth vinculados al usuario. */ getLinkedProviders(): Observable; /** * Desvincula un proveedor OAuth de la cuenta. * * @param provider - Proveedor a desvincular */ unlinkOAuthProvider(provider: OAuthProvider): Observable<{ success: boolean; }>; /** * Establece contraseña para usuarios que solo tienen OAuth. * Permite que usuarios OAuth-only puedan también usar email/password. * * @param password - Nueva contraseña */ setPasswordForOAuthUser(password: string): Observable<{ success: boolean; }>; /** * Verifica si el usuario tiene contraseña establecida. * Útil para mostrar/ocultar opciones de cambio de contraseña. */ checkHasPassword(): Observable; /** * Registra un nuevo usuario. * El usuario queda en estado PENDING hasta verificar su email. */ signup(request: SignupRequest): Observable; /** * Verifica email con código de 6 dígitos. * Si es exitoso, hace auto-login y retorna tokens. */ verifyEmail(request: VerifyEmailRequest): Observable; /** * Reenvía código de verificación al email. */ resendCode(request: ResendCodeRequest): Observable; /** * Verifica código MFA. */ verifyMFA(code: string): Observable; /** * Refresca el token de acceso. * Implementa token rotation: cada refresh genera un nuevo refresh token * que el cliente debe guardar para el próximo refresh. */ refreshAccessToken(): Observable; /** * Maneja autenticación exitosa desde fuentes externas (OAuth, etc). * Guarda tokens, actualiza estado, inicia Firebase si corresponde. * * @param authResult - Resultado de autenticación con tokens * * @example * ```typescript * // Desde un flujo OAuth externo * externalOAuth.signIn().subscribe({ * next: (result) => { * this.authService.setExternalAuth(result); * } * }); * ``` */ setExternalAuth(authResult: { accessToken: string; refreshToken: string; firebaseToken?: string; expiresIn: number; roles?: string[]; permissions?: string[]; }): void; /** * Cierra sesión tras pedir confirmación al usuario. * * Muestra un diálogo nativo (estilo destructivo). Si el usuario confirma, * ejecuta `logout()`. Si cancela, no pasa nada. * * Texto por defecto desde `_global` i18n: `logoutConfirmTitle`, * `logoutConfirmMessage`, `logout`, `cancel`. Override via `opts`. * * @returns true si el usuario confirmó (logout ejecutado), false si canceló. * * @example * onLogoutClick() { this.auth.logoutWithConfirmation(); } */ logoutWithConfirmation(opts?: { title?: string; message?: string; confirmText?: string; cancelText?: string; }): Promise; /** * Cierra sesión. */ logout(): Promise; /** * Configura MFA para el usuario. */ setupMFA(method: MFAMethod, phone?: string): Observable; /** * Confirma la configuración de MFA. */ confirmMFA(code: string): Observable; /** * Deshabilita MFA. Acepta `password` o `mfaCode` (uno requerido). * `mfaCode` es para usuarios OAuth-only con TOTP que no tienen contraseña. */ disableMFA(input: MFADisableRequest): Observable; sendMFADisableCode(): Observable<{ operationId: string; }>; /** * Inicia configuración de TOTP MFA. * Retorna el secreto, URL para QR code y códigos de respaldo. */ setupTOTP(): Observable; /** * Verifica el código TOTP y activa MFA. * Debe llamarse después de setupTOTP con el código del authenticator. */ verifyTOTPSetup(code: string): Observable; /** * Regenera los códigos de respaldo TOTP. * Los códigos anteriores son invalidados. */ regenerateBackupCodes(): Observable; /** * Obtiene la cantidad de códigos de respaldo restantes. */ getBackupCodesCount(): Observable; /** * Desactiva TOTP MFA (requiere contraseña). */ disableTOTP(password: string): Observable; /** * Cambia la organización activa del usuario. * Genera un nuevo Firebase token con el activeOrg actualizado. * * @param organizationId - ID de la organización a la que cambiar * @returns Observable con el nuevo Firebase token y activeOrg * * @example * ```typescript * await firstValueFrom(this.auth.switchOrg('org_xyz789')); * // Firebase ya está re-autenticado con la nueva org * const activeOrg = await this.firebase.getActiveOrg(); * ``` */ switchOrg(organizationId: string): Observable; /** * Obtiene el perfil del usuario autenticado. * Incluye información de MFA y teléfono. Hidrata el state automáticamente * con `name/handle/avatarUrl/phone` para que `auth.user()` los retorne. */ getProfile(): Observable; /** * Actualiza el perfil del usuario. Sincroniza state con los nuevos values * (name/phone) — el backend response no los echo'a pero el caller ya tiene * los valores que mandó. */ updateProfile(request: UpdateProfileRequest): Observable; /** * Actualiza el avatar del usuario en el backend. Sincroniza state con el * `avatarUrl` retornado para que el header / dashboard se refresquen sin * necesidad de un getProfile adicional. */ updateAvatar(request: UpdateAvatarRequest): Observable; /** * Inicia el proceso de recuperación de contraseña. * Envía un código al email del usuario. */ forgotPassword(request: ForgotPasswordRequest): Observable; /** * Resetea la contraseña usando el código enviado por email. */ resetPassword(request: ResetPasswordRequest): Observable; /** * Cambia la contraseña del usuario autenticado. * Requiere la contraseña actual para verificación. */ changePassword(currentPassword: string, newPassword: string): Observable; initiateEmailChange(currentPassword: string, newEmail: string): Observable; confirmEmailChangeStep1(code: string): Observable; confirmEmailChangeStep2(code: string): Observable; /** * Elimina la cuenta del usuario (soft delete). * Requiere confirmación de contraseña por seguridad. * Después de eliminar, cierra sesión automáticamente. * * @param password - Contraseña actual para confirmar * @returns Observable con resultado de la eliminación * * @example * ```typescript * async confirmDeleteAccount(password: string) { * try { * const result = await firstValueFrom(this.auth.deleteAccount(password)); * console.log(result.message); // "Tu cuenta ha sido eliminada..." * // Usuario es redirigido automáticamente al login * } catch (error) { * // Manejar error (contraseña incorrecta, etc) * } * } * ``` */ /** * Envía un código OTP al email del usuario para confirmar la eliminación. * Solo aplica a usuarios OAuth sin contraseña (`hasPassword() === false`). */ sendDeleteAccountCode(): Observable; /** * Elimina la cuenta del usuario (soft delete). * - Usuarios con contraseña: pasar `{ password }`. * - Usuarios OAuth sin contraseña: pasar `{ code }` (obtenido via sendDeleteAccountCode). * Después de eliminar, cierra sesión y redirige al login automáticamente. */ deleteAccount(credentials: { password?: string; code?: string; }): Observable; /** * Obtiene los permisos actualizados del backend. */ fetchPermissions(): Observable; /** * Verifica si el usuario tiene un permiso específico. * Formato: "resource:action" (ej: "templates:edit") */ hasPermission(permission: string): boolean; /** * Verifica si el usuario tiene alguno de los permisos dados. */ hasAnyPermission(permissions: string[]): boolean; /** * Verifica si el usuario tiene todos los permisos dados. */ hasAllPermissions(permissions: string[]): boolean; /** * Verifica si el usuario tiene un rol específico. */ hasRole(role: string): boolean; /** * Actualiza el handle (@username) del usuario. * @param handle - Nuevo handle (sin @, ej: "victorv") */ updateHandle(handle: string): Observable; /** * Verifica si un handle está disponible. * @param handle - Handle a verificar (sin @) */ checkHandleAvailability(handle: string): Observable; private get baseUrl(); /** Base for /v2/users endpoints (separate from /v2/auth). */ private get usersBaseUrl(); private handleSuccessfulAuth; private clearState; private setupFirestoreRBACSync; private _attachFirestoreRBACListener; private teardownFirestoreRBACSync; private startRefreshTimer; private stopRefreshTimer; private handleSyncEvent; private handleAuthError; private signInWithFirebase; private signOutFirebase; /** * Re-establece la sesión de Firebase Auth en el bootstrap de la app cuando * el access token de la app sigue válido (la rama de initialize() que NO * pasa por refresh). * * Firebase Auth mantiene su PROPIA sesión con persistencia IndexedDB * independiente del storage de tokens de la app. En un navegador normal esa * persistencia se auto-restaura sola; en una PWA standalone en iOS el * contenedor de storage no sobrevive, y `firebaseAuthReady` se queda en * `false` indefinidamente → los listeners Firestore cuelgan (~40s timeout). * * Estrategia: dar una ventana corta a la auto-restauración de Firebase y, si * no ocurre, forzar un `refreshAccessToken()` — que ya hace `signInWithFirebase` * con el `firebaseToken` que devuelve `/refresh`. * * Fire-and-forget: se invoca con `void` para no demorar `initialize()`. */ private ensureFirebaseSessionOnBootstrap; /** * Fallback self-healing de Firebase Auth. Pide un firebaseToken fresco vía * `/refresh` (que sí lo devuelve de forma confiable) y, en éxito, * `refreshAccessToken()` ya ejecuta `signInWithFirebase()`. * * Se invoca cuando un path de auth dejó la sesión de Firebase sin establecer: * - login OAuth/password que volvió sin `firebaseToken`, * - cold launch de PWA iOS sin persistencia de Firebase. * * `reason` aparece en los logs `[FBAuth] fallback` para poder atestiguar * desde el debug-console qué disparó el fallback. */ private reestablishFirebaseViaRefresh; /** * Solicita permisos de notificación y registra el dispositivo. * Usar cuando el usuario acepta recibir notificaciones. * * @example * ```typescript * async onEnableNotifications() { * const result = await this.auth.enableNotifications(); * if (result.granted) { * console.log('Notificaciones habilitadas'); * } * } * ``` */ enableNotifications(): Promise; /** * Desactiva las notificaciones para este dispositivo. * Elimina el token del backend y de FCM. * Usar desde un toggle de preferencias cuando el usuario desactiva notificaciones. * * NOTA: Esto NO revoca el permiso del navegador (el usuario debe hacerlo manualmente * desde la configuración del navegador). Solo elimina el registro del dispositivo. * * @example * ```typescript * async onToggleNotifications(enabled: boolean) { * if (enabled) { * await this.auth.enableNotifications(); * } else { * await this.auth.disableNotifications(); * } * } * ``` */ disableNotifications(): Promise<{ disabled: boolean; }>; /** * Retorna el estado actual de permisos de notificación. * * @returns 'granted' | 'denied' | 'default' | 'unsupported' */ getNotificationPermissionState(): NotificationPermissionState; /** * Registra manualmente el dispositivo en el backend. * Útil para sincronizar dispositivos que tienen token FCM pero no están registrados. * * @param providedToken Token FCM opcional. Si se proporciona, se usa directamente * sin depender del MessagingService (evita timing issues). * @returns Resultado del registro con deviceId si fue exitoso * * @example * ```typescript * // Sincronizar con token existente (recomendado) * const result = await this.auth.registerDevice(myToken); * * // Sin token, intentará obtenerlo del MessagingService * const result = await this.auth.registerDevice(); * ``` */ registerDevice(providedToken?: string): Promise; /** * Registra el dispositivo en el backend si tiene permisos de notificación. * Se llama automáticamente después de un login exitoso si enableDeviceRegistration=true. */ private registerDeviceIfNeeded; /** * Elimina el dispositivo del backend y borra el token FCM. */ private unregisterDevice; /** * Detecta información de la plataforma del dispositivo. */ private detectPlatformInfo; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; }