#136: EMail Auth on different browsers ...

This commit is contained in:
Andreas Knuth 2025-03-13 13:55:09 +01:00
parent 162c5b042f
commit 097a6cb360
4 changed files with 103 additions and 27 deletions

View File

@ -20,21 +20,31 @@ export class AuthController {
} }
try { try {
// Schritt 1: Hole den Benutzer anhand der E-Mail-Adresse // Step 1: Get the user by email address
const userRecord = await this.firebaseAdmin.auth().getUserByEmail(email); const userRecord = await this.firebaseAdmin.auth().getUserByEmail(email);
if (userRecord.emailVerified) { if (userRecord.emailVerified) {
return { message: 'Email is already verified' }; // Even if already verified, we'll still return a valid token
const customToken = await this.firebaseAdmin.auth().createCustomToken(userRecord.uid);
return {
message: 'Email is already verified',
token: customToken,
};
} }
// Schritt 2: Aktualisiere den Benutzerstatus // Step 2: Update the user status to set emailVerified to true
// Hinweis: Wir können den oobCode nicht serverseitig validieren.
// Wir nehmen an, dass der oobCode korrekt ist, da er von Firebase generiert wurde.
await this.firebaseAdmin.auth().updateUser(userRecord.uid, { await this.firebaseAdmin.auth().updateUser(userRecord.uid, {
emailVerified: true, emailVerified: true,
}); });
return { message: 'Email successfully verified' }; // Step 3: Generate a custom Firebase token for the user
// This token can be used on the client side to authenticate with Firebase
const customToken = await this.firebaseAdmin.auth().createCustomToken(userRecord.uid);
return {
message: 'Email successfully verified',
token: customToken,
};
} catch (error) { } catch (error) {
throw new HttpException(error.message || 'Failed to verify email', HttpStatus.BAD_REQUEST); throw new HttpException(error.message || 'Failed to verify email', HttpStatus.BAD_REQUEST);
} }

View File

@ -1,16 +1,35 @@
<div class="container mx-auto p-4 text-center min-h-screen bg-gray-100"> <div class="container mx-auto py-8 px-4 max-w-md">
<div class="bg-white p-6 rounded-lg shadow-md text-center">
<!-- Loading state -->
<ng-container *ngIf="verificationStatus === 'pending'"> <ng-container *ngIf="verificationStatus === 'pending'">
<p class="text-lg text-gray-600">Verifying your email...</p> <div class="flex justify-center mb-4">
<div class="w-10 h-10 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
</div>
<p class="text-gray-700">Verifying your email address...</p>
</ng-container> </ng-container>
<!-- Success state -->
<ng-container *ngIf="verificationStatus === 'success'"> <ng-container *ngIf="verificationStatus === 'success'">
<div class="flex justify-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<h2 class="text-2xl font-bold text-green-600 mb-5">Your email has been verified</h2> <h2 class="text-2xl font-bold text-green-600 mb-5">Your email has been verified</h2>
<!-- <p class="text-gray-700 mb-4">You can now sign in with your new account</p> --> <p class="text-gray-700 mb-4">You will be redirected to your account page in 5 seconds</p>
<a routerLink="/account" class="inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">Follow this link to access your Account Page </a> <a routerLink="/account" class="inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"> Go to Account Page Now </a>
</ng-container> </ng-container>
<!-- Error state -->
<ng-container *ngIf="verificationStatus === 'error'"> <ng-container *ngIf="verificationStatus === 'error'">
<h2 class="text-2xl font-bold text-red-600 mb-2">Verification failed</h2> <div class="flex justify-center mb-4">
<p class="text-gray-700">{{ errorMessage }}</p> <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h2 class="text-2xl font-bold text-red-600 mb-3">Verification Failed</h2>
<p class="text-gray-700 mb-4">{{ errorMessage }}</p>
<a routerLink="/login" class="inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"> Return to Login </a>
</ng-container> </ng-container>
</div> </div>
</div>

View File

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { UserService } from '../../services/user.service'; import { UserService } from '../../services/user.service';
@ -16,7 +16,7 @@ export class EmailAuthorizedComponent implements OnInit {
verificationStatus: 'pending' | 'success' | 'error' = 'pending'; verificationStatus: 'pending' | 'success' | 'error' = 'pending';
errorMessage: string | null = null; errorMessage: string | null = null;
constructor(private route: ActivatedRoute, private http: HttpClient, private authService: AuthService, private userService: UserService) {} constructor(private route: ActivatedRoute, private router: Router, private http: HttpClient, private authService: AuthService, private userService: UserService) {}
ngOnInit(): void { ngOnInit(): void {
const oobCode = this.route.snapshot.queryParamMap.get('oobCode'); const oobCode = this.route.snapshot.queryParamMap.get('oobCode');
@ -32,12 +32,32 @@ export class EmailAuthorizedComponent implements OnInit {
} }
private verifyEmail(oobCode: string, email: string): void { private verifyEmail(oobCode: string, email: string): void {
this.http.post(`${environment.apiBaseUrl}/bizmatch/auth/verify-email`, { oobCode, email }).subscribe({ this.http.post<{ message: string; token: string }>(`${environment.apiBaseUrl}/bizmatch/auth/verify-email`, { oobCode, email }).subscribe({
next: async () => { next: async response => {
this.verificationStatus = 'success'; this.verificationStatus = 'success';
//await this.authService.refreshToken();
await this.authService.refreshUserClaims(); try {
// Use the custom token from the server to sign in with Firebase
await this.authService.signInWithCustomToken(response.token);
// Try to get user info
try {
const user = await this.userService.getByMail(email); const user = await this.userService.getByMail(email);
console.log('User retrieved:', user);
} catch (userError) {
console.error('Error getting user:', userError);
// Don't change verification status - it's still a success
}
// Redirect to dashboard after a short delay
setTimeout(() => {
this.router.navigate(['/account']);
}, 5000);
} catch (authError) {
console.error('Error signing in with custom token:', authError);
// Keep success status for verification, but add warning about login
this.errorMessage = 'Email verified, but there was an issue signing you in. Please try logging in manually.';
}
}, },
error: err => { error: err => {
this.verificationStatus = 'error'; this.verificationStatus = 'error';

View File

@ -2,7 +2,7 @@
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core'; import { Injectable, inject } from '@angular/core';
import { FirebaseApp } from '@angular/fire/app'; import { FirebaseApp } from '@angular/fire/app';
import { GoogleAuthProvider, UserCredential, createUserWithEmailAndPassword, getAuth, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth'; import { GoogleAuthProvider, UserCredential, createUserWithEmailAndPassword, getAuth, signInWithCustomToken, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
import { BehaviorSubject, Observable, catchError, firstValueFrom, map, of, shareReplay, take, tap } from 'rxjs'; import { BehaviorSubject, Observable, catchError, firstValueFrom, map, of, shareReplay, take, tap } from 'rxjs';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { MailService } from './mail.service'; import { MailService } from './mail.service';
@ -274,4 +274,31 @@ export class AuthService {
return await this.refreshToken(); return await this.refreshToken();
} }
} }
// Add this new method to sign in with a custom token
async signInWithCustomToken(token: string): Promise<void> {
try {
// Sign in to Firebase with the custom token
const userCredential = await signInWithCustomToken(this.auth, token);
// Store the authentication token
if (userCredential.user) {
const idToken = await userCredential.user.getIdToken();
localStorage.setItem('authToken', idToken);
localStorage.setItem('refreshToken', userCredential.user.refreshToken);
if (userCredential.user.photoURL) {
localStorage.setItem('photoURL', userCredential.user.photoURL);
}
// Load user role from the token
this.loadRoleFromToken();
}
return;
} catch (error) {
console.error('Error signing in with custom token:', error);
throw error;
}
}
} }