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 { HeaderComponent } from '../header/header.component';
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 { 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 { TranslateModule } from '@ngx-translate/core';
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 { SharedService } from '../../../../services/shared.service';
import { Subscription } from 'rxjs';

View file

@ -20,10 +20,10 @@
placeholder="{{ 'login.email' | translate }}"
class="custom-input"
autocomplete="email"
[(ngModel)]="loginData.mail"
[(ngModel)]="loginData.email"
required
/>
@if(!loginData.mail) {
@if(!loginData.email) {
<img
class="custom-img"
src="./../../../assets/img/login/mail.svg"
@ -39,7 +39,7 @@
!sharedService.isBtnDisabled) {
<p>{{ "login.errorMail1" | translate }}</p>
} @if (mail.valid && mail.touched &&
!checkIfUserEmailIsValid(loginData.mail.toLowerCase()) &&
!checkIfUserEmailIsValid(loginData.email.toLowerCase()) &&
!sharedService.isBtnDisabled) {
<p>{{ "login.errorMail2" | translate }}</p>
}
@ -87,6 +87,7 @@
}}</a>
</div>
</div>
<!--
<div class="google-button">
<app-form-btn
[class]="'btn-google'"
@ -95,7 +96,8 @@
[disabled]="sharedService.isBtnDisabled"
(click)="googleLogin()"
></app-form-btn>
</div>
</div>
-->
<div class="form-buttons">
<app-form-btn
[class]="'btn-login'"
@ -103,11 +105,11 @@
[value]="'login.login' | translate"
[disabled]="
mail.invalid ||
!loginData.mail ||
!loginData.email ||
password.invalid ||
!loginData.password ||
sharedService.isBtnDisabled ||
!checkIfUserEmailIsValid(loginData.mail)
!checkIfUserEmailIsValid(loginData.email)
"
></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 { FirebaseService } from '../../services/firebase.service';
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 { FooterComponent } from './footer/footer.component';
import { HeaderComponent } from './header/header.component';
import { TranslateModule } from '@ngx-translate/core';
import { LoadingDialogComponent } from './loading-dialog/loading-dialog.component';
import { OverlayService } from '../../services/overlay.service';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-login',
@ -32,12 +33,12 @@ export class LoginComponent {
isPasswordIconVisible: boolean = true;
loginData = {
mail: '',
email: '',
password: '',
};
constructor(
private firebaseService: FirebaseService,
private authService: AuthService,
public loginService: LoginService,
public sharedService: SharedService,
private overlayService: OverlayService,
@ -46,9 +47,6 @@ export class LoginComponent {
) {}
ngOnInit() {
if (this.loginService.checkAuthUser()) {
this.router.navigate(['/summary']);
}
this.routeParams();
}
@ -63,16 +61,21 @@ export class LoginComponent {
});
}
onSubmit(ngForm: NgForm) {
async onSubmit(ngForm: NgForm) {
this.sharedService.isBtnDisabled = true;
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() {
this.sharedService.isBtnDisabled = true;
this.loginData.mail = 'guest@guestaccount.com';
this.loginData.email = 'guest@guestaccount.com';
this.loginData.password = 'guest@guestaccount.com';
this.isPasswordIconVisible = !this.isPasswordIconVisible;
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 { FormBtnComponent } from '../../../shared/components/buttons/form-btn/form-btn.component';
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 { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component';
import { RouterModule } from '@angular/router';

View file

@ -1,16 +1,22 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, map, Observable, of } from 'rxjs';
import { LoginService } from '../services/auth.service';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root',
})
export class AuthenticatedGuard {
constructor(private loginService: LoginService, private router: Router) {}
constructor(private authService: AuthService, private router: Router) {}
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) => {
if (isAuthenticated) {
return true;

View file

@ -2,16 +2,21 @@ import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { LoginService } from '../services/auth.service';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root',
})
export class RedirectIfAuthenticatedGuard {
constructor(private loginService: LoginService, private router: Router) {}
constructor(private authService: AuthService, private router: Router) {}
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) => {
if (isAuthenticated) {
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 {
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';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError, lastValueFrom, map, Observable, of } from 'rxjs';
import { apiConfig } from '../environments/config';
@Injectable({
providedIn: 'root',
})
export class LoginService {
firestore: Firestore = inject(Firestore);
passwordFieldType: string = 'password';
passwordIcon: string = './../../../assets/img/login/close-eye.svg';
errorCode: string = '';
export class AuthService {
private apiUrl = apiConfig.apiUrl;
private secretKey: string = CryptoESSecretKey.secretKey;
constructor(private http: HttpClient) {}
constructor(
private firebaseService: FirebaseService,
public sharedService: SharedService,
private router: Router
) {}
async login(body: any, storage: boolean) {
const data = (await lastValueFrom(
this.http.post(`${this.apiUrl}/auth/login/`, body)
)) 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> {
return this.firebaseService.getAuthUser().pipe(
map((user) => {
if (user) {
return true;
} else {
return false;
}
const headers = this.getAuthHeaders();
return this.http.get<any>(`${this.apiUrl}/auth/`, { headers }).pipe(
map(() => {
return true;
}),
catchError(() => {
return of(false);
})
catchError(() => 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);
private getAuthHeaders(): HttpHeaders {
let authToken = localStorage.getItem('authToken');
if (!authToken) {
authToken = sessionStorage.getItem('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(),
return new HttpHeaders({
Authorization: `Token ${authToken}`,
});
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();
private storeAuthToken(data: any, storage: boolean) {
storage
? localStorage.setItem('authToken', data.toString())
: sessionStorage.setItem('authToken', data.toString());
}
/**
* 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';
private deleteAuthToken() {
localStorage.removeItem('authToken');
sessionStorage.removeItem('authToken');
}
/**
* 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;
});
checkAuthToken(): string | null {
return (
localStorage.getItem('authToken') || sessionStorage.getItem('authToken')
);
}
}

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