#84 - Add login page as part of SPA instead of relying on external page
This commit is contained in:
parent
aad0aea79a
commit
0fecb8a4ba
@ -1,5 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
|
||||
import {LoginPage} from './pages/login/login.page';
|
||||
import {AuthService} from './service/auth.service';
|
||||
import {GuestOnlyGuard} from './service/guard/GuestOnly.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -9,15 +12,18 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
canActivate: [AuthService],
|
||||
loadChildren: () => import('./home/home.module').then(m => m.HomePageModule)
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
loadChildren: () => import('./list/list.module').then(m => m.ListPageModule)
|
||||
path: 'editor',
|
||||
canActivate: [AuthService],
|
||||
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
||||
},
|
||||
{
|
||||
path: 'editor',
|
||||
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
||||
path: 'login',
|
||||
canActivate: [GuestOnlyGuard],
|
||||
component: LoginPage,
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -24,6 +24,7 @@ import {NavigationService} from './service/navigation.service';
|
||||
import {DatabaseService} from './service/db/database.service';
|
||||
import {EditorService} from './service/editor.service';
|
||||
import {debug} from './utility';
|
||||
import {AuthService} from './service/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -96,7 +97,6 @@ export class AppComponent implements OnInit {
|
||||
protected showedNewVersionAlert = false;
|
||||
protected showedOfflineAlert = false;
|
||||
protected backbuttonSubscription: any;
|
||||
protected initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor(
|
||||
private platform: Platform,
|
||||
@ -114,6 +114,7 @@ export class AppComponent implements OnInit {
|
||||
protected toasts: ToastController,
|
||||
protected db: DatabaseService,
|
||||
protected editor: EditorService,
|
||||
protected auth: AuthService,
|
||||
) { }
|
||||
|
||||
async checkNewVersion() {
|
||||
@ -479,9 +480,11 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
async initializeApp() {
|
||||
const initializedOnce = this.navService.initialized$.getValue();
|
||||
|
||||
debug('app', this);
|
||||
this.loader = await this.loading.create({
|
||||
message: 'Starting up...',
|
||||
message: 'Setting things up...',
|
||||
cssClass: 'noded-loading-mask',
|
||||
showBackdrop: true,
|
||||
});
|
||||
@ -493,37 +496,33 @@ export class AppComponent implements OnInit {
|
||||
|
||||
let toast: any;
|
||||
|
||||
debug('Subscribing to offline changes...');
|
||||
this.api.offline$.subscribe(async isOffline => {
|
||||
if ( isOffline && !this.showedOfflineAlert ) {
|
||||
debug('Application went offline!');
|
||||
toast = await this.toasts.create({
|
||||
cssClass: 'compat-toast-container',
|
||||
message: 'Uh, oh! It looks like you\'re offline. Some features might not work as expected...',
|
||||
});
|
||||
if ( !initializedOnce ) {
|
||||
debug('Subscribing to offline changes...');
|
||||
this.api.offline$.subscribe(async isOffline => {
|
||||
if ( isOffline && !this.showedOfflineAlert ) {
|
||||
debug('Application went offline!');
|
||||
toast = await this.toasts.create({
|
||||
cssClass: 'compat-toast-container',
|
||||
message: 'Uh, oh! It looks like you\'re offline. Some features might not work as expected...',
|
||||
});
|
||||
|
||||
this.showedOfflineAlert = true;
|
||||
await toast.present();
|
||||
} else if ( !isOffline && this.showedOfflineAlert ) {
|
||||
debug('Appliation went online!');
|
||||
await toast.dismiss();
|
||||
this.showedOfflineAlert = false;
|
||||
await this.api.syncOfflineData();
|
||||
}
|
||||
});
|
||||
this.showedOfflineAlert = true;
|
||||
await toast.present();
|
||||
} else if ( !isOffline && this.showedOfflineAlert ) {
|
||||
debug('Appliation went online!');
|
||||
await toast.dismiss();
|
||||
this.showedOfflineAlert = false;
|
||||
await this.api.syncOfflineData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debug('Getting initial status...');
|
||||
let stat: any = await this.session.stat();
|
||||
debug('Got stat:', stat);
|
||||
|
||||
if ( stat.public_user ) {
|
||||
this.api.isPublicUser = true;
|
||||
}
|
||||
|
||||
if ( stat.authenticated_user ) {
|
||||
this.api.isAuthenticated = true;
|
||||
}
|
||||
|
||||
this.api.isPublicUser = !!stat.public_user;
|
||||
this.api.isAuthenticated = !!stat.authenticated_user;
|
||||
this.api.systemBase = stat.system_base;
|
||||
|
||||
if ( !this.api.isAuthenticated || this.api.isPublicUser ) {
|
||||
@ -558,7 +557,7 @@ export class AppComponent implements OnInit {
|
||||
debug('Initializing session...');
|
||||
await this.session.initialize();
|
||||
|
||||
if ( this.session.get('user.preferences.dark_mode') ) {
|
||||
if ( this.session.get('user.preferences.dark_mode') && !this.darkMode ) {
|
||||
this.toggleDark();
|
||||
}
|
||||
|
||||
@ -603,7 +602,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.initialized$.next(true);
|
||||
this.navService.initialized$.next(true);
|
||||
|
||||
if ( !this.api.isPublicUser && this.session.get('user.preferences.default_page') ) {
|
||||
debug('Navigating to default page!');
|
||||
@ -611,24 +610,38 @@ export class AppComponent implements OnInit {
|
||||
const node = this.findNode(id);
|
||||
if ( node ) {
|
||||
this.navigateEditorToNode(node);
|
||||
} else if ( this.auth.authInProgress ) {
|
||||
await this.router.navigate(['/home']);
|
||||
}
|
||||
} else if ( this.auth.authInProgress ) {
|
||||
await this.router.navigate(['/home']);
|
||||
}
|
||||
|
||||
debug('Creating menu subscription...');
|
||||
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
||||
this.onMenuRefresh(quiet);
|
||||
});
|
||||
if ( !initializedOnce ) {
|
||||
debug('Creating menu subscription...');
|
||||
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
||||
this.onMenuRefresh(quiet);
|
||||
});
|
||||
|
||||
this.navService.navigationRequest$.subscribe(pageId => {
|
||||
debug('Page navigation request: ', {pageId});
|
||||
if ( !pageId ) {
|
||||
debug('Empty page ID. Will not navigate.');
|
||||
return;
|
||||
}
|
||||
this.navService.navigationRequest$.subscribe(pageId => {
|
||||
debug('Page navigation request: ', {pageId});
|
||||
if ( !pageId ) {
|
||||
debug('Empty page ID. Will not navigate.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentPageId = pageId;
|
||||
this.router.navigate(['/editor', { id: pageId }]);
|
||||
});
|
||||
this.currentPageId = pageId;
|
||||
this.router.navigate(['/editor', { id: pageId }]);
|
||||
});
|
||||
|
||||
this.navService.initializationRequest$.subscribe((count) => {
|
||||
if ( count === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initializeApp();
|
||||
});
|
||||
}
|
||||
|
||||
debug('Reloading menu items...');
|
||||
this.reloadMenuItems().subscribe(() => {
|
||||
@ -647,6 +660,8 @@ export class AppComponent implements OnInit {
|
||||
}, 1000 * 60 * 5); // Check for new version every 5 mins
|
||||
}
|
||||
});
|
||||
|
||||
this.auth.authInProgress = false;
|
||||
}
|
||||
|
||||
async doPrefetch() {
|
||||
|
@ -34,6 +34,7 @@ import {MarkdownModule} from 'ngx-markdown';
|
||||
import {VersionModalComponent} from './version-modal/version-modal.component';
|
||||
import {EditorPageRoutingModule} from '../pages/editor/editor-routing.module';
|
||||
import {EditorPage} from '../pages/editor/editor.page';
|
||||
import {LoginPage} from '../pages/login/login.page';
|
||||
import {WysiwygComponent} from './wysiwyg/wysiwyg.component';
|
||||
import {WysiwygEditorComponent} from './editor/database/editors/wysiwyg/wysiwyg-editor.component';
|
||||
import {WysiwygModalComponent} from './editor/database/editors/wysiwyg/wysiwyg-modal.component';
|
||||
@ -76,6 +77,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
||||
MarkdownEditorComponent,
|
||||
VersionModalComponent,
|
||||
EditorPage,
|
||||
LoginPage,
|
||||
WysiwygComponent,
|
||||
WysiwygEditorComponent,
|
||||
WysiwygModalComponent,
|
||||
@ -130,6 +132,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
||||
MarkdownEditorComponent,
|
||||
VersionModalComponent,
|
||||
EditorPage,
|
||||
LoginPage,
|
||||
WysiwygComponent,
|
||||
WysiwygEditorComponent,
|
||||
WysiwygModalComponent,
|
||||
@ -170,6 +173,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
||||
MarkdownEditorComponent,
|
||||
VersionModalComponent,
|
||||
EditorPage,
|
||||
LoginPage,
|
||||
WysiwygComponent,
|
||||
WysiwygEditorComponent,
|
||||
WysiwygModalComponent,
|
||||
|
@ -33,7 +33,6 @@ export class BooleanRendererComponent implements ICellRendererAngularComp {
|
||||
) { }
|
||||
|
||||
agInit(params: ICellRendererParams): void {
|
||||
console.log('bool renderer', this);
|
||||
this.params = params;
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -3,6 +3,7 @@ import {Router} from '@angular/router';
|
||||
import {ApiService} from '../../service/api.service';
|
||||
import {PopoverController} from '@ionic/angular';
|
||||
import {DatabaseService} from '../../service/db/database.service';
|
||||
import {AuthService} from '../../service/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-option-picker',
|
||||
@ -22,6 +23,7 @@ export class OptionPickerComponent implements OnInit {
|
||||
protected router: Router,
|
||||
protected popover: PopoverController,
|
||||
protected db: DatabaseService,
|
||||
protected auth: AuthService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {}
|
||||
@ -30,8 +32,8 @@ export class OptionPickerComponent implements OnInit {
|
||||
if ( key === 'html_export' ) {
|
||||
window.open(this.api._build_url('/data/export/html'), '_blank');
|
||||
} else if ( key === 'logout' ) {
|
||||
await this.db.purge();
|
||||
window.location.href = '/auth/logout';
|
||||
await this.popover.dismiss();
|
||||
await this.auth.endSession();
|
||||
} else if ( key === 'toggle_darkmode' ) {
|
||||
this.toggleDark();
|
||||
} else if ( key === 'search_everywhere' ) {
|
||||
|
@ -8,7 +8,6 @@ import {isDebug, debug} from '../utility';
|
||||
styleUrls: ['home.page.scss'],
|
||||
})
|
||||
export class HomePage implements OnInit {
|
||||
|
||||
constructor(
|
||||
public readonly api: ApiService,
|
||||
) {}
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { ListPage } from './list.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: ListPage
|
||||
}
|
||||
])
|
||||
],
|
||||
declarations: [ListPage]
|
||||
})
|
||||
export class ListPageModule {}
|
@ -1,27 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
List
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item *ngFor="let item of items">
|
||||
<ion-icon [name]="item.icon" slot="start"></ion-icon>
|
||||
{{item.title}}
|
||||
<div class="item-note" slot="end">
|
||||
{{item.note}}
|
||||
</div>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<!--
|
||||
<div *ngIf="selectedItem" padding>
|
||||
You navigated here from <b>{{selectedItem.title }}</b>
|
||||
</div>
|
||||
-->
|
||||
</ion-content>
|
@ -1 +0,0 @@
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { ListPage } from './list.page';
|
||||
|
||||
describe('ListPage', () => {
|
||||
let component: ListPage;
|
||||
let fixture: ComponentFixture<ListPage>;
|
||||
let listPage: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListPage ],
|
||||
imports: [IonicModule.forRoot()]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ListPage);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have a list of 10 elements', () => {
|
||||
listPage = fixture.nativeElement;
|
||||
const items = listPage.querySelectorAll('ion-item');
|
||||
expect(items.length).toEqual(10);
|
||||
});
|
||||
|
||||
});
|
@ -1,39 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: 'list.page.html',
|
||||
styleUrls: ['list.page.scss']
|
||||
})
|
||||
export class ListPage implements OnInit {
|
||||
private selectedItem: any;
|
||||
private icons = [
|
||||
'flask',
|
||||
'wifi',
|
||||
'beer',
|
||||
'football',
|
||||
'basketball',
|
||||
'paper-plane',
|
||||
'american-football',
|
||||
'boat',
|
||||
'bluetooth',
|
||||
'build'
|
||||
];
|
||||
public items: Array<{ title: string; note: string; icon: string }> = [];
|
||||
constructor() {
|
||||
for (let i = 1; i < 11; i++) {
|
||||
this.items.push({
|
||||
title: 'Item ' + i,
|
||||
note: 'This is item #' + i,
|
||||
icon: this.icons[Math.floor(Math.random() * this.icons.length)]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
// add back when alpha.4 is out
|
||||
// navigate(item) {
|
||||
// this.router.navigate(['/list', JSON.stringify(item)]);
|
||||
// }
|
||||
}
|
94
src/app/pages/login/login.page.html
Normal file
94
src/app/pages/login/login.page.html
Normal file
@ -0,0 +1,94 @@
|
||||
<div class="login-container">
|
||||
<ion-grid class="ion-align-items-center">
|
||||
<ion-row class="ion-align-items-center" style="height: 100%" *ngIf="step === 'greeter'">
|
||||
<ion-col class="ion-align-items-end" style="text-align: right">
|
||||
<img src="/assets/icon/logo_lines.svg" alt="Noded" width="200">
|
||||
</ion-col>
|
||||
<ion-col style="padding: 40px;">
|
||||
<div class="hero" style="font-size: 2em;"><b style="font-size: 1.5em;">Noded</b><br>is your information, organized.</div>
|
||||
<button
|
||||
(click)="advance()"
|
||||
title="Click to get started"
|
||||
style="padding: 20px 0; margin: 0; color: #3171e0"
|
||||
>Get started <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row class="ion-align-items-center" style="height: 100%" *ngIf="step === 'username' || step === 'password' || step === 'create-account'">
|
||||
<ion-col class="ion-align-items-center" style="text-align: center">
|
||||
<img src="/assets/icon/logo_lines.svg" alt="Noded" height="100" style="margin-bottom: 20px;">
|
||||
<p class="message">{{ stepMessages[step] }}</p>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
#usernameInput
|
||||
[(ngModel)]="username"
|
||||
[disabled]="step !== 'username'"
|
||||
placeholder="Username"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'password'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
#passwordInput
|
||||
[(ngModel)]="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'create-account'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
#nameInput
|
||||
[(ngModel)]="fullName"
|
||||
placeholder="Full Name"
|
||||
type="text"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'create-account'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
[(ngModel)]="password"
|
||||
placeholder="Create a Password"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'create-account'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
[(ngModel)]="passwordConfirm"
|
||||
placeholder="Confirm Password"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<div class="buttons" style="text-align: right;">
|
||||
<div class="button" style="display: flex; flex-direction: row;" *ngIf="step !== 'username'">
|
||||
<button
|
||||
(click)="back()"
|
||||
style="text-align: left; margin: 20px 0 0 0;"
|
||||
><i class="fa fa-chevron-left" style="font-size: 0.8em; margin-left: 10px; color: #3171e0"></i></button>
|
||||
<button
|
||||
(click)="advance()"
|
||||
title="Click to sign-in to the system"
|
||||
style="flex: 1; padding: 20px 0 0 0; margin: 0; text-align: right;"
|
||||
[ngStyle]="{'color': ((!password || (step !== 'password' && (password !== passwordConfirm || !fullName))) ? '#666' : '#3171e0')}"
|
||||
[disabled]="!password || (step !== 'password' && (password !== passwordConfirm || !fullName))"
|
||||
>{{ step === 'password' ? 'Sign-in' : 'Create account' }} <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</div>
|
||||
<div class="button" *ngIf="step === 'username'">
|
||||
<button
|
||||
(click)="advance()"
|
||||
title="Click to continue"
|
||||
style="padding: 20px 0 0 0; margin: 0;"
|
||||
[ngStyle]="{'color': (username ? '#3171e0' : '#666')}"
|
||||
[disabled]="!username"
|
||||
>Continue <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</div>
|
||||
<div class="button">
|
||||
<button
|
||||
(click)="coreid()"
|
||||
*ngIf="step === 'username'"
|
||||
title="Click to sign-in with Starship CoreID instead"
|
||||
style="padding: 20px 0 0 0; margin: 0; color: #3171e0;"
|
||||
>Sign-in with CoreID <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="error-message" *ngIf="errorMessage" style="color: darkred; font-size: 0.9em; margin-top: 30px;">{{ errorMessage }}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</div>
|
5
src/app/pages/login/login.page.scss
Normal file
5
src/app/pages/login/login.page.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.login-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
175
src/app/pages/login/login.page.ts
Normal file
175
src/app/pages/login/login.page.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController, IonInput, LoadingController, ModalController, PopoverController} from '@ionic/angular';
|
||||
import {EditorService} from '../../service/editor.service';
|
||||
import {ApiService} from '../../service/api.service';
|
||||
import {NavigationService} from '../../service/navigation.service';
|
||||
import {AuthService} from '../../service/auth.service';
|
||||
|
||||
export type LoginPageStep = 'greeter' | 'username' | 'password' | 'create-account';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.page.html',
|
||||
styleUrls: ['./login.page.scss'],
|
||||
})
|
||||
export class LoginPage implements OnInit {
|
||||
public step: LoginPageStep = 'greeter';
|
||||
@ViewChild('usernameInput') usernameInput: IonInput;
|
||||
@ViewChild('passwordInput') passwordInput: IonInput;
|
||||
@ViewChild('nameInput') nameInput: IonInput;
|
||||
|
||||
stepMessages = {
|
||||
username: 'Enter a username to continue',
|
||||
password: 'Enter your password to sign-in',
|
||||
'create-account': 'Welcome! Create an account to continue',
|
||||
};
|
||||
|
||||
public username = '';
|
||||
public password = '';
|
||||
public fullName = '';
|
||||
public passwordConfirm = '';
|
||||
public userExists?: boolean;
|
||||
public userInfo: any = {};
|
||||
public errorMessage = '';
|
||||
|
||||
public readonly coreidUrl: string;
|
||||
|
||||
constructor(
|
||||
protected api: ApiService,
|
||||
protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected loader: LoadingController,
|
||||
protected popover: PopoverController,
|
||||
protected alerts: AlertController,
|
||||
protected modals: ModalController,
|
||||
public readonly editorService: EditorService,
|
||||
public readonly navService: NavigationService,
|
||||
public readonly auth: AuthService,
|
||||
) {
|
||||
this.coreidUrl = this.api._build_url('/auth/starship_coreid/login', '/');
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
async ionViewDidEnter() {
|
||||
|
||||
}
|
||||
|
||||
coreid() {
|
||||
window.location.assign(this.coreidUrl);
|
||||
}
|
||||
|
||||
async back() {
|
||||
this.errorMessage = '';
|
||||
|
||||
if ( this.step === 'password' ) {
|
||||
this.password = '';
|
||||
this.step = 'username';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.usernameInput ) {
|
||||
this.usernameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
} else if ( this.step === 'create-account' ) {
|
||||
this.fullName = '';
|
||||
this.password = '';
|
||||
this.passwordConfirm = '';
|
||||
this.step = 'username';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.usernameInput ) {
|
||||
this.usernameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
|
||||
async advance() {
|
||||
this.errorMessage = '';
|
||||
|
||||
if ( this.step === 'greeter' ) {
|
||||
this.step = 'username';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.usernameInput ) {
|
||||
this.usernameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
} else if ( this.step === 'username' ) {
|
||||
if ( !this.username ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = await this.loader.create({ message: 'Checking username...' });
|
||||
await loader.present();
|
||||
|
||||
this.userInfo = await this.api.getUserInfo(this.username);
|
||||
this.userExists = !!this.userInfo?.exists;
|
||||
if ( this.userExists ) {
|
||||
this.step = 'password';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.passwordInput ) {
|
||||
this.passwordInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
} else {
|
||||
this.step = 'create-account';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.nameInput ) {
|
||||
this.nameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
await loader.dismiss();
|
||||
} else if ( this.step === 'password' ) {
|
||||
if ( !this.username || !this.password ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = await this.loader.create({ message: 'Signing you in...' });
|
||||
await loader.present();
|
||||
|
||||
const result = await this.api.attemptLogin(this.username, this.password);
|
||||
await loader.dismiss();
|
||||
|
||||
if ( !result.success ) {
|
||||
this.errorMessage = result.message || 'Un unknown error has occurred';
|
||||
} else {
|
||||
this.auth.authInProgress = true;
|
||||
this.navService.requestInitialization();
|
||||
this.reset();
|
||||
}
|
||||
} else if ( this.step === 'create-account' ) {
|
||||
if ( !this.username || !this.password || this.passwordConfirm !== this.password || !this.fullName ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = await this.loader.create({ message: 'Creating your account...' });
|
||||
await loader.present();
|
||||
|
||||
const result = await this.api.attemptRegistration(this.username, this.password, this.passwordConfirm, this.fullName);
|
||||
await loader.dismiss();
|
||||
|
||||
if ( !result.success ) {
|
||||
this.errorMessage = result.message || 'Un unknown error has occurred';
|
||||
} else {
|
||||
this.auth.authInProgress = true;
|
||||
this.navService.requestInitialization();
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.username = '';
|
||||
this.password = '';
|
||||
this.passwordConfirm = '';
|
||||
this.fullName = '';
|
||||
this.step = 'greeter';
|
||||
}
|
||||
}
|
@ -1401,4 +1401,66 @@ export class ApiService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getUserInfo(uid: string): Promise<any> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
}
|
||||
|
||||
this.get('/auth/user-info', { uid }).subscribe({
|
||||
next: result => {
|
||||
res(result.data);
|
||||
},
|
||||
error: rej,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public attemptLogin(uid: string, password: string): Promise<{ success: boolean, message?: string }> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
}
|
||||
|
||||
this.post('/auth/attempt', { uid, password }).subscribe({
|
||||
next: result => {
|
||||
res(result.data);
|
||||
},
|
||||
error: rej,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public attemptRegistration(uid: string, password: string, passwordConfirmation: string, fullName: string):
|
||||
Promise<{ success: boolean, message?: string }> {
|
||||
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
}
|
||||
|
||||
this.post('/auth/register', { uid, password, passwordConfirmation, fullName }).subscribe({
|
||||
next: result => {
|
||||
res(result.data);
|
||||
},
|
||||
error: rej,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public endSession(): Promise<any> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
}
|
||||
|
||||
this.post('/auth/end-session').subscribe({
|
||||
next: result => {
|
||||
return res(result.data);
|
||||
},
|
||||
error: rej,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
59
src/app/service/auth.service.ts
Normal file
59
src/app/service/auth.service.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from './api.service';
|
||||
import {NavigationService} from './navigation.service';
|
||||
import {DatabaseService} from './db/database.service';
|
||||
import {LoadingController} from '@ionic/angular';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService implements CanActivate {
|
||||
public authInProgress = false;
|
||||
|
||||
constructor(
|
||||
protected readonly loader: LoadingController,
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly nav: NavigationService,
|
||||
protected readonly db: DatabaseService,
|
||||
) { }
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const checkCanActivate = async () => {
|
||||
const isAuthenticated = (this.api.isAuthenticated && !this.api.isPublicUser);
|
||||
|
||||
if ( !isAuthenticated ) {
|
||||
await this.router.navigate(['/login']);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(async res => {
|
||||
if ( !this.nav.initialized$.getValue() ) {
|
||||
const sub = this.nav.initialized$.subscribe(async initialized => {
|
||||
if ( initialized ) {
|
||||
sub.unsubscribe();
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async endSession() {
|
||||
const loader = await this.loader.create({ message: 'Logging you out...' });
|
||||
await loader.present();
|
||||
|
||||
await this.db.purge();
|
||||
await this.api.endSession();
|
||||
await this.router.navigate(['/login']);
|
||||
|
||||
await loader.dismiss();
|
||||
this.nav.requestInitialization();
|
||||
}
|
||||
}
|
41
src/app/service/guard/GuestOnly.guard.ts
Normal file
41
src/app/service/guard/GuestOnly.guard.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {CanActivate, Router} from '@angular/router';
|
||||
import {ApiService} from '../api.service';
|
||||
import {NavigationService} from '../navigation.service';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GuestOnlyGuard implements CanActivate {
|
||||
constructor(
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly nav: NavigationService,
|
||||
) { }
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
const checkCanActivate = async () => {
|
||||
const isAuthenticated = (this.api.isAuthenticated && !this.api.isPublicUser);
|
||||
|
||||
if ( isAuthenticated ) {
|
||||
await this.router.navigate(['/home']);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(async res => {
|
||||
if ( !this.nav.initialized$.getValue() ) {
|
||||
const sub = this.nav.initialized$.subscribe(async initialized => {
|
||||
if ( initialized ) {
|
||||
sub.unsubscribe();
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ export class NavigationService {
|
||||
protected refreshCount = 0;
|
||||
public readonly sidebarRefresh$: BehaviorSubject<[number, boolean]> = new BehaviorSubject<[number, boolean]>([this.refreshCount, true]);
|
||||
public readonly navigationRequest$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||
public readonly initializationRequest$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
|
||||
public readonly initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
requestSidebarRefresh({ quiet = false }) {
|
||||
this.refreshCount += 1;
|
||||
@ -21,4 +23,9 @@ export class NavigationService {
|
||||
|
||||
this.navigationRequest$.next(pageId);
|
||||
}
|
||||
|
||||
requestInitialization() {
|
||||
debug('Requesting application initialization');
|
||||
this.initializationRequest$.next(this.initializationRequest$.getValue() + 1);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user