diff --git a/Front/skydivelogs-app/src/app/app.module.ts b/Front/skydivelogs-app/src/app/app.module.ts
index 68ae247..15e7e19 100644
--- a/Front/skydivelogs-app/src/app/app.module.ts
+++ b/Front/skydivelogs-app/src/app/app.module.ts
@@ -17,9 +17,9 @@ import { NewGearComponent } from './new-gear/new-gear.component';
import { NewDropZoneComponent } from './new-drop-zone/new-drop-zone.component';
import { NewJumpTypeComponent } from './new-jump-type/new-jump-type.component';
import { DefaultComponent } from './default/default.component';
+import { LoginComponent } from './login/login.component';
import { DateService } from '../services/date.service';
-
import { AircraftService } from '../services/aircraft.service';
import { DropzoneService } from '../services/dropzone.service';
import { GearService } from '../services/gear.service';
@@ -27,6 +27,8 @@ import { JumpService } from '../services/jump.service';
import { JumpTypeService } from '../services/jump-type.service';
import { StatsService } from '../services/stats.service';
import { ServiceComm } from '../services/service-comm.service';
+import { RequestCache } from '../services/request-cache.service';
+import { AuthGuardService } from '../services/auth-guard.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -43,19 +45,23 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
-import { RequestCache } from '../services/request-cache.service';
-import { CachingInterceptor } from '../services/caching-interceptor.service';
+import { CachingInterceptor } from '../interceptor/caching.interceptor';
+import { BasicAuthInterceptor } from '../interceptor/basic-auth.interceptor';
+import { ErrorInterceptor } from '../interceptor/error.interceptor';
+
const appRoutes: Routes = [
- { path: '', component: DefaultComponent },
+ { path: '', component: DefaultComponent, canActivate: [AuthGuardService] },
- { path: 'summary', component: SummaryComponent },
- { path: 'jumps', component: ListOfJumpsComponent },
- { path: 'dzs', component: ListOfDzsComponent },
- { path: 'newjump', component: NewJumpComponent },
- { path: 'aircrafts', component: ListOfAircraftsComponent },
- { path: 'jumpTypes', component: ListOfJumpTypesComponent },
- { path: 'gears', component: ListOfGearsComponent },
+ { path: 'summary', component: SummaryComponent, canActivate: [AuthGuardService] },
+ { path: 'jumps', component: ListOfJumpsComponent, canActivate: [AuthGuardService] },
+ { path: 'dzs', component: ListOfDzsComponent, canActivate: [AuthGuardService] },
+ { path: 'newjump', component: NewJumpComponent, canActivate: [AuthGuardService] },
+ { path: 'aircrafts', component: ListOfAircraftsComponent, canActivate: [AuthGuardService] },
+ { path: 'jumpTypes', component: ListOfJumpTypesComponent, canActivate: [AuthGuardService] },
+ { path: 'gears', component: ListOfGearsComponent, canActivate: [AuthGuardService] },
+
+ { path: 'login', component: LoginComponent },
{ path: '**', redirectTo: '' }
];
@@ -74,7 +80,8 @@ const appRoutes: Routes = [
NewGearComponent,
NewDropZoneComponent,
NewJumpTypeComponent,
- DefaultComponent
+ DefaultComponent,
+ LoginComponent
],
imports: [
RouterModule.forRoot(
@@ -111,6 +118,8 @@ const appRoutes: Routes = [
DateService,
RequestCache,
// { provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }
+ { provide: HTTP_INTERCEPTORS, useClass: BasicAuthInterceptor, multi: true },
+ { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
],
bootstrap: [AppComponent]
})
diff --git a/Front/skydivelogs-app/src/app/login/login.component.css b/Front/skydivelogs-app/src/app/login/login.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/Front/skydivelogs-app/src/app/login/login.component.html b/Front/skydivelogs-app/src/app/login/login.component.html
new file mode 100644
index 0000000..bf55e88
--- /dev/null
+++ b/Front/skydivelogs-app/src/app/login/login.component.html
@@ -0,0 +1,34 @@
+
+
+ Username: test
+ Password: test
+
+
+
diff --git a/Front/skydivelogs-app/src/app/login/login.component.spec.ts b/Front/skydivelogs-app/src/app/login/login.component.spec.ts
new file mode 100644
index 0000000..d6d85a8
--- /dev/null
+++ b/Front/skydivelogs-app/src/app/login/login.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+ let component: LoginComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ LoginComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoginComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/Front/skydivelogs-app/src/app/login/login.component.ts b/Front/skydivelogs-app/src/app/login/login.component.ts
new file mode 100644
index 0000000..f92a831
--- /dev/null
+++ b/Front/skydivelogs-app/src/app/login/login.component.ts
@@ -0,0 +1,67 @@
+import { Component, OnInit } from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+
+import { first } from 'rxjs/operators';
+
+import { AuthenticationService } from '../../services/authentication.service';
+
+
+@Component({
+ selector: 'app-login',
+ templateUrl: './login.component.html',
+ styleUrls: ['./login.component.css']
+})
+export class LoginComponent implements OnInit {
+ loginForm: FormGroup;
+ loading = false;
+ submitted = false;
+ returnUrl: string;
+ error = '';
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private route: ActivatedRoute,
+ private router: Router,
+ private authenticationService: AuthenticationService
+ ) {
+ // redirect to home if already logged in
+ if (this.authenticationService.currentUserValue) {
+ this.router.navigate(['/']);
+ }
+ }
+
+ ngOnInit() {
+ this.loginForm = this.formBuilder.group({
+ username: ['', Validators.required],
+ password: ['', Validators.required]
+ });
+
+ // get return url from route parameters or default to '/'
+ this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
+ }
+
+ // convenience getter for easy access to form fields
+ get f() { return this.loginForm.controls; }
+
+ onSubmit() {
+ this.submitted = true;
+
+ // stop here if form is invalid
+ if (this.loginForm.invalid) {
+ return;
+ }
+
+ this.loading = true;
+ this.authenticationService.login(this.f.username.value, this.f.password.value)
+ .pipe(first())
+ .subscribe(
+ data => {
+ this.router.navigate([this.returnUrl]);
+ },
+ error => {
+ this.error = error;
+ this.loading = false;
+ });
+ }
+}
diff --git a/Front/skydivelogs-app/src/environments/environment.prod.ts b/Front/skydivelogs-app/src/environments/environment.prod.ts
index 9374e8d..2b01f99 100644
--- a/Front/skydivelogs-app/src/environments/environment.prod.ts
+++ b/Front/skydivelogs-app/src/environments/environment.prod.ts
@@ -1,5 +1,5 @@
export const environment = {
production: true,
- urlApi: "https://skydivelogsapi.azurewebsites.net",
+ apiUrl: 'https://skydivelogsapi.azurewebsites.net',
debugMode: false
};
diff --git a/Front/skydivelogs-app/src/environments/environment.ts b/Front/skydivelogs-app/src/environments/environment.ts
index 0457f23..787668f 100644
--- a/Front/skydivelogs-app/src/environments/environment.ts
+++ b/Front/skydivelogs-app/src/environments/environment.ts
@@ -5,6 +5,6 @@
export const environment = {
production: false,
- urlApi: "http://localhost:5000",
+ apiUrl: 'http://localhost:5000',
debugMode: false
};
diff --git a/Front/skydivelogs-app/src/interceptor/basic-auth.interceptor.ts b/Front/skydivelogs-app/src/interceptor/basic-auth.interceptor.ts
new file mode 100644
index 0000000..df89505
--- /dev/null
+++ b/Front/skydivelogs-app/src/interceptor/basic-auth.interceptor.ts
@@ -0,0 +1,24 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { AuthenticationService } from '../services/authentication.service';
+
+@Injectable()
+export class BasicAuthInterceptor implements HttpInterceptor {
+ constructor(private authenticationService: AuthenticationService) { }
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ // add authorization header with basic auth credentials if available
+ const currentUser = this.authenticationService.currentUserValue;
+ if (currentUser && currentUser.authdata) {
+ request = request.clone({
+ setHeaders: {
+ Authorization: `Basic ${currentUser.authdata}`
+ }
+ });
+ }
+
+ return next.handle(request);
+ }
+}
\ No newline at end of file
diff --git a/Front/skydivelogs-app/src/services/caching-interceptor.service.ts b/Front/skydivelogs-app/src/interceptor/caching.interceptor.ts
similarity index 93%
rename from Front/skydivelogs-app/src/services/caching-interceptor.service.ts
rename to Front/skydivelogs-app/src/interceptor/caching.interceptor.ts
index acb26f0..ec44a40 100644
--- a/Front/skydivelogs-app/src/services/caching-interceptor.service.ts
+++ b/Front/skydivelogs-app/src/interceptor/caching.interceptor.ts
@@ -11,7 +11,7 @@ import { Observable } from 'rxjs/Observable';
import { startWith, tap } from 'rxjs/operators';
import 'rxjs/add/observable/of';
-import { RequestCache } from './request-cache.service';
+import { RequestCache } from '../services/request-cache.service';
@Injectable()
export class CachingInterceptor implements HttpInterceptor {
diff --git a/Front/skydivelogs-app/src/interceptor/error.interceptor.ts b/Front/skydivelogs-app/src/interceptor/error.interceptor.ts
new file mode 100644
index 0000000..0f1d9f2
--- /dev/null
+++ b/Front/skydivelogs-app/src/interceptor/error.interceptor.ts
@@ -0,0 +1,24 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { Observable, throwError } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+
+import { AuthenticationService } from '../services/authentication.service';
+
+@Injectable()
+export class ErrorInterceptor implements HttpInterceptor {
+ constructor(private authenticationService: AuthenticationService) { }
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ return next.handle(request).pipe(catchError(err => {
+ if (err.status === 401) {
+ // auto logout if 401 response returned from api
+ this.authenticationService.logout();
+ location.reload(true);
+ }
+
+ const error = err.error.message || err.statusText;
+ return throwError(error);
+ }))
+ }
+}
\ No newline at end of file
diff --git a/Front/skydivelogs-app/src/models/user.ts b/Front/skydivelogs-app/src/models/user.ts
new file mode 100644
index 0000000..2a232f8
--- /dev/null
+++ b/Front/skydivelogs-app/src/models/user.ts
@@ -0,0 +1,8 @@
+export class User {
+ id: number;
+ username: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ authdata?: string;
+}
\ No newline at end of file
diff --git a/Front/skydivelogs-app/src/services/aircraft.service.ts b/Front/skydivelogs-app/src/services/aircraft.service.ts
index 2763822..b784ddc 100644
--- a/Front/skydivelogs-app/src/services/aircraft.service.ts
+++ b/Front/skydivelogs-app/src/services/aircraft.service.ts
@@ -8,13 +8,13 @@ import { AircraftResp, AircraftReq } from '../models/aircraft';
export class AircraftService {
private readonly headers = new HttpHeaders({
- 'Access-Control-Allow-Origin': environment.urlApi
+ 'Access-Control-Allow-Origin': environment.apiUrl
});
constructor(private http: HttpClient) { }
public getListOfAircrafts(): Observable> {
return this.http.get>(
- `${environment.urlApi}/api/Aircraft`,
+ `${environment.apiUrl}/api/Aircraft`,
{ headers: this.headers }
);
}
@@ -26,7 +26,7 @@ export class AircraftService {
};
this.http
- .post(`${environment.urlApi}/api/Aircraft`, bodyNewAircraft, {
+ .post(`${environment.apiUrl}/api/Aircraft`, bodyNewAircraft, {
headers: this.headers
})
.subscribe(data => console.log(data));
diff --git a/Front/skydivelogs-app/src/services/auth-guard.service.ts b/Front/skydivelogs-app/src/services/auth-guard.service.ts
new file mode 100644
index 0000000..3e71ce4
--- /dev/null
+++ b/Front/skydivelogs-app/src/services/auth-guard.service.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
+import { AuthenticationService } from './authentication.service';
+
+@Injectable({ providedIn: 'root' })
+export class AuthGuardService implements CanActivate {
+ constructor(
+ private router: Router,
+ private authenticationService: AuthenticationService
+ ) { }
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+ const currentUser = this.authenticationService.currentUserValue;
+ if (currentUser) {
+ // logged in so return true
+ return true;
+ }
+
+ // not logged in so redirect to login page with the return url
+ this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
+ return false;
+ }
+}
diff --git a/Front/skydivelogs-app/src/services/authentication.service.ts b/Front/skydivelogs-app/src/services/authentication.service.ts
new file mode 100644
index 0000000..4aee6f6
--- /dev/null
+++ b/Front/skydivelogs-app/src/services/authentication.service.ts
@@ -0,0 +1,43 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { environment } from '../environments/environment';
+import { User } from '../models/user';
+
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthenticationService {
+ private currentUserSubject: BehaviorSubject;
+ public currentUser: Observable;
+
+ constructor(private http: HttpClient) {
+ this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));
+ this.currentUser = this.currentUserSubject.asObservable();
+ }
+
+ public get currentUserValue(): User {
+ return this.currentUserSubject.value;
+ }
+
+ login(username: string, password: string) {
+ return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password })
+ .pipe(map(user => {
+ // store user details and basic auth credentials in local storage to keep user logged in between page refreshes
+ user.authdata = window.btoa(username + ':' + password);
+ localStorage.setItem('currentUser', JSON.stringify(user));
+ this.currentUserSubject.next(user);
+ return user;
+ }));
+ }
+
+ logout() {
+ // remove user from local storage to log user out
+ localStorage.removeItem('currentUser');
+ this.currentUserSubject.next(null);
+ }
+}
diff --git a/Front/skydivelogs-app/src/services/dropzone.service.ts b/Front/skydivelogs-app/src/services/dropzone.service.ts
index a5c9f1d..30959fa 100644
--- a/Front/skydivelogs-app/src/services/dropzone.service.ts
+++ b/Front/skydivelogs-app/src/services/dropzone.service.ts
@@ -10,13 +10,13 @@ import { DropZoneResp } from '../models/dropzone';
export class DropzoneService {
private readonly headers = new HttpHeaders({
- 'Access-Control-Allow-Origin': environment.urlApi
+ 'Access-Control-Allow-Origin': environment.apiUrl
});
constructor(private http: HttpClient) { }
public getListOfDropZones(): Observable> {
return this.http
- .get>(`${environment.urlApi}/api/DropZone`, {
+ .get>(`${environment.apiUrl}/api/DropZone`, {
headers: this.headers
})
.pipe(
@@ -30,7 +30,7 @@ export class DropzoneService {
public SetFavoriteDropZone(selectedDz: DropZoneResp): boolean {
selectedDz.isFavorite = true;
this.http
- .put(`${environment.urlApi}/api/DropZone/${selectedDz.id}`, selectedDz, {
+ .put(`${environment.apiUrl}/api/DropZone/${selectedDz.id}`, selectedDz, {
headers: this.headers
})
.subscribe(data => console.log(data));
@@ -41,7 +41,7 @@ export class DropzoneService {
public RemoveFavoriteDropZone(selectedDz: DropZoneResp): boolean {
selectedDz.isFavorite = false;
this.http
- .put(`${environment.urlApi}/api/DropZone/${selectedDz.id}`, selectedDz, {
+ .put(`${environment.apiUrl}/api/DropZone/${selectedDz.id}`, selectedDz, {
headers: this.headers
})
.subscribe(data => console.log(data));
diff --git a/Front/skydivelogs-app/src/services/gear.service.ts b/Front/skydivelogs-app/src/services/gear.service.ts
index 3173c50..bac7469 100644
--- a/Front/skydivelogs-app/src/services/gear.service.ts
+++ b/Front/skydivelogs-app/src/services/gear.service.ts
@@ -9,12 +9,12 @@ import { GearResp, GearReq } from '../models/gear';
export class GearService {
private readonly headers = new HttpHeaders({
- 'Access-Control-Allow-Origin': environment.urlApi
+ 'Access-Control-Allow-Origin': environment.apiUrl
});
constructor(private http: HttpClient) { }
public getListOfGears(): Observable> {
- return this.http.get>(`${environment.urlApi}/api/Gear`, {
+ return this.http.get>(`${environment.apiUrl}/api/Gear`, {
headers: this.headers
});
}
@@ -38,7 +38,7 @@ export class GearService {
};
this.http
- .post(`${environment.urlApi}/api/Gear`, bodyNewGear, {
+ .post(`${environment.apiUrl}/api/Gear`, bodyNewGear, {
headers: this.headers
})
.subscribe(data => console.log(data));
diff --git a/Front/skydivelogs-app/src/services/jump-type.service.ts b/Front/skydivelogs-app/src/services/jump-type.service.ts
index c0ba3cc..fd657d8 100644
--- a/Front/skydivelogs-app/src/services/jump-type.service.ts
+++ b/Front/skydivelogs-app/src/services/jump-type.service.ts
@@ -8,13 +8,13 @@ import { JumpTypeResp } from '../models/jumpType';
export class JumpTypeService {
private readonly headers = new HttpHeaders({
- 'Access-Control-Allow-Origin': environment.urlApi
+ 'Access-Control-Allow-Origin': environment.apiUrl
});
constructor(private http: HttpClient) { }
public getListOfJumpTypes(): Observable> {
return this.http.get>(
- `${environment.urlApi}/api/JumpType`,
+ `${environment.apiUrl}/api/JumpType`,
{ headers: this.headers }
);
}
diff --git a/Front/skydivelogs-app/src/services/jump.service.ts b/Front/skydivelogs-app/src/services/jump.service.ts
index dfed75d..a732d22 100644
--- a/Front/skydivelogs-app/src/services/jump.service.ts
+++ b/Front/skydivelogs-app/src/services/jump.service.ts
@@ -10,12 +10,12 @@ import { JumpResp, JumpReq } from '../models/jump';
export class JumpService {
private readonly headers = new HttpHeaders({
- 'Access-Control-Allow-Origin': environment.urlApi
+ 'Access-Control-Allow-Origin': environment.apiUrl
});
constructor(private http: HttpClient, private dateService: DateService) { }
public getListOfJumps(): Observable> {
- return this.http.get>(`${environment.urlApi}/api/Jump`, {
+ return this.http.get>(`${environment.apiUrl}/api/Jump`, {
headers: this.headers
})
.pipe(
@@ -98,7 +98,7 @@ export class JumpService {
};
this.http
- .post(`${environment.urlApi}/api/Jump`, bodyNewjump, {
+ .post(`${environment.apiUrl}/api/Jump`, bodyNewjump, {
headers: this.headers
})
.subscribe(data => console.log(data));
diff --git a/Front/skydivelogs-app/src/services/stats.service.ts b/Front/skydivelogs-app/src/services/stats.service.ts
index 1fee099..91adbf1 100644
--- a/Front/skydivelogs-app/src/services/stats.service.ts
+++ b/Front/skydivelogs-app/src/services/stats.service.ts
@@ -18,7 +18,7 @@ import {
@Injectable()
export class StatsService {
private readonly headers = new HttpHeaders({
- 'Access-Control-Allow-Origin': environment.urlApi
+ 'Access-Control-Allow-Origin': environment.apiUrl
});
constructor(private http: HttpClient) { }
@@ -42,7 +42,7 @@ export class StatsService {
private getSimpleSummary(): Observable {
return this.http
- .get>(`${environment.urlApi}/api/Stats/Simple`, {
+ .get>(`${environment.apiUrl}/api/Stats/Simple`, {
headers: this.headers
})
.pipe(
@@ -55,7 +55,7 @@ export class StatsService {
private getStatsByDz(): Observable> {
return this.http
- .get>(`${environment.urlApi}/api/Stats/ByDz`, {
+ .get>(`${environment.apiUrl}/api/Stats/ByDz`, {
headers: this.headers
})
.pipe(
@@ -69,7 +69,7 @@ export class StatsService {
private getStatsByAircraft(): Observable> {
return this.http
.get>(
- `${environment.urlApi}/api/Stats/ByAircraft`,
+ `${environment.apiUrl}/api/Stats/ByAircraft`,
{ headers: this.headers }
)
.pipe(
@@ -83,7 +83,7 @@ export class StatsService {
private getStatsByJumpType(): Observable> {
return this.http
.get>(
- `${environment.urlApi}/api/Stats/ByJumpType`,
+ `${environment.apiUrl}/api/Stats/ByJumpType`,
{ headers: this.headers }
)
.pipe(
@@ -96,7 +96,7 @@ export class StatsService {
private getStatsByGear(): Observable> {
return this.http
- .get>(`${environment.urlApi}/api/Stats/ByGear`, {
+ .get>(`${environment.apiUrl}/api/Stats/ByGear`, {
headers: this.headers
})
.pipe(
@@ -109,7 +109,7 @@ export class StatsService {
private getStatsByYear(): Observable> {
return this.http
- .get>(`${environment.urlApi}/api/Stats/ByYear`, {
+ .get>(`${environment.apiUrl}/api/Stats/ByYear`, {
headers: this.headers
})
.pipe(
@@ -123,7 +123,7 @@ export class StatsService {
private getStatsOfLastYear(): Observable {
return this.http
.get(
- `${environment.urlApi}/api/Stats/ForLastYear`,
+ `${environment.apiUrl}/api/Stats/ForLastYear`,
{ headers: this.headers }
)
.pipe(
@@ -141,7 +141,7 @@ export class StatsService {
private getStatsOfLastMonth(): Observable {
return this.http
.get(
- `${environment.urlApi}/api/Stats/ForLastMonth`,
+ `${environment.apiUrl}/api/Stats/ForLastMonth`,
{ headers: this.headers }
)
.pipe(