* auth added

This commit is contained in:
Danyi Dávid 2018-05-12 00:19:48 +02:00
parent 7a44ba70d4
commit 03a160dafe
22 changed files with 492 additions and 59 deletions

67
deploy.php Normal file
View File

@ -0,0 +1,67 @@
<?php
namespace Deployer;
require 'recipe/common.php';
set('ssh_type', 'native');
set('ssh_multiplexing', true);
// Configuration
// set('repository', 'git@domain.com:username/repository.git');
set('shared_files', []);
set('shared_dirs', []);
set('writable_dirs', []);
set('keep_releases', 3);
set('default_stage', 'staging');
// Servers
host('alfheim')
->stage('staging')
->user('yvan')
->forwardAgent()
->set('ng_basehref', '/admin/')
->set('ng_target', 'production')
->set('ng_environment', 'prod')
->set('env_vars', 'NODE_ENV=production')
->set('deploy_path', '/mnt/apps/granprize/admin');
host('granprize')
->stage('production')
->user('edvidan')
->forwardAgent()
->set('ng_basehref', '/admin/')
->set('ng_target', 'production')
->set('ng_environment', 'prod')
->set('env_vars', 'NODE_ENV=production')
->set('deploy_path', '/var/www/granprize.swedishchamber.hu/admin');
// Tasks
desc('Prepare release');
task('deploy:ng-prepare', function() {
runLocally("ng build --base-href={{ng_basehref}} --target={{ng_target}} --environment={{ng_environment}}");
runLocally("tar -cJf dist.tar.xz dist");
});
desc('Upload release');
task('deploy:ng-upload', function() {
upload("dist.tar.xz", "{{release_path}}/dist.tar.xz");
run("tar -C {{release_path}} -xJf {{release_path}}/dist.tar.xz");
run("rm -f {{release_path}}/dist.tar.xz");
runLocally("rm -rf dist.tar.xz dist");
});
desc('Deploy your project');
task('deploy', [
'deploy:prepare',
'deploy:lock',
'deploy:release',
'deploy:ng-prepare',
'deploy:ng-upload',
'deploy:shared',
'deploy:clear_paths',
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
after('deploy', 'success');

15
package-lock.json generated
View File

@ -392,6 +392,14 @@
"tslib": "1.9.0" "tslib": "1.9.0"
} }
}, },
"@auth0/angular-jwt": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-2.0.0.tgz",
"integrity": "sha512-RVlXFpcqQ+9uCpzboU7Tm1ubaRVO2FrR5+RYuwHtTT4BXquVMEwOSbAuuaArFud/kMc00XYoGgiP1JkCfOAfpA==",
"requires": {
"url": "0.11.0"
}
},
"@fortawesome/fontawesome-free-webfonts": { "@fortawesome/fontawesome-free-webfonts": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.8.tgz",
@ -7547,8 +7555,7 @@
"querystring": { "querystring": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
"dev": true
}, },
"querystring-es3": { "querystring-es3": {
"version": "0.2.1", "version": "0.2.1",
@ -9611,7 +9618,6 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"dev": true,
"requires": { "requires": {
"punycode": "1.3.2", "punycode": "1.3.2",
"querystring": "0.2.0" "querystring": "0.2.0"
@ -9620,8 +9626,7 @@
"punycode": { "punycode": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
"dev": true
} }
} }
}, },

View File

@ -22,6 +22,7 @@
"@angular/platform-browser": "^6.0.0", "@angular/platform-browser": "^6.0.0",
"@angular/platform-browser-dynamic": "^6.0.0", "@angular/platform-browser-dynamic": "^6.0.0",
"@angular/router": "^6.0.0", "@angular/router": "^6.0.0",
"@auth0/angular-jwt": "^2.0.0",
"@fortawesome/fontawesome-free-webfonts": "^1.0.8", "@fortawesome/fontawesome-free-webfonts": "^1.0.8",
"core-js": "^2.5.4", "core-js": "^2.5.4",
"rxjs": "^6.0.0", "rxjs": "^6.0.0",

View File

@ -10,53 +10,61 @@ import { JudgeResolverService } from "./shared/judge-resolver.service";
import { AwardeeService } from "./shared/awardee.service"; import { AwardeeService } from "./shared/awardee.service";
import { AwardeeResolverService } from "./shared/awardee-resolver.service"; import { AwardeeResolverService } from "./shared/awardee-resolver.service";
import { YearResolverService } from "./shared/year-resolver.service"; import { YearResolverService } from "./shared/year-resolver.service";
import { AuthGuardService } from "./auth/auth-guard.service";
const routes: Routes = [ const routes: Routes = [
{ {
path: 'awardees', path: 'awardees',
component: AwardeeListComponent, component: AwardeeListComponent,
resolve: { resolve: {
awardees: AwardeeService, awardees: AwardeeService,
} },
// canActivate: [AuthGuardService, RoleGuardService], canActivate: [AuthGuardService],
}, { }, {
path: 'awardee/new', path: 'awardee/new',
component: AwardeeEditorComponent, component: AwardeeEditorComponent,
resolve: { resolve: {
years: YearResolverService, years: YearResolverService,
} },
// canActivate: [AuthGuardService, RoleGuardService], canActivate: [AuthGuardService],
}, { }, {
path: 'awardee/edit/:id', path: 'awardee/edit/:id',
component: AwardeeEditorComponent, component: AwardeeEditorComponent,
resolve: { resolve: {
awardee: AwardeeResolverService, awardee: AwardeeResolverService,
years: YearResolverService, years: YearResolverService,
} },
// canActivate: [AuthGuardService, RoleGuardService], canActivate: [AuthGuardService],
}, { }, {
path: 'judges', path: 'judges',
component: JudgeListComponent, component: JudgeListComponent,
resolve: { resolve: {
judges: JudgeService, judges: JudgeService,
} },
// canActivate: [AuthGuardService, RoleGuardService], canActivate: [AuthGuardService],
}, { }, {
path: 'judge/new', path: 'judge/new',
component: JudgeEditorComponent, component: JudgeEditorComponent,
// canActivate: [AuthGuardService, RoleGuardService], canActivate: [AuthGuardService],
}, { }, {
path: 'judge/edit/:id', path: 'judge/edit/:id',
component: JudgeEditorComponent, component: JudgeEditorComponent,
resolve: { resolve: {
judge: JudgeResolverService, judge: JudgeResolverService,
} },
// canActivate: [AuthGuardService, RoleGuardService], canActivate: [AuthGuardService],
} }, {
path: '',
redirectTo: '/awardees',
pathMatch: 'full',
canActivate: [AuthGuardService]
},
// {path: '**', component: PageNotFoundComponent},
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes)], imports: [RouterModule.forRoot(routes)],
exports: [RouterModule] exports: [RouterModule]
}) })
export class AppRoutingModule {} export class AppRoutingModule {
}

View File

@ -1,8 +1,15 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AuthService } from "./auth/auth.service";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent {} export class AppComponent {
constructor(private authService: AuthService) {}
get loggedIn(): boolean {
return this.authService.isLoggedIn;
}
}

View File

@ -34,6 +34,7 @@ import { AwardeeListTableComponent } from './awardee-list-table/awardee-list-tab
import { AwardeeEditorComponent } from './awardee-editor/awardee-editor.component'; import { AwardeeEditorComponent } from './awardee-editor/awardee-editor.component';
import { JudgeEditorComponent } from './judge-editor/judge-editor.component'; import { JudgeEditorComponent } from './judge-editor/judge-editor.component';
import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';
import { AuthModule } from "./auth/auth.module";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -55,7 +56,6 @@ import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.componen
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
AppRoutingModule,
LayoutModule, LayoutModule,
MatToolbarModule, MatToolbarModule,
MatButtonModule, MatButtonModule,
@ -74,6 +74,9 @@ import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.componen
MatExpansionModule, MatExpansionModule,
MatTooltipModule, MatTooltipModule,
MatDialogModule, MatDialogModule,
AuthModule,
AppRoutingModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthGuardService } from './auth-guard.service';
describe('AuthGuardService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthGuardService]
});
});
it('should be created', inject([AuthGuardService], (service: AuthGuardService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router";
import { AuthService } from "./auth.service";
@Injectable({
providedIn: 'root'
})
export class AuthGuardService {
constructor(private authService: AuthService,
private router: Router) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) {
return true;
}
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthComponent } from "./auth/auth.component";
const routes: Routes = [
{path: 'login', component: AuthComponent},
{path: 'logout', component: AuthComponent},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AuthRoutingModule { }

View File

@ -0,0 +1,13 @@
import { AuthModule } from './auth.module';
describe('AuthModule', () => {
let authModule: AuthModule;
beforeEach(() => {
authModule = new AuthModule();
});
it('should create an instance', () => {
expect(authModule).toBeTruthy();
});
});

View File

@ -0,0 +1,43 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from "@angular/forms";
import { JwtModule } from '@auth0/angular-jwt';
import { AuthRoutingModule } from './auth-routing.module';
import { AuthComponent } from './auth/auth.component';
import {
MatButtonModule,
MatCardModule,
MatFormFieldModule,
MatInputModule
} from "@angular/material";
export function tokenGetterFunctionWrapper() {
return localStorage.getItem('token');
}
@NgModule({
imports: [
JwtModule.forRoot({
config: {
tokenGetter: tokenGetterFunctionWrapper,
whitelistedDomains: [
"localhost:8888",
"granprize.dev.yvan.hu",
"granprize.swedishchamber.hu",
],
},
}),
CommonModule,
FormsModule,
AuthRoutingModule,
MatCardModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
],
declarations: [AuthComponent]
})
export class AuthModule {
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthService]
});
});
it('should be created', inject([AuthService], (service: AuthService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,79 @@
import { Injectable } from '@angular/core';
import { environment } from "../../environments/environment";
import { HttpClient } from "@angular/common/http";
import { Router } from "@angular/router";
import { JwtHelperService } from "@auth0/angular-jwt";
const TOKEN_LS_NAME = 'token';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private url = environment.apiUrl + '/auth';
public redirectUrl: string;
public isLoading: boolean = false;
public hasError: boolean = false;
public errorMessage: string = "";
constructor(private httpService: HttpClient,
private jwtHelperService: JwtHelperService,
private router: Router) {
}
get token(): string {
return localStorage.getItem(TOKEN_LS_NAME);
}
get isLoggedIn(): boolean {
try {
return !this.jwtHelperService.isTokenExpired(this.token);
} catch (ex) {
return false;
}
}
get tokenData() {
return this.jwtHelperService.decodeToken(this.token);
}
public authRedirect() {
this.router.navigate(['/login']);
}
public login(login: string, password: string) {
this.hasError = false;
this.isLoading = true;
this.httpService.post<string>(this.url + '/login',{
'user': login,
'pass': password,
}).subscribe(
apiResponse => {
this.isLoading = false;
localStorage.setItem(TOKEN_LS_NAME, apiResponse);
this.router.navigate(['/']);
},
err => {
console.log(err);
this.hasError = true;
this.errorMessage = "Hiba történt bejelentkezés közben.";
this.isLoading = false;
}
);
}
public renew() {
this.httpService.get<string>(this.url + '/renew')
.subscribe(
apiResponse => {
localStorage.setItem(TOKEN_LS_NAME, apiResponse);
},
err => console.log(err)
);
}
public logout() {
localStorage.removeItem(TOKEN_LS_NAME);
}
}

View File

@ -0,0 +1,9 @@
.mat-card {
min-width: 250px;
width: 25%;
margin: 150px auto auto;
}
.mat-form-field {
width: 100%;
}

View File

@ -0,0 +1,12 @@
<mat-card>
<h2 mat-card-title="">GranPrize login</h2>
<form name="login-form" class="login-form" (ngSubmit)="doSubmit()">
<mat-form-field class="full-width">
<input name="username" type="text" matInput placeholder="Username" [(ngModel)]="userName">
</mat-form-field>
<mat-form-field class="full-width">
<input name="password" type="password" matInput placeholder="Password" [(ngModel)]="password">
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!canLogin">Login</button>
</form>
</mat-card>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthComponent } from './auth.component';
describe('AuthComponent', () => {
let component: AuthComponent;
let fixture: ComponentFixture<AuthComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AuthComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,60 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from "../auth.service";
import { Router } from "@angular/router";
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css']
})
export class AuthComponent implements OnInit {
public userName: string = '';
public password: string = '';
constructor(
private authService: AuthService,
private router: Router
) {
switch (this.router.url) {
case '/logout':
this.authService.logout();
this.authService.authRedirect();
return;
}
}
ngOnInit() {
if (this.authService.isLoggedIn) {
this.router.navigate(['/']);
}
}
get canLogin(): boolean {
return [
this.userName,
this.password,
].every(field => field.trim().length > 0);
}
public doSubmit() {
if (this.canLogin) {
this.authService.login(
this.userName.trim(),
this.password.trim()
);
}
}
get hasError(): boolean {
return this.authService.hasError;
}
get isLoading(): boolean {
return this.authService.isLoading;
}
get errorMessage(): string {
return this.authService.errorMessage;
}
}

View File

@ -33,7 +33,7 @@
<mat-paginator #paginator <mat-paginator #paginator
[length]="awardees.length" [length]="awardees.length"
[pageIndex]="0" [pageIndex]="0"
[pageSize]="50" [pageSize]="15"
[pageSizeOptions]="[25, 50, 100, 250]"> [pageSizeOptions]="[15, 25, 50]">
</mat-paginator> </mat-paginator>
</div> </div>

View File

@ -6,3 +6,7 @@
width: 200px; width: 200px;
box-shadow: 3px 0 6px rgba(0,0,0,.24); box-shadow: 3px 0 6px rgba(0,0,0,.24);
} }
.nav-logout {
margin-top: 3em;
}

View File

@ -1,5 +1,5 @@
<mat-sidenav-container class="sidenav-container"> <mat-sidenav-container class="sidenav-container">
<mat-sidenav <mat-sidenav *ngIf="loggedIn"
#drawer #drawer
class="sidenav" class="sidenav"
fixedInViewport="true" fixedInViewport="true"
@ -8,12 +8,22 @@
[opened]="!(isHandset | async)!.matches"> [opened]="!(isHandset | async)!.matches">
<mat-toolbar color="primary">Menu</mat-toolbar> <mat-toolbar color="primary">Menu</mat-toolbar>
<mat-nav-list> <mat-nav-list>
<a mat-list-item [routerLink]="['judges']">Judges</a> <a mat-list-item [routerLink]="['judges']">
<a mat-list-item [routerLink]="['awardees']">Awardees</a> <mat-icon fontSet="fas" fontIcon="fa-gavel"></mat-icon>
Judges
</a>
<a mat-list-item [routerLink]="['awardees']">
<mat-icon fontSet="fas" fontIcon="fa-trophy"></mat-icon>
Awardees
</a>
<a mat-list-item [routerLink]="['logout']" class="nav-logout">
<mat-icon fontSet="fas" fontIcon="fa-sign-out-alt"></mat-icon>
Logout
</a>
</mat-nav-list> </mat-nav-list>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content> <mat-sidenav-content>
<mat-toolbar color="primary"> <mat-toolbar color="primary" *ngIf="loggedIn">
<button <button
type="button" type="button"
aria-label="Toggle sidenav" aria-label="Toggle sidenav"

View File

@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout'; import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Title } from "@angular/platform-browser"; import { Title } from "@angular/platform-browser";
import { AuthService } from "../auth/auth.service";
@Component({ @Component({
selector: 'app-navigation', selector: 'app-navigation',
@ -12,9 +13,14 @@ export class NavigationComponent {
isHandset: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.Handset); isHandset: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.Handset);
constructor( constructor(
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private titleService: Title private titleService: Title,
private authService: AuthService,
) {} ) {}
get title(): string { get title(): string {
return this.titleService.getTitle(); return this.titleService.getTitle();
} }
get loggedIn(): boolean {
return this.authService.isLoggedIn;
}
} }

View File

@ -1 +1,6 @@
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
html,
body {
height: 100%;
margin: 0;
}