Use bearer instead of basic auth

This commit is contained in:
William Brawner 2021-01-26 21:00:15 -07:00
parent 84dae70b7f
commit ae14a33616
10 changed files with 75 additions and 55 deletions

7
.vscode/launch.json vendored
View file

@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "pwa-chrome",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}"
},
{
"name": "ng serve",
"type": "node",

5
package-lock.json generated
View file

@ -8470,11 +8470,6 @@
"resolved": "https://registry.npmjs.org/ng2-currency-mask/-/ng2-currency-mask-9.0.2.tgz",
"integrity": "sha512-ydtSTEqXR32mHsCElDvmSEywmN45Dam4aWUhoYJl3eEm9T6QWgA3DzHxNiFcur2kH94gomdvlQAB490CcObGMg=="
},
"ngx-cookie-service": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-2.4.0.tgz",
"integrity": "sha512-uR/6mCQ1t+XY5G1/irqRhoEddx1PPtmz7JHM/2nn5yQmicnj+n48x8C2PMxwaYDHKRh7QPQ9G5scR36Mmdz09A=="
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View file

@ -30,7 +30,6 @@
"dexie": "^2.0.4",
"ng2-charts": "^2.4.2",
"ng2-currency-mask": "^9.0.2",
"ngx-cookie-service": "^2.4.0",
"rxjs": "^6.6.3",
"tslib": "^2.0.0",
"zone.js": "~0.10.3"

View file

@ -1,8 +1,7 @@
import { Component, Inject, ApplicationRef, ChangeDetectorRef } from '@angular/core';
import { Component, Inject, ApplicationRef, ChangeDetectorRef, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { User } from './users/user';
import { TWIGS_SERVICE, TwigsService } from './shared/twigs.service';
import { CookieService } from 'ngx-cookie-service';
import { SwUpdate } from '@angular/service-worker';
import { first, filter, map } from 'rxjs/operators';
import { interval, concat, BehaviorSubject } from 'rxjs';
@ -14,7 +13,7 @@ import { Actionable, isActionable } from './shared/actionable';
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
export class AppComponent implements OnInit {
public title = 'Twigs';
public backEnabled = false;
public user = new BehaviorSubject<User>(null);
@ -26,41 +25,50 @@ export class AppComponent {
constructor(
@Inject(TWIGS_SERVICE) private twigsService: TwigsService,
private location: Location,
private cookieService: CookieService,
private router: Router,
private activatedRoute: ActivatedRoute,
private appRef: ApplicationRef,
private updates: SwUpdate,
private changeDetector: ChangeDetectorRef,
) {
private storage: Storage
){}
ngOnInit(): void {
const unauthenticatedRoutes = [
'',
'/',
'/login',
'/register'
]
if (this.cookieService.check('Authorization')) {
this.twigsService.getProfile().subscribe(user => {
this.user.next(user);
if (this.router.url == '/') {
let auth = this.storage.getItem('Authorization');
let savedUser = JSON.parse(this.storage.getItem('user')) as User;
if (auth && auth.length == 255) {
if (savedUser) {
this.user.next(savedUser);
}
this.twigsService.getProfile().subscribe(fetchedUser => {
this.storage.setItem('user', JSON.stringify(fetchedUser));
this.user.next(fetchedUser);
if (unauthenticatedRoutes.indexOf(this.location.path()) != -1) {
//TODO: Save last opened budget and redirect to there instead of the main list
this.router.navigateByUrl("/budgets");
}
});
} else if (unauthenticatedRoutes.indexOf(this.router.url) == -1) {
this.router.navigateByUrl("/login");
} else if (unauthenticatedRoutes.indexOf(this.location.path()) == -1) {
this.router.navigateByUrl(`/login?redirect=${this.location.path()}`);
}
updates.available.subscribe(
this.updates.available.subscribe(
event => {
console.log('current version is', event.current);
console.log('available version is', event.available);
// TODO: Prompt user to click something to update
updates.activateUpdate();
this.updates.activateUpdate();
},
err => {
}
);
updates.activated.subscribe(
this.updates.activated.subscribe(
event => {
console.log('old version was', event.previous);
console.log('new version is', event.current);
@ -70,10 +78,10 @@ export class AppComponent {
}
);
const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
const appIsStable$ = this.appRef.isStable.pipe(first(isStable => isStable === true));
const everySixHours$ = interval(6 * 60 * 60 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
everySixHoursOnceAppIsStable$.subscribe(() => this.updates.checkForUpdate());
this.user.subscribe(
user => {
if (user) {

View file

@ -46,7 +46,6 @@ import { TWIGS_SERVICE } from './shared/twigs.service';
import { AuthInterceptor } from './shared/auth.interceptor';
import { TwigsHttpService } from './shared/twigs.http.service';
import { TwigsLocalService } from './shared/twigs.local.service';
import { CookieService } from 'ngx-cookie-service';
import { TransactionListComponent } from './transactions/transaction-list/transaction-list.component';
import { EditCategoryComponent } from './categories/edit-category/edit-category.component';
import { EditBudgetComponent } from './budgets/edit-budget/edit-budget.component';
@ -114,8 +113,8 @@ export const CustomCurrencyMaskConfig: CurrencyMaskConfig = {
{ provide: CURRENCY_MASK_CONFIG, useValue: CustomCurrencyMaskConfig },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: TWIGS_SERVICE, useClass: TwigsHttpService },
{ provide: Storage, useValue: window.localStorage },
// { provide: TWIGS_SERVICE, useClass: TwigsLocalService },
CookieService
],
bootstrap: [AppComponent]
})

View file

@ -1,22 +1,21 @@
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private cookieService: CookieService
private storage: Storage
) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.cookieService.check('Authorization')) {
let token = this.storage.getItem('Authorization')
if (!token) {
return next.handle(req);
}
let headers = req.headers;
headers = headers.append('Authorization', `Basic ${this.cookieService.get('Authorization')}`);
this.cookieService.set('Authorization', this.cookieService.get('Authorization'), 14, null, null, true);
headers = headers.append('Authorization', `Bearer ${token}`);
return next.handle(req.clone({headers: headers}));
}
}

View file

@ -1,42 +1,46 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { BehaviorSubject, Observable, pipe, Subscriber } from 'rxjs';
import { User, UserPermission, Permission } from '../users/user';
import { User, UserPermission, Permission, AuthToken } from '../users/user';
import { TwigsService } from './twigs.service';
import { Budget } from '../budgets/budget';
import { Category } from '../categories/category';
import { Transaction } from '../transactions/transaction';
import { environment } from '../../environments/environment';
import { map } from 'rxjs/operators';
import { CookieService } from 'ngx-cookie-service';
@Injectable({
providedIn: 'root'
})
export class TwigsHttpService implements TwigsService {
constructor(
private http: HttpClient,
private cookieService: CookieService
) { }
private options = {
withCredentials: true
};
private apiUrl = environment.apiUrl;
private budgets: BehaviorSubject<Budget[]> = new BehaviorSubject(null);
// Auth
constructor(
private http: HttpClient,
private storage: Storage
) { }
login(email: string, password: string): Observable<User> {
// const params = {
// 'username': email,
// 'password': password
// };
// return this.http.post<User>(this.apiUrl + '/users/login', params, this.options);
const credentials = btoa(`${email}:${password}`)
this.cookieService.set('Authorization', credentials, 14, null, null, true);
return this.getProfile();
return new Observable(emitter => {
const params = {
'username': email,
'password': password
};
this.http.post<AuthToken>(this.apiUrl + '/users/login', params, this.options)
.subscribe(
auth => {
// TODO: Use token expiration to determine cookie expiration
this.storage.setItem('Authorization', auth.token);
this.getProfile().subscribe(user => emitter.next(user), error => emitter.error(error));
},
error => emitter.error(error)
);
});
}
register(username: string, email: string, password: string): Observable<User> {
@ -49,8 +53,8 @@ export class TwigsHttpService implements TwigsService {
}
logout(): Observable<void> {
return Observable.create(emitter => {
this.cookieService.delete('Authorization');
return new Observable(emitter => {
this.storage.removeItem('Authorization');
emitter.next();
emitter.complete();
})
@ -74,7 +78,7 @@ export class TwigsHttpService implements TwigsService {
cachedBudget = this.budgets.value.find(budget => {
return budget.id === id;
});
}
}
if (cachedBudget) {
emitter.next(cachedBudget);
emitter.complete();
@ -140,7 +144,7 @@ export class TwigsHttpService implements TwigsService {
var index = updatedBudgets.findIndex(oldBudget => oldBudget.id === id);
if (index > -1) {
updatedBudgets.splice(index, 1);
}
}
updatedBudgets.push(budget);
updatedBudgets.sort();
this.budgets.next(updatedBudgets);

View file

@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, Inject, ChangeDetectorRef } from '@angula
import { TwigsService, TWIGS_SERVICE } from '../../shared/twigs.service';
import { User } from '../user';
import { AppComponent } from 'src/app/app.component';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-login',
@ -14,16 +14,19 @@ export class LoginComponent implements OnInit {
public isLoading = false;
public email: string;
public password: string;
private redirect: string;
constructor(
private app: AppComponent,
@Inject(TWIGS_SERVICE) private twigsService: TwigsService,
private router: Router,
private activatedRoute: ActivatedRoute
) { }
ngOnInit() {
this.app.setTitle('Login')
this.app.setBackEnabled(true);
this.redirect = this.activatedRoute.snapshot.queryParamMap.get('redirect');
}
login(): void {
@ -31,10 +34,11 @@ export class LoginComponent implements OnInit {
this.twigsService.login(this.email, this.password)
.subscribe(user => {
this.app.user.next(user);
this.router.navigate(['/'])
this.router.navigate([this.redirect || '/'])
},
error => {
console.error(error)
//TODO: Replace this with an in-app dialog
alert("Login failed. Please verify you have the correct credentials");
this.isLoading = false;
})

View file

@ -12,6 +12,11 @@ export class User {
}
}
export class AuthToken {
token: string;
expiration: Date;
}
export class UserPermission {
user: User;
permission: Permission;

View file

@ -13,4 +13,4 @@ export const environment = {
* below file. Don't forget to comment it out in production mode
* because it will have a performance impact when errors are thrown
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
import 'zone.js/dist/zone-error'; // Included with Angular CLI.