Skip to content

Commit 72ed06c

Browse files
midsorbetpaulbert
authored andcommitted
Use PouchDB for handling auth flow (#1515)
1 parent c6029cb commit 72ed06c

File tree

7 files changed

+119
-53
lines changed

7 files changed

+119
-53
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"mime": "^2.3.1",
4747
"ngx-img": "^10.15.0",
4848
"pouchdb": "^7.0.0",
49+
"pouchdb-authentication": "^1.1.3",
4950
"pouchdb-find": "^7.0.0",
5051
"rxjs": "^6.2.0",
5152
"zone.js": "~0.8.26"

src/app/home/home.component.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { interval, Subject, forkJoin } from 'rxjs';
99
import { tap, switchMap, takeUntil } from 'rxjs/operators';
1010
import { findDocuments } from '../shared/mangoQueries';
1111
import { debug } from '../debug-operator';
12+
import { PouchAuthService } from '../shared/database';
1213

1314
@Component({
1415
templateUrl: './home.component.html',
@@ -48,7 +49,8 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
4849
constructor(
4950
private couchService: CouchService,
5051
private router: Router,
51-
private userService: UserService
52+
private userService: UserService,
53+
private pouchAuthService: PouchAuthService
5254
) {
5355
this.userService.userChange$.pipe(takeUntil(this.onDestroy$))
5456
.subscribe(() => {
@@ -116,7 +118,7 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
116118

117119
logoutClick() {
118120
this.userService.endSessionLog().pipe(switchMap(() => {
119-
const obsArr = [ this.couchService.delete('_session', { withCredentials: true }) ];
121+
const obsArr = [ this.pouchAuthService.logout() ];
120122
const localAdminName = this.userService.getConfig().adminName.split('@')[0];
121123
if (localAdminName === this.userService.get().name) {
122124
obsArr.push(

src/app/login/login-form.component.ts

+30-33
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@ import { PlanetMessageService } from '../shared/planet-message.service';
1010
import { environment } from '../../environments/environment';
1111
import { ValidatorService } from '../validators/validator.service';
1212
import { SyncService } from '../shared/sync.service';
13+
import { PouchAuthService } from '../shared/database';
1314

1415
const registerForm = {
1516
name: [],
1617
password: [ '', Validators.compose([
1718
Validators.required,
1819
CustomValidators.matchPassword('repeatPassword', false)
19-
]) ],
20+
]) ],
2021
repeatPassword: [ '', Validators.compose([
2122
Validators.required,
2223
CustomValidators.matchPassword('password', true)
23-
]) ]
24+
]) ]
2425
};
2526

2627
const loginForm = {
@@ -42,7 +43,8 @@ export class LoginFormComponent {
4243
private formBuilder: FormBuilder,
4344
private planetMessageService: PlanetMessageService,
4445
private validatorService: ValidatorService,
45-
private syncService: SyncService
46+
private syncService: SyncService,
47+
private pouchAuthService: PouchAuthService
4648
) {
4749
registerForm.name = [ '', [
4850
Validators.required,
@@ -89,16 +91,22 @@ export class LoginFormComponent {
8991
return this.router.navigateByUrl(this.returnUrl);
9092
}
9193

92-
createUser({ name, password }: {name: string, password: string}) {
93-
this.couchService.put('_users/org.couchdb.user:' + name,
94-
{ 'name': name, 'password': password, 'roles': [], 'type': 'user', 'isUserAdmin': false, joinDate: Date.now() })
95-
.pipe(switchMap(() => {
96-
return this.couchService.put('shelf/org.couchdb.user:' + name, { });
97-
})).subscribe((response: any) => {
98-
this.planetMessageService.showMessage('User created: ' + response.id.replace('org.couchdb.user:', ''));
99-
this.welcomeNotification(response.id);
100-
this.login(this.userForm.value, true);
101-
}, error => this.planetMessageService.showAlert('An error occurred please try again'));
94+
createUser({ name, password }: { name: string, password: string }) {
95+
const metadata = {
96+
isUserAdmin: false,
97+
joinDate: Date.now()
98+
};
99+
100+
this.pouchAuthService.signup(name, password, metadata).pipe(
101+
switchMap(() => this.couchService.put('shelf/org.couchdb.user:' + name, {}))
102+
).subscribe(
103+
res => {
104+
this.planetMessageService.showMessage('User created: ' + res.id.replace('org.couchdb.user:', ''));
105+
this.welcomeNotification(res.id);
106+
this.login(this.userForm.value, true);
107+
},
108+
err => this.planetMessageService.showAlert('An error occurred please try again')
109+
);
102110
}
103111

104112
createParentSession({ name, password }) {
@@ -107,26 +115,15 @@ export class LoginFormComponent {
107115
{ withCredentials: true, domain: this.userService.getConfig().parentDomain });
108116
}
109117

110-
login({ name, password }: {name: string, password: string}, isCreate: boolean) {
111-
this.couchService.post('_session', { 'name': name, 'password': password }, { withCredentials: true })
112-
.pipe(switchMap((data) => {
113-
// Navigate into app
114-
if (isCreate) {
115-
return from(this.router.navigate( [ 'users/update/' + name ]));
116-
} else {
117-
return from(this.reRoute());
118-
}
119-
}),
120-
switchMap(this.createSession(name, password)),
121-
switchMap((sessionData) => {
122-
if (isCreate) {
123-
const adminName = this.userService.getConfig().adminName.split('@')[0];
124-
return this.sendNotifications(adminName, name);
125-
}
126-
return of(sessionData);
127-
})
128-
).subscribe((res) => {
129-
}, (error) => this.planetMessageService.showAlert('Username and/or password do not match'));
118+
login({ name, password }: { name: string, password: string }, isCreate: boolean) {
119+
this.pouchAuthService.login(name, password).pipe(
120+
switchMap(() => isCreate ? from(this.router.navigate([ 'users/update/' + name ])) : from(this.reRoute())),
121+
switchMap(this.createSession(name, password)),
122+
switchMap((sessionData) => {
123+
const adminName = this.userService.getConfig().adminName.split('@')[0];
124+
return isCreate ? this.sendNotifications(adminName, name) : of(sessionData);
125+
})
126+
).subscribe(() => {}, (error) => this.planetMessageService.showAlert('Username and/or password do not match'));
130127
}
131128

132129
sendNotifications(userName, addedMember) {

src/app/shared/auth-guard.service.ts

+17-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { Injectable } from '@angular/core';
2-
import { CouchService } from './couchdb.service';
32
import { UserService } from './user.service';
43
import { Observable, of } from 'rxjs';
54
import { switchMap, map } from 'rxjs/operators';
6-
7-
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
5+
import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
6+
import { PouchAuthService } from './database';
87

98
@Injectable()
109
export class AuthService {
1110

12-
constructor(private couchService: CouchService, private userService: UserService, private router: Router) {}
11+
constructor(private userService: UserService, private router: Router,
12+
private pouchAuthService: PouchAuthService) { }
1313

1414
private checkUser(url: any): Observable<boolean> {
15-
return this.couchService
16-
.get('_session', { withCredentials: true })
17-
.pipe(switchMap((res: any) => {
15+
return this.pouchAuthService.getSessionInfo().pipe(
16+
switchMap(res => {
1817
if (res.userCtx.name) {
1918
// If user already matches one on the user service, do not make additional call to CouchDB
2019
if (res.userCtx.name === this.userService.get().name) {
@@ -26,9 +25,8 @@ export class AuthService {
2625
this.router.navigate([ '/login' ], { queryParams: { returnUrl: url }, replaceUrl: true });
2726
return of(false);
2827
}),
29-
map((isLoggedIn) => {
30-
return isLoggedIn;
31-
}));
28+
map(isLoggedIn => isLoggedIn)
29+
);
3230
}
3331

3432
// For main app (which requires login). Uses canActivateChild to check on every route
@@ -39,13 +37,15 @@ export class AuthService {
3937

4038
// For login route will redirect to main app if there is an active session
4139
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
42-
return this.couchService.get('_session', { withCredentials: true }).pipe(map(res => {
43-
if (res.userCtx.name) {
44-
this.router.navigate([ '' ]);
45-
return false;
46-
}
47-
return true;
48-
}));
40+
return this.pouchAuthService.getSessionInfo().pipe(
41+
map(res => {
42+
if (res.userCtx.name) {
43+
this.router.navigate([ '' ]);
44+
return false;
45+
}
46+
return true;
47+
})
48+
);
4949
}
5050

5151
}

src/app/shared/database/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { PouchService } from './pouch.service';
2+
import { PouchAuthService } from './pouch-auth.service';
23

34
export { PouchService } from './pouch.service';
4-
export const SHARED_SERVICES = [ PouchService ];
5+
export { PouchAuthService } from './pouch-auth.service';
6+
export const SHARED_SERVICES = [ PouchService, PouchAuthService ];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Injectable, Inject } from '@angular/core';
2+
import { from, throwError, Observable } from 'rxjs';
3+
import { catchError } from 'rxjs/operators';
4+
import { PouchService } from './pouch.service';
5+
6+
interface SessionInfo {
7+
userCtx: {
8+
name: String;
9+
roles: String[];
10+
};
11+
}
12+
@Injectable()
13+
export class PouchAuthService {
14+
private authDB;
15+
16+
constructor(private pouchService: PouchService) {
17+
this.authDB = this.pouchService.getAuthDB();
18+
}
19+
20+
getSessionInfo(): Observable<SessionInfo> {
21+
return from(this.authDB.getSession()).pipe(
22+
catchError(this.handleError)
23+
) as Observable<SessionInfo>;
24+
}
25+
26+
login(username, password) {
27+
return from(this.authDB.logIn(username, password)).pipe(
28+
catchError(this.handleError)
29+
);
30+
}
31+
32+
signup(username, password, metadata?) {
33+
return from(this.authDB.signUp(username, password, { metadata })).pipe(
34+
catchError(this.handleError)
35+
);
36+
}
37+
38+
logout() {
39+
return from(this.authDB.logOut()).pipe(
40+
catchError(this.handleError)
41+
);
42+
}
43+
44+
private handleError(err) {
45+
console.error('An error occured while signing in the user', err);
46+
return throwError(err.message || err);
47+
}
48+
}

src/app/shared/database/pouch.service.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { Injectable } from '@angular/core';
22
import PouchDB from 'pouchdb';
3+
import PouchDBAuth from 'pouchdb-authentication';
34
import PouchDBFind from 'pouchdb-find';
45
import { throwError, from } from 'rxjs';
56
import { catchError } from 'rxjs/operators';
67
import { environment } from '../../../environments/environment';
78

9+
PouchDB.plugin(PouchDBAuth);
810
PouchDB.plugin(PouchDBFind);
911

1012
@Injectable()
1113
export class PouchService {
1214
private baseUrl = environment.couchAddress;
1315
private localDBs;
16+
private authDB;
1417
private databases = [];
1518

1619
constructor() {
@@ -25,6 +28,15 @@ export class PouchService {
2528
});
2629
this.localDBs[db] = pouchDB;
2730
});
31+
32+
// test is a placeholder temp database
33+
// we need a central remote database
34+
// since we will have different levels of authentication (manager, intern)
35+
// we will have to create corresponding documents in couchdb and we can sync
36+
// we can decide that when the user is being created for the first time?
37+
this.authDB = new PouchDB(this.baseUrl + 'test', {
38+
skip_setup: true
39+
});
2840
}
2941

3042
// @TODO: handle edge cases like offline, duplicate, duplications
@@ -61,6 +73,10 @@ export class PouchService {
6173
return this.localDBs[db];
6274
}
6375

76+
getAuthDB() {
77+
return this.authDB;
78+
}
79+
6480
private handleError(err) {
6581
console.error('An error occurred in PouchDB', err);
6682
return throwError(err.message || err);

0 commit comments

Comments
 (0)