229 lines
7.9 KiB
TypeScript
229 lines
7.9 KiB
TypeScript
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;
|
|
}
|
|
}
|