feat: migrate login, logout, and auth guards to Django Rest
This commit is contained in:
parent
4b795ae977
commit
93000fd4c7
11 changed files with 457 additions and 366 deletions
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
390
src/app/services/auth.service.ts
Normal file → Executable 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')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
361
src/app/services/login.service.ts
Normal file
361
src/app/services/login.service.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in a new issue