Use bearer instead of basic auth
This commit is contained in:
parent
84dae70b7f
commit
ae14a33616
10 changed files with 75 additions and 55 deletions
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
|
@ -4,6 +4,13 @@
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"url": "http://localhost:4200",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ng serve",
|
"name": "ng serve",
|
||||||
"type": "node",
|
"type": "node",
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -8470,11 +8470,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/ng2-currency-mask/-/ng2-currency-mask-9.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/ng2-currency-mask/-/ng2-currency-mask-9.0.2.tgz",
|
||||||
"integrity": "sha512-ydtSTEqXR32mHsCElDvmSEywmN45Dam4aWUhoYJl3eEm9T6QWgA3DzHxNiFcur2kH94gomdvlQAB490CcObGMg=="
|
"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": {
|
"nice-try": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
"dexie": "^2.0.4",
|
"dexie": "^2.0.4",
|
||||||
"ng2-charts": "^2.4.2",
|
"ng2-charts": "^2.4.2",
|
||||||
"ng2-currency-mask": "^9.0.2",
|
"ng2-currency-mask": "^9.0.2",
|
||||||
"ngx-cookie-service": "^2.4.0",
|
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "^6.6.3",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"zone.js": "~0.10.3"
|
"zone.js": "~0.10.3"
|
||||||
|
|
|
@ -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 { Location } from '@angular/common';
|
||||||
import { User } from './users/user';
|
import { User } from './users/user';
|
||||||
import { TWIGS_SERVICE, TwigsService } from './shared/twigs.service';
|
import { TWIGS_SERVICE, TwigsService } from './shared/twigs.service';
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
|
||||||
import { SwUpdate } from '@angular/service-worker';
|
import { SwUpdate } from '@angular/service-worker';
|
||||||
import { first, filter, map } from 'rxjs/operators';
|
import { first, filter, map } from 'rxjs/operators';
|
||||||
import { interval, concat, BehaviorSubject } from 'rxjs';
|
import { interval, concat, BehaviorSubject } from 'rxjs';
|
||||||
|
@ -14,7 +13,7 @@ import { Actionable, isActionable } from './shared/actionable';
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit {
|
||||||
public title = 'Twigs';
|
public title = 'Twigs';
|
||||||
public backEnabled = false;
|
public backEnabled = false;
|
||||||
public user = new BehaviorSubject<User>(null);
|
public user = new BehaviorSubject<User>(null);
|
||||||
|
@ -26,41 +25,50 @@ export class AppComponent {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(TWIGS_SERVICE) private twigsService: TwigsService,
|
@Inject(TWIGS_SERVICE) private twigsService: TwigsService,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
private cookieService: CookieService,
|
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
|
||||||
private appRef: ApplicationRef,
|
private appRef: ApplicationRef,
|
||||||
private updates: SwUpdate,
|
private updates: SwUpdate,
|
||||||
private changeDetector: ChangeDetectorRef,
|
private changeDetector: ChangeDetectorRef,
|
||||||
) {
|
private storage: Storage
|
||||||
|
){}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
const unauthenticatedRoutes = [
|
const unauthenticatedRoutes = [
|
||||||
|
'',
|
||||||
'/',
|
'/',
|
||||||
'/login',
|
'/login',
|
||||||
'/register'
|
'/register'
|
||||||
]
|
]
|
||||||
if (this.cookieService.check('Authorization')) {
|
let auth = this.storage.getItem('Authorization');
|
||||||
this.twigsService.getProfile().subscribe(user => {
|
let savedUser = JSON.parse(this.storage.getItem('user')) as User;
|
||||||
this.user.next(user);
|
if (auth && auth.length == 255) {
|
||||||
if (this.router.url == '/') {
|
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");
|
this.router.navigateByUrl("/budgets");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (unauthenticatedRoutes.indexOf(this.router.url) == -1) {
|
} else if (unauthenticatedRoutes.indexOf(this.location.path()) == -1) {
|
||||||
this.router.navigateByUrl("/login");
|
this.router.navigateByUrl(`/login?redirect=${this.location.path()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updates.available.subscribe(
|
this.updates.available.subscribe(
|
||||||
event => {
|
event => {
|
||||||
console.log('current version is', event.current);
|
console.log('current version is', event.current);
|
||||||
console.log('available version is', event.available);
|
console.log('available version is', event.available);
|
||||||
// TODO: Prompt user to click something to update
|
// TODO: Prompt user to click something to update
|
||||||
updates.activateUpdate();
|
this.updates.activateUpdate();
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
updates.activated.subscribe(
|
this.updates.activated.subscribe(
|
||||||
event => {
|
event => {
|
||||||
console.log('old version was', event.previous);
|
console.log('old version was', event.previous);
|
||||||
console.log('new version is', event.current);
|
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 everySixHours$ = interval(6 * 60 * 60 * 1000);
|
||||||
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
|
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
|
||||||
everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
|
everySixHoursOnceAppIsStable$.subscribe(() => this.updates.checkForUpdate());
|
||||||
this.user.subscribe(
|
this.user.subscribe(
|
||||||
user => {
|
user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
|
@ -46,7 +46,6 @@ import { TWIGS_SERVICE } from './shared/twigs.service';
|
||||||
import { AuthInterceptor } from './shared/auth.interceptor';
|
import { AuthInterceptor } from './shared/auth.interceptor';
|
||||||
import { TwigsHttpService } from './shared/twigs.http.service';
|
import { TwigsHttpService } from './shared/twigs.http.service';
|
||||||
import { TwigsLocalService } from './shared/twigs.local.service';
|
import { TwigsLocalService } from './shared/twigs.local.service';
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
|
||||||
import { TransactionListComponent } from './transactions/transaction-list/transaction-list.component';
|
import { TransactionListComponent } from './transactions/transaction-list/transaction-list.component';
|
||||||
import { EditCategoryComponent } from './categories/edit-category/edit-category.component';
|
import { EditCategoryComponent } from './categories/edit-category/edit-category.component';
|
||||||
import { EditBudgetComponent } from './budgets/edit-budget/edit-budget.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: CURRENCY_MASK_CONFIG, useValue: CustomCurrencyMaskConfig },
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||||
{ provide: TWIGS_SERVICE, useClass: TwigsHttpService },
|
{ provide: TWIGS_SERVICE, useClass: TwigsHttpService },
|
||||||
|
{ provide: Storage, useValue: window.localStorage },
|
||||||
// { provide: TWIGS_SERVICE, useClass: TwigsLocalService },
|
// { provide: TWIGS_SERVICE, useClass: TwigsLocalService },
|
||||||
CookieService
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
|
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthInterceptor implements HttpInterceptor {
|
export class AuthInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cookieService: CookieService
|
private storage: Storage
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
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);
|
return next.handle(req);
|
||||||
}
|
}
|
||||||
let headers = req.headers;
|
let headers = req.headers;
|
||||||
headers = headers.append('Authorization', `Basic ${this.cookieService.get('Authorization')}`);
|
headers = headers.append('Authorization', `Bearer ${token}`);
|
||||||
this.cookieService.set('Authorization', this.cookieService.get('Authorization'), 14, null, null, true);
|
|
||||||
return next.handle(req.clone({headers: headers}));
|
return next.handle(req.clone({headers: headers}));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,42 +1,46 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
||||||
import { BehaviorSubject, Observable, pipe, Subscriber } from 'rxjs';
|
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 { TwigsService } from './twigs.service';
|
||||||
import { Budget } from '../budgets/budget';
|
import { Budget } from '../budgets/budget';
|
||||||
import { Category } from '../categories/category';
|
import { Category } from '../categories/category';
|
||||||
import { Transaction } from '../transactions/transaction';
|
import { Transaction } from '../transactions/transaction';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class TwigsHttpService implements TwigsService {
|
export class TwigsHttpService implements TwigsService {
|
||||||
|
|
||||||
constructor(
|
|
||||||
private http: HttpClient,
|
|
||||||
private cookieService: CookieService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
private options = {
|
private options = {
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
};
|
};
|
||||||
|
|
||||||
private apiUrl = environment.apiUrl;
|
private apiUrl = environment.apiUrl;
|
||||||
|
|
||||||
private budgets: BehaviorSubject<Budget[]> = new BehaviorSubject(null);
|
private budgets: BehaviorSubject<Budget[]> = new BehaviorSubject(null);
|
||||||
// Auth
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private storage: Storage
|
||||||
|
) { }
|
||||||
|
|
||||||
login(email: string, password: string): Observable<User> {
|
login(email: string, password: string): Observable<User> {
|
||||||
// const params = {
|
return new Observable(emitter => {
|
||||||
// 'username': email,
|
const params = {
|
||||||
// 'password': password
|
'username': email,
|
||||||
// };
|
'password': password
|
||||||
// return this.http.post<User>(this.apiUrl + '/users/login', params, this.options);
|
};
|
||||||
const credentials = btoa(`${email}:${password}`)
|
this.http.post<AuthToken>(this.apiUrl + '/users/login', params, this.options)
|
||||||
this.cookieService.set('Authorization', credentials, 14, null, null, true);
|
.subscribe(
|
||||||
return this.getProfile();
|
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> {
|
register(username: string, email: string, password: string): Observable<User> {
|
||||||
|
@ -49,8 +53,8 @@ export class TwigsHttpService implements TwigsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(): Observable<void> {
|
logout(): Observable<void> {
|
||||||
return Observable.create(emitter => {
|
return new Observable(emitter => {
|
||||||
this.cookieService.delete('Authorization');
|
this.storage.removeItem('Authorization');
|
||||||
emitter.next();
|
emitter.next();
|
||||||
emitter.complete();
|
emitter.complete();
|
||||||
})
|
})
|
||||||
|
@ -74,7 +78,7 @@ export class TwigsHttpService implements TwigsService {
|
||||||
cachedBudget = this.budgets.value.find(budget => {
|
cachedBudget = this.budgets.value.find(budget => {
|
||||||
return budget.id === id;
|
return budget.id === id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (cachedBudget) {
|
if (cachedBudget) {
|
||||||
emitter.next(cachedBudget);
|
emitter.next(cachedBudget);
|
||||||
emitter.complete();
|
emitter.complete();
|
||||||
|
@ -140,7 +144,7 @@ export class TwigsHttpService implements TwigsService {
|
||||||
var index = updatedBudgets.findIndex(oldBudget => oldBudget.id === id);
|
var index = updatedBudgets.findIndex(oldBudget => oldBudget.id === id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
updatedBudgets.splice(index, 1);
|
updatedBudgets.splice(index, 1);
|
||||||
}
|
}
|
||||||
updatedBudgets.push(budget);
|
updatedBudgets.push(budget);
|
||||||
updatedBudgets.sort();
|
updatedBudgets.sort();
|
||||||
this.budgets.next(updatedBudgets);
|
this.budgets.next(updatedBudgets);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, Inject, ChangeDetectorRef } from '@angula
|
||||||
import { TwigsService, TWIGS_SERVICE } from '../../shared/twigs.service';
|
import { TwigsService, TWIGS_SERVICE } from '../../shared/twigs.service';
|
||||||
import { User } from '../user';
|
import { User } from '../user';
|
||||||
import { AppComponent } from 'src/app/app.component';
|
import { AppComponent } from 'src/app/app.component';
|
||||||
import { Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
|
@ -14,16 +14,19 @@ export class LoginComponent implements OnInit {
|
||||||
public isLoading = false;
|
public isLoading = false;
|
||||||
public email: string;
|
public email: string;
|
||||||
public password: string;
|
public password: string;
|
||||||
|
private redirect: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private app: AppComponent,
|
private app: AppComponent,
|
||||||
@Inject(TWIGS_SERVICE) private twigsService: TwigsService,
|
@Inject(TWIGS_SERVICE) private twigsService: TwigsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private activatedRoute: ActivatedRoute
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.app.setTitle('Login')
|
this.app.setTitle('Login')
|
||||||
this.app.setBackEnabled(true);
|
this.app.setBackEnabled(true);
|
||||||
|
this.redirect = this.activatedRoute.snapshot.queryParamMap.get('redirect');
|
||||||
}
|
}
|
||||||
|
|
||||||
login(): void {
|
login(): void {
|
||||||
|
@ -31,10 +34,11 @@ export class LoginComponent implements OnInit {
|
||||||
this.twigsService.login(this.email, this.password)
|
this.twigsService.login(this.email, this.password)
|
||||||
.subscribe(user => {
|
.subscribe(user => {
|
||||||
this.app.user.next(user);
|
this.app.user.next(user);
|
||||||
this.router.navigate(['/'])
|
this.router.navigate([this.redirect || '/'])
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
//TODO: Replace this with an in-app dialog
|
||||||
alert("Login failed. Please verify you have the correct credentials");
|
alert("Login failed. Please verify you have the correct credentials");
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,6 +12,11 @@ export class User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AuthToken {
|
||||||
|
token: string;
|
||||||
|
expiration: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export class UserPermission {
|
export class UserPermission {
|
||||||
user: User;
|
user: User;
|
||||||
permission: Permission;
|
permission: Permission;
|
||||||
|
|
|
@ -13,4 +13,4 @@ export const environment = {
|
||||||
* below file. Don't forget to comment it out in production mode
|
* below file. Don't forget to comment it out in production mode
|
||||||
* because it will have a performance impact when errors are thrown
|
* 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.
|
||||||
|
|
Loading…
Reference in a new issue