feat: migrate login, logout, and auth guards to Django Rest

This commit is contained in:
Chneemann 2025-03-23 19:58:26 +01:00
parent 4b795ae977
commit 93000fd4c7
11 changed files with 457 additions and 366 deletions

View file

@ -5,7 +5,7 @@ import { FormBtnComponent } from '../../../shared/components/buttons/form-btn/fo
import { FooterComponent } from '../footer/footer.component'; import { FooterComponent } from '../footer/footer.component';
import { HeaderComponent } from '../header/header.component'; import { HeaderComponent } from '../header/header.component';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LoginService } from '../../../services/auth.service'; import { LoginService } from '../../../services/login.service';
import { SharedService } from '../../../services/shared.service'; import { SharedService } from '../../../services/shared.service';
import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component'; import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component';

View file

@ -6,7 +6,7 @@ import { FooterComponent } from '../../footer/footer.component';
import { HeaderComponent } from '../../header/header.component'; import { HeaderComponent } from '../../header/header.component';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { FirebaseService } from '../../../../services/firebase.service'; import { FirebaseService } from '../../../../services/firebase.service';
import { LoginService } from '../../../../services/auth.service'; import { LoginService } from '../../../../services/login.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { SharedService } from '../../../../services/shared.service'; import { SharedService } from '../../../../services/shared.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';

View file

@ -20,10 +20,10 @@
placeholder="{{ 'login.email' | translate }}" placeholder="{{ 'login.email' | translate }}"
class="custom-input" class="custom-input"
autocomplete="email" autocomplete="email"
[(ngModel)]="loginData.mail" [(ngModel)]="loginData.email"
required required
/> />
@if(!loginData.mail) { @if(!loginData.email) {
<img <img
class="custom-img" class="custom-img"
src="./../../../assets/img/login/mail.svg" src="./../../../assets/img/login/mail.svg"
@ -39,7 +39,7 @@
!sharedService.isBtnDisabled) { !sharedService.isBtnDisabled) {
<p>{{ "login.errorMail1" | translate }}</p> <p>{{ "login.errorMail1" | translate }}</p>
} @if (mail.valid && mail.touched && } @if (mail.valid && mail.touched &&
!checkIfUserEmailIsValid(loginData.mail.toLowerCase()) && !checkIfUserEmailIsValid(loginData.email.toLowerCase()) &&
!sharedService.isBtnDisabled) { !sharedService.isBtnDisabled) {
<p>{{ "login.errorMail2" | translate }}</p> <p>{{ "login.errorMail2" | translate }}</p>
} }
@ -87,6 +87,7 @@
}}</a> }}</a>
</div> </div>
</div> </div>
<!--
<div class="google-button"> <div class="google-button">
<app-form-btn <app-form-btn
[class]="'btn-google'" [class]="'btn-google'"
@ -96,6 +97,7 @@
(click)="googleLogin()" (click)="googleLogin()"
></app-form-btn> ></app-form-btn>
</div> </div>
-->
<div class="form-buttons"> <div class="form-buttons">
<app-form-btn <app-form-btn
[class]="'btn-login'" [class]="'btn-login'"
@ -103,11 +105,11 @@
[value]="'login.login' | translate" [value]="'login.login' | translate"
[disabled]=" [disabled]="
mail.invalid || mail.invalid ||
!loginData.mail || !loginData.email ||
password.invalid || password.invalid ||
!loginData.password || !loginData.password ||
sharedService.isBtnDisabled || sharedService.isBtnDisabled ||
!checkIfUserEmailIsValid(loginData.mail) !checkIfUserEmailIsValid(loginData.email)
" "
></app-form-btn> ></app-form-btn>
<app-form-btn <app-form-btn

View file

@ -4,13 +4,14 @@ import { FormsModule, NgForm } from '@angular/forms';
import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component'; import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component';
import { FirebaseService } from '../../services/firebase.service'; import { FirebaseService } from '../../services/firebase.service';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { LoginService } from '../../services/auth.service'; import { LoginService } from '../../services/login.service';
import { SharedService } from '../../services/shared.service'; import { SharedService } from '../../services/shared.service';
import { FooterComponent } from './footer/footer.component'; import { FooterComponent } from './footer/footer.component';
import { HeaderComponent } from './header/header.component'; import { HeaderComponent } from './header/header.component';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LoadingDialogComponent } from './loading-dialog/loading-dialog.component'; import { LoadingDialogComponent } from './loading-dialog/loading-dialog.component';
import { OverlayService } from '../../services/overlay.service'; import { OverlayService } from '../../services/overlay.service';
import { AuthService } from '../../services/auth.service';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@ -32,12 +33,12 @@ export class LoginComponent {
isPasswordIconVisible: boolean = true; isPasswordIconVisible: boolean = true;
loginData = { loginData = {
mail: '', email: '',
password: '', password: '',
}; };
constructor( constructor(
private firebaseService: FirebaseService, private authService: AuthService,
public loginService: LoginService, public loginService: LoginService,
public sharedService: SharedService, public sharedService: SharedService,
private overlayService: OverlayService, private overlayService: OverlayService,
@ -46,9 +47,6 @@ export class LoginComponent {
) {} ) {}
ngOnInit() { ngOnInit() {
if (this.loginService.checkAuthUser()) {
this.router.navigate(['/summary']);
}
this.routeParams(); this.routeParams();
} }
@ -63,16 +61,21 @@ export class LoginComponent {
}); });
} }
onSubmit(ngForm: NgForm) { async onSubmit(ngForm: NgForm) {
this.sharedService.isBtnDisabled = true; this.sharedService.isBtnDisabled = true;
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
this.loginService.login(this.loginData); try {
await this.authService.login(this.loginData, true);
this.router.navigate(['/summary']);
} catch (error) {
this.sharedService.isBtnDisabled = false;
}
} }
} }
guestLogin() { guestLogin() {
this.sharedService.isBtnDisabled = true; this.sharedService.isBtnDisabled = true;
this.loginData.mail = 'guest@guestaccount.com'; this.loginData.email = 'guest@guestaccount.com';
this.loginData.password = 'guest@guestaccount.com'; this.loginData.password = 'guest@guestaccount.com';
this.isPasswordIconVisible = !this.isPasswordIconVisible; this.isPasswordIconVisible = !this.isPasswordIconVisible;
this.onSubmit({ submitted: true, form: { valid: true } } as NgForm); this.onSubmit({ submitted: true, form: { valid: true } } as NgForm);

View file

@ -5,7 +5,7 @@ import { FormsModule, NgForm } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormBtnComponent } from '../../../shared/components/buttons/form-btn/form-btn.component'; import { FormBtnComponent } from '../../../shared/components/buttons/form-btn/form-btn.component';
import { FirebaseService } from '../../../services/firebase.service'; import { FirebaseService } from '../../../services/firebase.service';
import { LoginService } from '../../../services/auth.service'; import { LoginService } from '../../../services/login.service';
import { SharedService } from '../../../services/shared.service'; import { SharedService } from '../../../services/shared.service';
import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component'; import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';

View file

@ -1,16 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { catchError, map, Observable, of } from 'rxjs'; import { catchError, map, Observable, of } from 'rxjs';
import { LoginService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthenticatedGuard { export class AuthenticatedGuard {
constructor(private loginService: LoginService, private router: Router) {} constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> | Promise<boolean> | boolean { canActivate(): Observable<boolean> | Promise<boolean> | boolean {
return this.loginService.checkAuthUser().pipe( const authToken = this.authService.checkAuthToken();
if (!authToken) {
this.router.navigate(['/login']);
return false;
}
return this.authService.checkAuthUser().pipe(
map((isAuthenticated) => { map((isAuthenticated) => {
if (isAuthenticated) { if (isAuthenticated) {
return true; return true;

View file

@ -2,16 +2,21 @@ import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators'; import { map, catchError } from 'rxjs/operators';
import { LoginService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class RedirectIfAuthenticatedGuard { export class RedirectIfAuthenticatedGuard {
constructor(private loginService: LoginService, private router: Router) {} constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> | Promise<boolean> | boolean { canActivate(): Observable<boolean> | Promise<boolean> | boolean {
return this.loginService.checkAuthUser().pipe( const authToken = this.authService.checkAuthToken();
if (!authToken) {
return true;
}
return this.authService.checkAuthUser().pipe(
map((isAuthenticated) => { map((isAuthenticated) => {
if (isAuthenticated) { if (isAuthenticated) {
this.router.navigate(['/summary']); this.router.navigate(['/summary']);

390
src/app/services/auth.service.ts Normal file → Executable file
View file

@ -1,361 +1,73 @@
import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { import { Injectable } from '@angular/core';
getAuth, import { catchError, lastValueFrom, map, Observable, of } from 'rxjs';
signInWithEmailAndPassword, import { apiConfig } from '../environments/config';
signInWithPopup,
GoogleAuthProvider,
createUserWithEmailAndPassword,
sendPasswordResetEmail,
confirmPasswordReset,
} from 'firebase/auth';
import { FirebaseService } from './firebase.service';
import {
Firestore,
addDoc,
collection,
doc,
getDocs,
query,
updateDoc,
where,
} from '@angular/fire/firestore';
import { SharedService } from './shared.service';
import { User } from '../interfaces/user.interface';
import { Router } from '@angular/router';
import CryptoES from 'crypto-es';
import { CryptoESSecretKey } from '../environments/config';
import { catchError, map, Observable, of } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class LoginService { export class AuthService {
firestore: Firestore = inject(Firestore); private apiUrl = apiConfig.apiUrl;
passwordFieldType: string = 'password';
passwordIcon: string = './../../../assets/img/login/close-eye.svg';
errorCode: string = '';
private secretKey: string = CryptoESSecretKey.secretKey; constructor(private http: HttpClient) {}
constructor( async login(body: any, storage: boolean) {
private firebaseService: FirebaseService, const data = (await lastValueFrom(
public sharedService: SharedService, this.http.post(`${this.apiUrl}/auth/login/`, body)
private router: Router )) as { token: string };
) {} this.storeAuthToken(data.token, storage);
}
async logout() {
const token = this.checkAuthToken();
if (!token) return;
await lastValueFrom(
this.http.post(
`${this.apiUrl}/auth/logout/`,
{},
{ headers: { Authorization: `Token ${token}` } }
)
).catch((error) => console.error('Logout failed:', error));
this.deleteAuthToken();
window.location.href = '/login';
}
/**
* Checks if a user is logged in.
*
* @returns An Observable resolving to a boolean indicating whether a user is logged in.
*/
checkAuthUser(): Observable<boolean> { checkAuthUser(): Observable<boolean> {
return this.firebaseService.getAuthUser().pipe( const headers = this.getAuthHeaders();
map((user) => { return this.http.get<any>(`${this.apiUrl}/auth/`, { headers }).pipe(
if (user) { map(() => {
return true; return true;
} else {
return false;
}
}), }),
catchError(() => { catchError(() => of(false))
return of(false);
})
); );
} }
/** private getAuthHeaders(): HttpHeaders {
* Logs in a user given their email and password. let authToken = localStorage.getItem('authToken');
* @param loginData An object with properties `mail` and `password`. if (!authToken) {
* @returns A Promise resolving to void. authToken = sessionStorage.getItem('authToken');
* @throws An error if the login attempt fails.
*/
login(loginData: { mail: string; password: string }) {
signInWithEmailAndPassword(getAuth(), loginData.mail, loginData.password)
.then((userCredential) => {
const user = userCredential.user;
const userData = this.firebaseService.getUserDataFromUid(user.uid);
if (userData.length > 0 && userData[0].id) {
this.getUserIdInLocalStorage(userData[0].id);
this.updateUserOnlineStatus(userData[0].id, true);
}
})
.catch((error) => {
this.errorCode = error.code;
this.sharedService.isBtnDisabled = false;
});
}
/**
* Registers a new user given their name, first name, last name, email and password.
* @param registerData An object with properties `name`, `firstName`, `lastName`, `mail` and `password`.
* @returns A Promise resolving to void.
* @throws An error if the registration attempt fails.
*/
register(registerData: {
name: string;
firstName: string;
lastName: string;
mail: string;
password: string;
}) {
createUserWithEmailAndPassword(
getAuth(),
registerData.mail,
registerData.password
)
.then((userCredential) => {
const user = userCredential.user;
const userDataToSave: User = {
uId: user.uid,
email: user.email || '',
firstName: registerData.firstName
? registerData.firstName.charAt(0).toUpperCase() +
registerData.firstName.slice(1)
: '',
lastName: registerData.lastName
? registerData.lastName.charAt(0).toUpperCase() +
registerData.lastName.slice(1)
: '',
status: true,
phone: '',
initials: registerData.name
? registerData.firstName.slice(0, 1).toUpperCase() +
registerData.lastName.slice(0, 1).toUpperCase()
: '',
color: this.sharedService.generateRandomColor(),
lastLogin: new Date().getTime(),
};
this.createUserInFirestore(userDataToSave);
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
/**
* Google login method.
*
* Signs in the user with Google Authentication.
* If the user is not in the database, creates a new user with the provided name and email.
* If the user is already in the database, logins the user.
*
* @returns {Promise<void>}
* @memberof LoginService
*/
googleLogin() {
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((userCredential) => {
const user = userCredential.user;
const usersCollection = collection(this.firestore, 'users');
const querySnapshot = query(
usersCollection,
where('uId', '==', user.uid)
);
getDocs(querySnapshot).then((snapshot) => {
if (snapshot.empty) {
const displayName = user.displayName || '';
const [firstName = '', lastName = ''] = displayName.split(' ');
this.createUserInFirestore({
uId: user.uid,
email: user.email || 'no mail',
firstName: firstName.charAt(0).toUpperCase() + firstName.slice(1),
lastName: lastName.charAt(0).toUpperCase() + lastName.slice(1),
status: true,
phone: '',
initials: firstName.slice(0, 1) + lastName.slice(0, 1),
color: this.sharedService.generateRandomColor(),
lastLogin: 0,
});
} else {
this.ifExistUser(user.uid);
}
});
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
/**
* Creates a new user in the Firestore database with the given user data.
* If the user already exists in the database, the method does nothing.
* @param user The user data to save. The user data should contain the following properties: `uId`, `email`, `firstName`, `lastName`, `initials`, `color`.
* @returns A Promise resolving to void.
*/
async createUserInFirestore(user: User) {
const userDataToSave: User = {
uId: user.uId,
email: user.email,
firstName: user.firstName || '',
lastName: user.lastName || '',
status: true,
phone: '',
initials: user.initials,
color: user.color,
lastLogin: new Date().getTime(),
};
const usersCollection = collection(this.firestore, 'users');
try {
const docRef = await addDoc(usersCollection, userDataToSave);
this.ifExistUser(user.uId);
} catch (error) {
console.error(error);
} }
} return new HttpHeaders({
Authorization: `Token ${authToken}`,
/**
* Checks if a user with the given user id exists in the database.
* If the user exists, the method saves the user's id in the local storage and
* updates the user's online status in the database.
* @param user The id of the user to check.
* @returns void
*/
ifExistUser(user: string) {
const userData = this.firebaseService.getUserDataFromUid(user);
if (userData.length > 0 && userData[0].id) {
this.getUserIdInLocalStorage(userData[0].id);
this.updateUserOnlineStatus(userData[0].id, true);
}
}
/**
* Updates the online status of the user with the given id in the Firestore database.
* If the user's status is set to true, the user's lastLogin property is also updated
* to the current time.
* @param userId The id of the user to update.
* @param status The new online status of the user.
* @returns A Promise resolving to void.
*/
async updateUserOnlineStatus(userId: string, status: boolean) {
await updateDoc(doc(collection(this.firestore, 'users'), userId), {
status: status,
lastLogin: new Date().getTime(),
}); });
window.location.reload();
} }
/** private storeAuthToken(data: any, storage: boolean) {
* Toggles the visibility of the password field. storage
* ? localStorage.setItem('authToken', data.toString())
* This function switches the input type between 'password' and 'text', : sessionStorage.setItem('authToken', data.toString());
* allowing the user to view or hide the password as needed.
* It also updates the icon to reflect the current visibility state.
*/
togglePasswordVisibility() {
this.passwordFieldType =
this.passwordFieldType === 'password' ? 'text' : 'password';
this.toggleIcon();
} }
/** private deleteAuthToken() {
* Toggles the password visibility icon. localStorage.removeItem('authToken');
* sessionStorage.removeItem('authToken');
* This function switches the icon src between the 'close eye' and 'open eye'
* icons, indicating whether the password is hidden or visible.
*/
toggleIcon() {
this.passwordIcon =
this.passwordIcon === './../../../assets/img/login/close-eye.svg'
? './../../../assets/img/login/open-eye.svg'
: './../../../assets/img/login/close-eye.svg';
} }
/** checkAuthToken(): string | null {
* Saves the given userId to local storage, encrypted with the secret key. return (
* localStorage.getItem('authToken') || sessionStorage.getItem('authToken')
* The userId is first stringified and then encrypted with the secret key. );
* The encrypted string is then saved to local storage under the key
* 'currentUserJOIN'. Additionally, the current timestamp is saved to local
* storage under the key 'sessionTimeJOIN'.
*
* @param userId The id of the user to be saved.
*/
getUserIdInLocalStorage(userId: string): void {
const encryptedValue = CryptoES.AES.encrypt(
JSON.stringify(userId),
this.secretKey
).toString();
localStorage.setItem('currentUserJOIN', encryptedValue);
localStorage.setItem('sessionTimeJOIN', new Date().getTime().toString());
}
// LOGOUT
/**
* Logs out the current user by deleting the user ID from local storage and
* updating the user's online status to offline in the Firestore database.
*
* @param userId The id of the user to log out.
*/
logout(userId: string) {
this.deleteUserIdInLocalStorage();
this.updateUserOnlineStatus(userId, false);
}
/**
* Deletes the user ID from local storage, effectively logging the user out.
*
* This function removes the user ID from local storage by deleting the
* 'currentUserJOIN' and 'sessionTimeJOIN' items.
*/
deleteUserIdInLocalStorage() {
localStorage.removeItem('currentUserJOIN');
localStorage.removeItem('sessionTimeJOIN');
}
// FORGOT PASSWORD
/**
* Initiates the password reset process for the given email address.
*
* This function sends a password reset email to the specified email address
* using Firebase Authentication. Upon successful sending of the email, the
* user is redirected to the password reset notice page. If there is an error,
* the button will be re-enabled for the user to try again.
*
* @param email The email address of the user requesting a password reset.
*/
passwordReset(email: string) {
const auth = getAuth();
const actionCodeSettings = {
url: 'https://join.andre-kempf.com/',
handleCodeInApp: true,
};
sendPasswordResetEmail(auth, email, actionCodeSettings)
.then(() => {
this.router.navigate(['/login/notice/pw-send']);
this.sharedService.isBtnDisabled = false;
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
/**
* Resets the user's password given the new password and the oobCode from the
* password reset email.
*
* This function sends a request to Firebase Authentication to reset the user's
* password. If the request is successful, it navigates to the password reset
* notice page and re-enables the submit button. If there is an error, the
* button will be re-enabled for the user to try again.
*
* @param newPW The new password to be set for the user.
* @param oobCode The one-time code from the password reset email.
*/
newPassword(newPW: string, oobCode: string) {
const auth = getAuth();
confirmPasswordReset(auth, oobCode, newPW)
.then(() => {
this.router.navigate(['/login/notice/pw-change']);
this.sharedService.isBtnDisabled = false;
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
} }
} }

View file

@ -0,0 +1,361 @@
import { Injectable, inject } from '@angular/core';
import {
getAuth,
signInWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
createUserWithEmailAndPassword,
sendPasswordResetEmail,
confirmPasswordReset,
} from 'firebase/auth';
import { FirebaseService } from './firebase.service';
import {
Firestore,
addDoc,
collection,
doc,
getDocs,
query,
updateDoc,
where,
} from '@angular/fire/firestore';
import { SharedService } from './shared.service';
import { User } from '../interfaces/user.interface';
import { Router } from '@angular/router';
import CryptoES from 'crypto-es';
import { CryptoESSecretKey } from '../environments/config';
import { catchError, map, Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class LoginService {
firestore: Firestore = inject(Firestore);
passwordFieldType: string = 'password';
passwordIcon: string = './../../../assets/img/login/close-eye.svg';
errorCode: string = '';
private secretKey: string = CryptoESSecretKey.secretKey;
constructor(
private firebaseService: FirebaseService,
public sharedService: SharedService,
private router: Router
) {}
/**
* Checks if a user is logged in.
*
* @returns An Observable resolving to a boolean indicating whether a user is logged in.
*/
checkAuthUser(): Observable<boolean> {
return this.firebaseService.getAuthUser().pipe(
map((user) => {
if (user) {
return true;
} else {
return false;
}
}),
catchError(() => {
return of(false);
})
);
}
/**
* Logs in a user given their email and password.
* @param loginData An object with properties `mail` and `password`.
* @returns A Promise resolving to void.
* @throws An error if the login attempt fails.
*/
login(loginData: { mail: string; password: string }) {
signInWithEmailAndPassword(getAuth(), loginData.mail, loginData.password)
.then((userCredential) => {
const user = userCredential.user;
const userData = this.firebaseService.getUserDataFromUid(user.uid);
if (userData.length > 0 && userData[0].id) {
this.getUserIdInLocalStorage(userData[0].id);
this.updateUserOnlineStatus(userData[0].id, true);
}
})
.catch((error) => {
this.errorCode = error.code;
this.sharedService.isBtnDisabled = false;
});
}
/**
* Registers a new user given their name, first name, last name, email and password.
* @param registerData An object with properties `name`, `firstName`, `lastName`, `mail` and `password`.
* @returns A Promise resolving to void.
* @throws An error if the registration attempt fails.
*/
register(registerData: {
name: string;
firstName: string;
lastName: string;
mail: string;
password: string;
}) {
createUserWithEmailAndPassword(
getAuth(),
registerData.mail,
registerData.password
)
.then((userCredential) => {
const user = userCredential.user;
const userDataToSave: User = {
uId: user.uid,
email: user.email || '',
firstName: registerData.firstName
? registerData.firstName.charAt(0).toUpperCase() +
registerData.firstName.slice(1)
: '',
lastName: registerData.lastName
? registerData.lastName.charAt(0).toUpperCase() +
registerData.lastName.slice(1)
: '',
status: true,
phone: '',
initials: registerData.name
? registerData.firstName.slice(0, 1).toUpperCase() +
registerData.lastName.slice(0, 1).toUpperCase()
: '',
color: this.sharedService.generateRandomColor(),
lastLogin: new Date().getTime(),
};
this.createUserInFirestore(userDataToSave);
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
/**
* Google login method.
*
* Signs in the user with Google Authentication.
* If the user is not in the database, creates a new user with the provided name and email.
* If the user is already in the database, logins the user.
*
* @returns {Promise<void>}
* @memberof LoginService
*/
googleLogin() {
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((userCredential) => {
const user = userCredential.user;
const usersCollection = collection(this.firestore, 'users');
const querySnapshot = query(
usersCollection,
where('uId', '==', user.uid)
);
getDocs(querySnapshot).then((snapshot) => {
if (snapshot.empty) {
const displayName = user.displayName || '';
const [firstName = '', lastName = ''] = displayName.split(' ');
this.createUserInFirestore({
uId: user.uid,
email: user.email || 'no mail',
firstName: firstName.charAt(0).toUpperCase() + firstName.slice(1),
lastName: lastName.charAt(0).toUpperCase() + lastName.slice(1),
status: true,
phone: '',
initials: firstName.slice(0, 1) + lastName.slice(0, 1),
color: this.sharedService.generateRandomColor(),
lastLogin: 0,
});
} else {
this.ifExistUser(user.uid);
}
});
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
/**
* Creates a new user in the Firestore database with the given user data.
* If the user already exists in the database, the method does nothing.
* @param user The user data to save. The user data should contain the following properties: `uId`, `email`, `firstName`, `lastName`, `initials`, `color`.
* @returns A Promise resolving to void.
*/
async createUserInFirestore(user: User) {
const userDataToSave: User = {
uId: user.uId,
email: user.email,
firstName: user.firstName || '',
lastName: user.lastName || '',
status: true,
phone: '',
initials: user.initials,
color: user.color,
lastLogin: new Date().getTime(),
};
const usersCollection = collection(this.firestore, 'users');
try {
const docRef = await addDoc(usersCollection, userDataToSave);
this.ifExistUser(user.uId);
} catch (error) {
console.error(error);
}
}
/**
* Checks if a user with the given user id exists in the database.
* If the user exists, the method saves the user's id in the local storage and
* updates the user's online status in the database.
* @param user The id of the user to check.
* @returns void
*/
ifExistUser(user: string) {
const userData = this.firebaseService.getUserDataFromUid(user);
if (userData.length > 0 && userData[0].id) {
this.getUserIdInLocalStorage(userData[0].id);
this.updateUserOnlineStatus(userData[0].id, true);
}
}
/**
* Updates the online status of the user with the given id in the Firestore database.
* If the user's status is set to true, the user's lastLogin property is also updated
* to the current time.
* @param userId The id of the user to update.
* @param status The new online status of the user.
* @returns A Promise resolving to void.
*/
async updateUserOnlineStatus(userId: string, status: boolean) {
await updateDoc(doc(collection(this.firestore, 'users'), userId), {
status: status,
lastLogin: new Date().getTime(),
});
window.location.reload();
}
/**
* Toggles the visibility of the password field.
*
* This function switches the input type between 'password' and 'text',
* allowing the user to view or hide the password as needed.
* It also updates the icon to reflect the current visibility state.
*/
togglePasswordVisibility() {
this.passwordFieldType =
this.passwordFieldType === 'password' ? 'text' : 'password';
this.toggleIcon();
}
/**
* Toggles the password visibility icon.
*
* This function switches the icon src between the 'close eye' and 'open eye'
* icons, indicating whether the password is hidden or visible.
*/
toggleIcon() {
this.passwordIcon =
this.passwordIcon === './../../../assets/img/login/close-eye.svg'
? './../../../assets/img/login/open-eye.svg'
: './../../../assets/img/login/close-eye.svg';
}
/**
* Saves the given userId to local storage, encrypted with the secret key.
*
* The userId is first stringified and then encrypted with the secret key.
* The encrypted string is then saved to local storage under the key
* 'currentUserJOIN'. Additionally, the current timestamp is saved to local
* storage under the key 'sessionTimeJOIN'.
*
* @param userId The id of the user to be saved.
*/
getUserIdInLocalStorage(userId: string): void {
const encryptedValue = CryptoES.AES.encrypt(
JSON.stringify(userId),
this.secretKey
).toString();
localStorage.setItem('currentUserJOIN', encryptedValue);
localStorage.setItem('sessionTimeJOIN', new Date().getTime().toString());
}
// LOGOUT
/**
* Logs out the current user by deleting the user ID from local storage and
* updating the user's online status to offline in the Firestore database.
*
* @param userId The id of the user to log out.
*/
logout(userId: string) {
this.deleteUserIdInLocalStorage();
this.updateUserOnlineStatus(userId, false);
}
/**
* Deletes the user ID from local storage, effectively logging the user out.
*
* This function removes the user ID from local storage by deleting the
* 'currentUserJOIN' and 'sessionTimeJOIN' items.
*/
deleteUserIdInLocalStorage() {
localStorage.removeItem('currentUserJOIN');
localStorage.removeItem('sessionTimeJOIN');
}
// FORGOT PASSWORD
/**
* Initiates the password reset process for the given email address.
*
* This function sends a password reset email to the specified email address
* using Firebase Authentication. Upon successful sending of the email, the
* user is redirected to the password reset notice page. If there is an error,
* the button will be re-enabled for the user to try again.
*
* @param email The email address of the user requesting a password reset.
*/
passwordReset(email: string) {
const auth = getAuth();
const actionCodeSettings = {
url: 'https://join.andre-kempf.com/',
handleCodeInApp: true,
};
sendPasswordResetEmail(auth, email, actionCodeSettings)
.then(() => {
this.router.navigate(['/login/notice/pw-send']);
this.sharedService.isBtnDisabled = false;
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
/**
* Resets the user's password given the new password and the oobCode from the
* password reset email.
*
* This function sends a request to Firebase Authentication to reset the user's
* password. If the request is successful, it navigates to the password reset
* notice page and re-enables the submit button. If there is an error, the
* button will be re-enabled for the user to try again.
*
* @param newPW The new password to be set for the user.
* @param oobCode The one-time code from the password reset email.
*/
newPassword(newPW: string, oobCode: string) {
const auth = getAuth();
confirmPasswordReset(auth, oobCode, newPW)
.then(() => {
this.router.navigate(['/login/notice/pw-change']);
this.sharedService.isBtnDisabled = false;
})
.catch((error) => {
this.sharedService.isBtnDisabled = false;
});
}
}

View file

@ -3,8 +3,9 @@ import { Router, RouterModule } from '@angular/router';
import { LanguageService } from '../../../../services/language.service'; import { LanguageService } from '../../../../services/language.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LoginService } from '../../../../services/auth.service'; import { LoginService } from '../../../../services/login.service';
import { FirebaseService } from '../../../../services/firebase.service'; import { FirebaseService } from '../../../../services/firebase.service';
import { AuthService } from '../../../../services/auth.service';
@Component({ @Component({
selector: 'app-navbar', selector: 'app-navbar',
@ -22,6 +23,7 @@ export class NavbarComponent {
constructor( constructor(
public langService: LanguageService, public langService: LanguageService,
private router: Router, private router: Router,
private authService: AuthService,
private loginService: LoginService, private loginService: LoginService,
private firebaseService: FirebaseService private firebaseService: FirebaseService
) {} ) {}
@ -45,6 +47,6 @@ export class NavbarComponent {
* The user is then navigated to the login page. * The user is then navigated to the login page.
*/ */
logout() { logout() {
this.loginService.logout(this.firebaseService.getCurrentUserId()); this.authService.logout();
} }
} }

View file

@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core';
import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LanguageService } from '../../../services/language.service'; import { LanguageService } from '../../../services/language.service';
import { LoginService } from '../../../services/auth.service'; import { LoginService } from '../../../services/login.service';
import { FirebaseService } from '../../../services/firebase.service'; import { FirebaseService } from '../../../services/firebase.service';
@Component({ @Component({