From 0764094ac1ffde46da28ddf55189ea62ec2304e3 Mon Sep 17 00:00:00 2001 From: New Date: Mon, 6 Feb 2023 11:11:53 +1000 Subject: [PATCH] feat: authentication system added a dual-purpose login/register page and guarded JournalPage from unauthenticated Users. included menu with logout button. --- src/app/app-routing.module.ts | 9 +++ src/app/app.module.ts | 29 +++++++-- src/app/auth/login/login-routing.module.ts | 17 ++++++ src/app/auth/login/login.module.ts | 16 +++++ src/app/auth/login/login.page.html | 39 ++++++++++++ src/app/auth/login/login.page.scss | 4 ++ src/app/auth/login/login.page.spec.ts | 24 ++++++++ src/app/auth/login/login.page.ts | 70 ++++++++++++++++++++++ src/app/journal/journal.page.html | 18 +++++- src/app/journal/journal.page.ts | 19 +++++- src/app/services/auth.service.spec.ts | 16 +++++ src/app/services/auth.service.ts | 39 ++++++++++++ 12 files changed, 292 insertions(+), 8 deletions(-) create mode 100644 src/app/auth/login/login-routing.module.ts create mode 100644 src/app/auth/login/login.module.ts create mode 100644 src/app/auth/login/login.page.html create mode 100644 src/app/auth/login/login.page.scss create mode 100644 src/app/auth/login/login.page.spec.ts create mode 100644 src/app/auth/login/login.page.ts create mode 100644 src/app/services/auth.service.spec.ts create mode 100644 src/app/services/auth.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d10b433..2efedef 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,11 +1,20 @@ import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; +import { canActivate, redirectUnauthorizedTo } from '@angular/fire/auth-guard'; + +const redirectUnauthorized = () => redirectUnauthorizedTo(['login']); const routes: Routes = [ { path: 'journal', loadChildren: () => import('./journal/journal.module').then((m) => m.JournalPageModule), + ...canActivate(redirectUnauthorized), + }, + { + path: 'login', + loadChildren: () => + import('./auth/login/login.module').then((m) => m.LoginPageModule), }, { path: '', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1da2a13..50b9235 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,16 +6,33 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; -import { initializeApp,provideFirebaseApp } from '@angular/fire/app'; +import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; import { environment } from '../environments/environment'; -import { provideAnalytics,getAnalytics,ScreenTrackingService,UserTrackingService } from '@angular/fire/analytics'; -import { provideAuth,getAuth } from '@angular/fire/auth'; -import { provideFirestore,getFirestore } from '@angular/fire/firestore'; +import { + provideAnalytics, + getAnalytics, + ScreenTrackingService, + UserTrackingService, +} from '@angular/fire/analytics'; +import { provideAuth, getAuth } from '@angular/fire/auth'; +import { provideFirestore, getFirestore } from '@angular/fire/firestore'; @NgModule({ declarations: [AppComponent], - imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, provideFirebaseApp(() => initializeApp(environment.firebase)), provideAnalytics(() => getAnalytics()), provideAuth(() => getAuth()), provideFirestore(() => getFirestore())], - providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, ScreenTrackingService,UserTrackingService], + imports: [ + BrowserModule, + IonicModule.forRoot(), + AppRoutingModule, + provideFirebaseApp(() => initializeApp(environment.firebase)), + provideAnalytics(() => getAnalytics()), + provideAuth(() => getAuth()), + provideFirestore(() => getFirestore()), + ], + providers: [ + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + ScreenTrackingService, + UserTrackingService, + ], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/src/app/auth/login/login-routing.module.ts b/src/app/auth/login/login-routing.module.ts new file mode 100644 index 0000000..29ef3a2 --- /dev/null +++ b/src/app/auth/login/login-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { LoginPage } from './login.page'; + +const routes: Routes = [ + { + path: '', + component: LoginPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class LoginPageRoutingModule {} diff --git a/src/app/auth/login/login.module.ts b/src/app/auth/login/login.module.ts new file mode 100644 index 0000000..8b705ec --- /dev/null +++ b/src/app/auth/login/login.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { LoginPageRoutingModule } from './login-routing.module'; + +import { LoginPage } from './login.page'; +import { SharedModule } from 'src/app/shared/shared.module'; + +@NgModule({ + imports: [SharedModule, LoginPageRoutingModule], + declarations: [LoginPage], +}) +export class LoginPageModule {} diff --git a/src/app/auth/login/login.page.html b/src/app/auth/login/login.page.html new file mode 100644 index 0000000..dea905b --- /dev/null +++ b/src/app/auth/login/login.page.html @@ -0,0 +1,39 @@ + + + Authenticate yourself! + + + + +
+ + + Email: + + + + Password: + + + + Log In +
+ Register +
+
diff --git a/src/app/auth/login/login.page.scss b/src/app/auth/login/login.page.scss new file mode 100644 index 0000000..5e141e2 --- /dev/null +++ b/src/app/auth/login/login.page.scss @@ -0,0 +1,4 @@ +form { + margin: auto; + max-width: 500px; +} diff --git a/src/app/auth/login/login.page.spec.ts b/src/app/auth/login/login.page.spec.ts new file mode 100644 index 0000000..e6f9aac --- /dev/null +++ b/src/app/auth/login/login.page.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { LoginPage } from './login.page'; + +describe('LoginPage', () => { + let component: LoginPage; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ LoginPage ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(LoginPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/login/login.page.ts b/src/app/auth/login/login.page.ts new file mode 100644 index 0000000..c79b76e --- /dev/null +++ b/src/app/auth/login/login.page.ts @@ -0,0 +1,70 @@ +// NOTE this is a dual purpose login/register page. Usually you'd split the two obviously but for the sake of time... + +import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { AlertController } from '@ionic/angular'; +import { AuthService } from 'src/app/services/auth.service'; +import { LoadingService } from 'src/app/services/loading.service'; + +@Component({ + selector: 'app-login', + templateUrl: './login.page.html', + styleUrls: ['./login.page.scss'], +}) +export class LoginPage implements OnInit { + form = new FormGroup({ + email: new FormControl('', [Validators.required, Validators.email]), + password: new FormControl('', [ + Validators.required, + Validators.minLength(8), + ]), + }); + + constructor( + private auth: AuthService, + private router: Router, + private loadingService: LoadingService, + private alert: AlertController + ) {} + + ngOnInit() {} + + async login() { + await this.loadingService.create('Logging you in...'); + try { + const user = await this.auth.login( + this.form.value as { email: string; password: string } + ); + this.loadingService.dismiss(); + this.router.navigate(['/']); + } catch (error) { + // TODO proper handling of this error, give the Users an explanation/way to resolve + this.loadingService.dismiss(); + const alert = await this.alert.create({ + message: 'Failed to log you in. Check your credentials.', + buttons: ['OK'], + }); + alert.present(); + } + } + + async register() { + await this.loadingService.create('Signing you up...'); + try { + const user = await this.auth.register( + this.form.value as { email: string; password: string } + ); + this.loadingService.dismiss(); + this.router.navigate(['/']); + } catch (error) { + // TODO proper handling of this error, give the Users an explanation/way to resolve + this.loadingService.dismiss(); + const alert = await this.alert.create({ + message: 'Failed to register. Try again with a different email.', + buttons: ['OK'], + }); + alert.present(); + } + } +} diff --git a/src/app/journal/journal.page.html b/src/app/journal/journal.page.html index 7a0dc0e..dbc9b26 100644 --- a/src/app/journal/journal.page.html +++ b/src/app/journal/journal.page.html @@ -1,10 +1,26 @@ + + + + Menu + + + + Logout + + + + + + Diar.io - +

Welcome to Diar.io!

diff --git a/src/app/journal/journal.page.ts b/src/app/journal/journal.page.ts index f521fca..1d92ac8 100644 --- a/src/app/journal/journal.page.ts +++ b/src/app/journal/journal.page.ts @@ -2,6 +2,9 @@ import { Component, OnInit } from '@angular/core'; import { Base } from '../shared/components/base.component'; import { ModalController } from '@ionic/angular'; import { WriteEntryPage } from './write-entry/write-entry.page'; +import { AuthService } from '../services/auth.service'; +import { LoadingService } from '../services/loading.service'; +import { Router } from '@angular/router'; @Component({ selector: 'app-journal', @@ -9,7 +12,12 @@ import { WriteEntryPage } from './write-entry/write-entry.page'; styleUrls: ['./journal.page.scss'], }) export class JournalPage extends Base implements OnInit { - constructor(private modalController: ModalController) { + constructor( + private modalController: ModalController, + private auth: AuthService, + private loading: LoadingService, + private router: Router + ) { super(); } @@ -22,4 +30,13 @@ export class JournalPage extends Base implements OnInit { }); modal.present(); } + + async logout() { + await this.loading.create('Logging you out...'); + try { + await this.auth.logout(); + this.router.navigate(['/', 'login']); + } catch (error) {} + this.loading.dismiss(); + } } diff --git a/src/app/services/auth.service.spec.ts b/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000..f1251ca --- /dev/null +++ b/src/app/services/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..fa9f7a2 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { + Auth, + UserCredential, + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + signOut, +} from '@angular/fire/auth'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + constructor(private auth: Auth) {} + + register(creds: { + email: string; + password: string; + }): Promise { + // try { + + // } catch (error) { + + // } + return createUserWithEmailAndPassword( + this.auth, + creds.email, + creds.password + ); + } + + login(creds: { email: string; password: string }): Promise { + return signInWithEmailAndPassword(this.auth, creds.email, creds.password); + } + + logout(): Promise { + return signOut(this.auth); + } +}