Firebase authentication completed

This commit is contained in:
Anish Gurung 2024-03-20 11:37:59 -07:00
parent b1e895f69a
commit c26e0c99f1
11 changed files with 277 additions and 19 deletions

View file

@ -1,9 +1,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { ExtraOptions, RouterModule, Routes } from '@angular/router'; import { ExtraOptions, RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './service/auth-guard.service';
export const routes: Routes = [ export const routes: Routes = [
{ {
path: 'pages', path: 'pages',
canActivate: [AuthGuard],
loadChildren: () => import('./pages/pages.module') loadChildren: () => import('./pages/pages.module')
.then(m => m.PagesModule), .then(m => m.PagesModule),
}, },
@ -11,7 +13,6 @@ export const routes: Routes = [
path: 'auth', path: 'auth',
loadChildren: () => import('./auth/auth.module').then(m => m.NgxAuthModule) loadChildren: () => import('./auth/auth.module').then(m => m.NgxAuthModule)
}, },
{ path: '', redirectTo: 'auth', pathMatch: 'full' },
{ path: '**', redirectTo: 'pages' }, { path: '**', redirectTo: 'pages' },
]; ];

View file

@ -1,8 +1,3 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire/compat'; import { AngularFireModule } from '@angular/fire/compat';
@ -22,6 +17,9 @@ import { CoreModule } from './@core/core.module';
import { ThemeModule } from './@theme/theme.module'; import { ThemeModule } from './@theme/theme.module';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AngularFireAuthModule } from '@angular/fire/compat/auth';
import { AuthGuard } from './service/auth-guard.service';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
@ -31,6 +29,7 @@ import { AppComponent } from './app.component';
HttpClientModule, HttpClientModule,
AppRoutingModule, AppRoutingModule,
AngularFireModule.initializeApp(environment.firebase), AngularFireModule.initializeApp(environment.firebase),
AngularFireAuthModule,
NbSidebarModule.forRoot(), NbSidebarModule.forRoot(),
NbMenuModule.forRoot(), NbMenuModule.forRoot(),
NbDatepickerModule.forRoot(), NbDatepickerModule.forRoot(),
@ -43,6 +42,9 @@ import { AppComponent } from './app.component';
CoreModule.forRoot(), CoreModule.forRoot(),
ThemeModule.forRoot(), ThemeModule.forRoot(),
], ],
providers: [
AuthGuard
],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { export class AppModule {

View file

@ -0,0 +1,72 @@
import { NbAuthStrategyOptions, NbAuthJWTToken, NbPasswordStrategyModule, NbPasswordStrategyMessage } from "@nebular/auth";
export class NbFirebasePasswordStrategyOptions extends NbAuthStrategyOptions {
name: string = 'email';
token = {
class: NbAuthJWTToken,
};
register?: boolean | NbPasswordStrategyModule = {
redirect: {
success: '/auth/login',
failure: null,
},
defaultErrors: ['Something went wrong, please try again.'],
defaultMessages: ['You have been successfully registered.'],
};
login?: boolean | NbPasswordStrategyModule = {
redirect: {
success: '/dashboard',
failure: null,
},
defaultErrors: ['Login/Email combination is not correct, please try again.'],
defaultMessages: ['You have been successfully logged in.'],
};
logout?: boolean | NbPasswordStrategyModule = {
redirect: {
success: '/',
failure: null,
},
defaultErrors: ['Something went wrong, please try again.'],
defaultMessages: ['You have been successfully logged out.'],
};
refreshToken?: boolean | NbPasswordStrategyModule = {
redirect: {
success: null,
failure: null,
},
defaultErrors: ['Something went wrong, please try again.'],
defaultMessages: ['Your token has been successfully refreshed.'],
};
// requestPassword?: boolean | NbPasswordStrategyModule = {
// redirect: {
// success: '/',
// failure: null,
// },
// defaultErrors: ['Something went wrong, please try again.'],
// defaultMessages: ['Reset password instructions have been sent to your email.'],
// };
// resetPassword?: boolean | NbPasswordStrategyModule = {
// redirect: {
// success: '/',
// failure: null,
// },
// defaultErrors: ['Something went wrong, please try again.'],
// defaultMessages: ['Your password has been successfully changed.'],
// };
// errors?: NbPasswordStrategyMessage = {
// key: 'message',
// getter: (module: string, res, options: NbFirebasePasswordStrategyOptions) => getDeepFromObject(
// res,
// options.errors.key,
// options[module].defaultErrors,
// ),
// };
// messages?: NbPasswordStrategyMessage = {
// key: 'messages',
// getter: (module: string, res, options: NbFirebasePasswordStrategyOptions) => getDeepFromObject(
// res.body,
// options.messages.key,
// options[module].defaultMessages,
// ),
// };
}

View file

@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { NgxAuthRoutingModule } from './auth-routing.module'; import { NgxAuthRoutingModule } from './auth-routing.module';
import { NbAuthModule } from '@nebular/auth'; import { NbAuthModule, NbPasswordAuthStrategy } from '@nebular/auth';
import { import {
NbAlertModule, NbAlertModule,
NbButtonModule, NbButtonModule,
@ -13,6 +13,7 @@ import {
} from '@nebular/theme'; } from '@nebular/theme';
import { NgxLoginComponent } from './login/login.component'; import { NgxLoginComponent } from './login/login.component';
import { NgxRegisterComponent } from './register/register.component'; import { NgxRegisterComponent } from './register/register.component';
import { NbFirebasePasswordStrategyOptions } from './auth-firebase.config';
@NgModule({ @NgModule({
@ -26,7 +27,29 @@ import { NgxRegisterComponent } from './register/register.component';
NbCheckboxModule, NbCheckboxModule,
NgxAuthRoutingModule, NgxAuthRoutingModule,
NbAuthModule, NbAuthModule.forRoot({
strategies: [
NbPasswordAuthStrategy.setup({
name: 'email',
login: {
redirect: {
success: '/dashboard',
failure: null, // stay on the same page
},
},
register: {
redirect: {
success: '/auth/login',
failure: null, // stay on the same page
},
},
...new NbFirebasePasswordStrategyOptions()
}),
],
forms: {},
}),
], ],
declarations: [ declarations: [
// ... here goes our new components // ... here goes our new components

View file

@ -1,7 +1,7 @@
<h1 id="title" class="title">Login</h1> <h1 id="title" class="title">Login</h1>
<p class="sub-title">Welcome to RESUME TAILOR!!!</p> <p class="sub-title">Welcome to RESUME TAILOR!!!</p>
<nb-alert <!-- <nb-alert
*ngIf="showMessages.error && errors?.length && !submitted" *ngIf="showMessages.error && errors?.length && !submitted"
outline="danger" outline="danger"
role="alert" role="alert"
@ -10,9 +10,9 @@
<ul class="alert-message-list"> <ul class="alert-message-list">
<li *ngFor="let error of errors" class="alert-message">{{ error }}</li> <li *ngFor="let error of errors" class="alert-message">{{ error }}</li>
</ul> </ul>
</nb-alert> </nb-alert> -->
<nb-alert <!-- <nb-alert
*ngIf="showMessages.success && messages?.length && !submitted" *ngIf="showMessages.success && messages?.length && !submitted"
outline="success" outline="success"
role="alert" role="alert"
@ -23,7 +23,7 @@
{{ message }} {{ message }}
</li> </li>
</ul> </ul>
</nb-alert> </nb-alert> -->
<form (ngSubmit)="login()" #form="ngForm" aria-labelledby="title"> <form (ngSubmit)="login()" #form="ngForm" aria-labelledby="title">
<div class="form-control-group"> <div class="form-control-group">

View file

@ -1,4 +1,7 @@
import { Component } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import { Router } from '@angular/router'; // Import Router from @angular/router
import { NbAuthService } from '@nebular/auth';
import { NbLoginComponent } from '@nebular/auth'; import { NbLoginComponent } from '@nebular/auth';
@Component({ @Component({
@ -6,4 +9,63 @@ import { NbLoginComponent } from '@nebular/auth';
templateUrl: './login.component.html', templateUrl: './login.component.html',
}) })
export class NgxLoginComponent extends NbLoginComponent { export class NgxLoginComponent extends NbLoginComponent {
} constructor(
private fireAuth: AngularFireAuth,
private authService: NbAuthService,
public cd: ChangeDetectorRef, // Change to public or protected if NbLoginComponent's cd is public or protected
public router: Router // Import Router from @angular/router
) {
super(authService, {}, cd, router);
}
async login() {
try {
await this.fireAuth.signInWithEmailAndPassword(this.user.email, this.user.password);
const user = await this.fireAuth.currentUser; // Get current user
if (user) {
const accessToken = await user.getIdToken(); // Get Firebase Authentication token (access token)
const expiresIn = new Date().getTime() + 3600 * 1000; // Token expiration time (1 hour)
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('accessTokenExpiresIn', expiresIn.toString()); // Store token expiration time
this.router.navigate(['dashboard']); // Redirect to dashboard
this.scheduleTokenRefresh(); // Schedule token refresh
}
console.log("Successfully Logged in.")
} catch (error) {
// Handle login errors here
console.error('Error signing in:', error);
// You can also display error messages to the user using this.errors array
this.errors = ['Error signing in. Please try again.'];
}
}
private async scheduleTokenRefresh(): Promise<void> {
const expiresInString = localStorage.getItem('accessTokenExpiresIn');
if (expiresInString) {
const expiresIn = +expiresInString;
const timeUntilRefresh = expiresIn - new Date().getTime() - 300000; // Refresh token 5 minutes before expiration
if (timeUntilRefresh > 0) {
setTimeout(async () => {
try {
const user = await this.fireAuth.currentUser;
if (user) {
const newAccessToken = await user.getIdToken();
localStorage.setItem('accessToken', newAccessToken);
const newExpiresIn = new Date().getTime() + 3600 * 1000; // Extend expiration time by 1 hour
localStorage.setItem('accessTokenExpiresIn', newExpiresIn.toString());
this.scheduleTokenRefresh();
}
} catch (error) {
console.error('Token refresh failed:', error);
}
}, timeUntilRefresh);
}
} else {
console.error('Access token expiration time is not available.');
}
}
getConfigValue(key: string): any {
// Implement this method based on your configuration retrieval logic
}
}

View file

@ -1,6 +1,6 @@
<h1 id="title" class="title">Register</h1> <h1 id="title" class="title">Register</h1>
<nb-alert <!-- <nb-alert
*ngIf="showMessages.error && errors?.length && !submitted" *ngIf="showMessages.error && errors?.length && !submitted"
outline="danger" outline="danger"
role="alert" role="alert"
@ -22,7 +22,7 @@
{{ message }} {{ message }}
</li> </li>
</ul> </ul>
</nb-alert> </nb-alert> -->
<form (ngSubmit)="register()" #form="ngForm" aria-labelledby="title"> <form (ngSubmit)="register()" #form="ngForm" aria-labelledby="title">
<div class="form-control-group"> <div class="form-control-group">

View file

@ -1,9 +1,42 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { NbRegisterComponent } from '@nebular/auth'; import { AngularFireAuth } from '@angular/fire/compat/auth';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'ngx-register', selector: 'ngx-register',
templateUrl: './register.component.html', templateUrl: './register.component.html',
}) })
export class NgxRegisterComponent extends NbRegisterComponent { export class NgxRegisterComponent {
} user = {
fullName: '',
email: '',
password: '',
confirmPassword: ''
};
submitted = false;
errors: string[] = [];
constructor(private fireAuth: AngularFireAuth, private router: Router) {}
async register() {
this.submitted = true;
try {
if (this.user.password !== this.user.confirmPassword) {
throw new Error('Password does not match the confirm password.');
}
await this.fireAuth.createUserWithEmailAndPassword(this.user.email, this.user.password);
// Registration successful, redirect to login page or dashboard
this.router.navigate(['auth/login']);
} catch (error) {
// Handle registration errors
console.error('Error registering user:', error);
this.errors.push(error.message || 'An error occurred. Please try again later.');
}
this.submitted = false;
}
getConfigValue(key: string): any {
// Implement this method based on your configuration retrieval logic
}
}

View file

@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const accessToken = localStorage.getItem('accessToken');
const accessTokenExpiresIn = localStorage.getItem('accessTokenExpiresIn');
// Check if access token and expiration time exist
if (accessToken && accessTokenExpiresIn) {
// Check if the access token is not expired
const currentTime = new Date().getTime();
const expiresIn = +accessTokenExpiresIn;
if (currentTime < expiresIn) {
return true; // Allow access to the route
}
}
// If access token is not found or expired, navigate to login page
this.router.navigate(['/auth/login']);
return false;
}
}

View file

@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthService {
user$: Observable<firebase.default.User | null>;
constructor(private fireAuth: AngularFireAuth, private router: Router) {
this.user$ = this.fireAuth.authState;
}
// logout method
async logout(): Promise<void> {
try {
await this.fireAuth.signOut();
localStorage.removeItem('accessToken');
localStorage.removeItem('accessTokenExpiresIn');
this.router.navigate(['dashboard'], { queryParams: { logout: true } });
} catch (error: any) {
throw error;
}
}
}

View file

@ -0,0 +1,6 @@
export interface ResponseWrapper<T> {
success: boolean;
message: string;
data: T;
error?: any;
}