import { inject, Injectable } from '@angular/core'; import { AuthConfig, OAuthEvent, OAuthService } from 'angular-oauth2-oidc'; import { UserService } from './user.service'; import { User } from '../model/User'; import { Router } from '@angular/router'; import { environment } from '../../environments/environment'; import { catchError, from, of, tap } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class AuthService { private readonly authConfig: AuthConfig = { issuer: 'https://oauth.simonis.lol/application/o/casino-dev/', clientId: environment.OAUTH_CLIENT_ID, dummyClientSecret: environment.OAUTH_CLIENT_SECRET, scope: `openid email profile ${environment.OAUTH_CLIENT_ID}`, responseType: 'code', redirectUri: window.location.origin + '/auth/callback', oidc: true, requestAccessToken: true, // Explicitly set token endpoint since discovery is failing tokenEndpoint: 'https://oauth.simonis.lol/application/o/token/', userinfoEndpoint: 'https://oauth.simonis.lol/application/o/userinfo/', // Loosen validation since Authentik might not fully conform to the spec strictDiscoveryDocumentValidation: false, skipIssuerCheck: true, disableAtHashCheck: true, requireHttps: false, showDebugInformation: true, // Enable for debugging sessionChecksEnabled: false, }; private userService: UserService = inject(UserService); private oauthService: OAuthService = inject(OAuthService); private router: Router = inject(Router); private user: User | null = null; constructor() { console.log('Auth service initializing'); this.oauthService.configure(this.authConfig); this.setupEventHandling(); // Check if we're on the callback page const hasAuthParams = window.location.search.includes('code=') || window.location.search.includes('token=') || window.location.search.includes('id_token='); if (hasAuthParams) { console.log('Auth parameters detected in URL, processing code flow'); // We're in the OAuth callback this.processCodeFlow(); } else { // Normal app startup console.log('Normal startup, checking for existing session'); this.checkExistingSession(); } } private processCodeFlow() { // Try to exchange the authorization code for tokens this.oauthService.tryLogin({ onTokenReceived: context => { console.log('Token received in code flow:', context); // Manually create a token_received event this.handleSuccessfulLogin(); } }).catch(err => { console.error('Error processing code flow:', err); }); } private checkExistingSession() { // Try login on startup this.oauthService.loadDiscoveryDocumentAndTryLogin().then((isLoggedIn) => { console.log('Initial login attempt result:', isLoggedIn); if (isLoggedIn && !this.user) { this.handleSuccessfulLogin(); } }).catch(err => { console.error('Error during initial login attempt:', err); }); } private setupEventHandling() { this.oauthService.events.subscribe((event: OAuthEvent) => { console.log('Auth event:', event); if (event.type === 'token_received') { this.handleSuccessfulLogin(); } else if (event.type === 'token_refresh_error' || event.type === 'token_expires') { console.warn('Token issue detected:', event.type); } }); } private handleSuccessfulLogin() { console.log('Access token received, loading user profile'); console.log('Token valid:', this.oauthService.hasValidAccessToken()); console.log('ID token valid:', this.oauthService.hasValidIdToken()); console.log('Access token:', this.oauthService.getAccessToken()); // Extract claims from id token if available let claims = this.oauthService.getIdentityClaims(); console.log('ID token claims:', claims); // If we have claims, use that as profile if (claims && (claims['sub'] || claims['email'])) { console.log('Using ID token claims as profile'); this.processUserProfile(claims); return; } // Otherwise try to load user profile try { from(this.oauthService.loadUserProfile()).pipe( tap(profile => console.log('User profile loaded:', profile)), catchError(error => { console.error('Error loading user profile:', error); // If we can't load the profile but have a token, create a minimal profile if (this.oauthService.hasValidAccessToken()) { const token = this.oauthService.getAccessToken(); // Create a basic profile from the token const minimalProfile = { sub: 'user-' + Math.random().toString(36).substring(2, 10), preferred_username: 'user' + Date.now() }; return of({ info: minimalProfile }); } return of(null); }) ).subscribe(profile => { if (profile) { this.processUserProfile(profile); } else { console.error('Could not load or create user profile'); this.router.navigate(['/']); } }); } catch (err) { console.error('Exception in handleSuccessfulLogin:', err); // Try to navigate to home if we have a token anyway if (this.oauthService.hasValidAccessToken()) { this.router.navigate(['/home']); } else { this.router.navigate(['/']); } } } private processUserProfile(profile: any) { this.fromUserProfile(profile).subscribe({ next: (user) => { console.log('User created/retrieved from backend:', user); this.user = user; this.router.navigate(['home']); }, error: (err) => { console.error('Error creating/retrieving user:', err); // Navigate to home if we have a token anyway - the backend will need to handle auth if (this.oauthService.hasValidAccessToken()) { this.router.navigate(['/home']); } else { this.router.navigate(['/']); } } }); } login() { console.log('Initiating login flow'); try { // First ensure discovery document is loaded this.oauthService.loadDiscoveryDocument().then(() => { console.log('Discovery document loaded, starting login flow'); this.oauthService.initLoginFlow(); }).catch(err => { console.error('Error loading discovery document:', err); // Try login anyway with configured endpoints this.oauthService.initLoginFlow(); }); } catch (err) { console.error('Exception in login:', err); // Try direct login as a fallback const redirectUri = this.authConfig.redirectUri || window.location.origin + '/auth/callback'; const scope = this.authConfig.scope || 'openid email profile'; const authUrl = `${this.authConfig.issuer}authorize?client_id=${this.authConfig.clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`; window.location.href = authUrl; } } logout() { try { console.log('Logging out'); this.user = null; this.oauthService.logOut(); // Clear any lingering token in storage localStorage.removeItem('access_token'); sessionStorage.removeItem('access_token'); this.router.navigate(['/']); } catch (err) { console.error('Exception in logout:', err); // Force clear tokens this.oauthService.revokeTokenAndLogout().catch(() => { // Just navigate to home page as fallback this.router.navigate(['/']); }); } } isLoggedIn() { return this.oauthService.hasValidAccessToken(); } private fromUserProfile(profile: object) { return this.userService.getOrCreateUser(profile); } getAccessToken() { return this.oauthService.getAccessToken(); } getUser() { return this.user; } }