Skip to content

Commit c5f9418

Browse files
committed
feat(frontend): login form (work in progress)
1 parent df83f9a commit c5f9418

File tree

9 files changed

+121
-13
lines changed

9 files changed

+121
-13
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
"e2e": "protractor",
7777
"e2e:live": "protractor --elementExplorer",
7878
"lint": "ng lint",
79-
"lint:fix": "tslint --fix \"src/main/typescript/**/*.ts\" --project src/main/typescript/tsconfig.json --type-check && tslint --fix \"e2e/**/*.ts\" --project e2e/tsconfig.json --type-check",
8079
"pretest": "rimraf coverage && yarn run lint",
8180
"test": "ng test -w false --code-coverage --progress false",
8281
"test:live": "ng test --progress false",

src/main/typescript/app/auth/login/login.component.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
<form class="login">
44
<label class="title">Shardis Login Page</label>
55
<div class="login-group">
6-
<input class="username" type="text" id="login_username" placeholder="Username">
7-
<input class="password" type="password" id="login_password" placeholder="Password">
6+
<input class="username" name="username" type="text" id="login_username" placeholder="Username" [(ngModel)]="username">
7+
<input class="password" name="password" type="password" id="login_password" placeholder="Password" [(ngModel)]="password">
88
<div class="checkbox">
99
<input type="checkbox" id="rememberme">
1010
<label for="rememberme">
1111
Remember me
1212
</label>
1313
</div>
14-
<div class="error active">
15-
Invalid user name or password
14+
<div class="error active" *ngIf="authError$ | async">
15+
{{ authError$ | async }}
1616
</div>
17-
<button type="submit" class="btn btn-primary">LOG IN</button>
17+
<button type="submit" class="btn btn-primary" (click)="authenticate()">LOG IN</button>
1818
<a href="#" [routerLink]="['/register']" class="signup">Sign up for an account</a>
1919
</div>
2020
</form>

src/main/typescript/app/auth/login/login.component.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {RouterTestingModule} from '@angular/router/testing';
55
import {CoreModule} from '../../core/core.module';
66
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
77
import {ClarityModule} from 'clarity-angular';
8+
import {AuthService} from '../../core/store/auth/auth.service';
9+
import {MockBackend} from '@angular/http/testing';
10+
import {Http, ConnectionBackend, RequestOptions, BaseRequestOptions} from '@angular/http';
811

912
describe('LoginComponent', () => {
1013
let fixture: ComponentFixture<LoginComponent>;
@@ -20,6 +23,18 @@ describe('LoginComponent', () => {
2023
ReactiveFormsModule,
2124
ClarityModule.forRoot(),
2225
RouterTestingModule
26+
],
27+
providers: [
28+
BaseRequestOptions,
29+
MockBackend,
30+
{
31+
provide: Http,
32+
useFactory: function (backend: ConnectionBackend, defaultOptions: RequestOptions) {
33+
return new Http(backend, defaultOptions);
34+
},
35+
deps: [MockBackend, BaseRequestOptions]
36+
},
37+
AuthService
2338
]
2439
})
2540
.compileComponents();

src/main/typescript/app/auth/login/login.component.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import {Component, OnInit} from '@angular/core';
2+
import {AuthService} from '../../core/store/auth/auth.service';
3+
import {Observable} from 'rxjs/Observable';
4+
import {Store} from '@ngrx/store';
5+
import {RootState} from '../../core/store/index';
26

37
@Component({
48
selector: 'shardis-login',
@@ -7,7 +11,17 @@ import {Component, OnInit} from '@angular/core';
711
})
812
export class LoginComponent implements OnInit {
913

10-
constructor() {
14+
username = 'admin';
15+
password = 'xxxxxx';
16+
authError$: Observable<string>;
17+
18+
constructor(private authService: AuthService, private store: Store<RootState>) {
19+
this.authError$ = store.select(s => s.auth.error);
20+
}
21+
22+
authenticate() {
23+
console.log('authenticate');
24+
this.authService.authenticate(this.username, this.password);
1125
}
1226

1327
ngOnInit() {

src/main/typescript/app/core/store/auth/auth.actions.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {uniqueActionType} from '../shared/unique-action-type';
33

44
export const ActionTypes = {
55
LOGOUT: uniqueActionType('[Auth] Logout'),
6+
PROCESS_TOKEN: uniqueActionType('[Auth] Process token'),
7+
AUTH_ERROR: uniqueActionType('[Auth] Auth error')
68
};
79

810
export class LogoutAction implements Action {
@@ -12,6 +14,22 @@ export class LogoutAction implements Action {
1214
}
1315
}
1416

17+
export class ProcessTokenAction implements Action {
18+
type = ActionTypes.PROCESS_TOKEN;
19+
20+
constructor(public jwtToken: string) {
21+
}
22+
}
23+
24+
export class AuthErrorAction implements Action {
25+
type = ActionTypes.AUTH_ERROR;
26+
27+
constructor(public error: string) {
28+
}
29+
}
30+
1531

1632
export type Actions
17-
= LogoutAction;
33+
= LogoutAction
34+
| ProcessTokenAction
35+
| AuthErrorAction;

src/main/typescript/app/core/store/auth/auth.reducer.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,29 @@ import 'rxjs/add/operator/map';
33
import 'rxjs/add/operator/let';
44
import {AuthState, initialState} from './auth.state';
55
import {Actions, ActionTypes} from './auth.actions';
6+
import {AuthService} from './auth.service';
67

78
export function authReducer(state = initialState, action: Actions): AuthState {
89
switch (action.type) {
10+
911
case ActionTypes.LOGOUT:
10-
return initialState;
12+
return Object.assign({}, initialState);
13+
14+
case ActionTypes.PROCESS_TOKEN:
15+
const userData = AuthService.decodeAccessToken(action.jwtToken);
16+
return Object.assign({}, initialState, {
17+
authenticated: true,
18+
tokenExpirationDate: new Date(userData.exp * 1000),
19+
userData: userData,
20+
jwtToken: action.jwtToken
21+
});
22+
23+
case ActionTypes.AUTH_ERROR:
24+
return Object.assign({}, userData, {error: action.error});
25+
1126
default:
1227
return state;
1328
}
1429
}
1530

16-
1731
export const isAuthenticated = (state: AuthState) => state.authenticated;
Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import {Injectable} from '@angular/core';
2-
import {Http} from '@angular/http';
2+
import {Http, Headers} from '@angular/http';
33
import {Store} from '@ngrx/store';
44
import {RootState} from '../index';
55
import {Observable} from 'rxjs/Observable';
6+
import {LogoutAction, ProcessTokenAction, AuthErrorAction} from './auth.actions';
67

78

89
@Injectable()
910
export class AuthService {
1011

1112
private authenticated$: Observable<boolean>;
1213

14+
public static decodeAccessToken(access_token: string) {
15+
return JSON.parse(window.atob(access_token.split('.')[1]));
16+
}
17+
1318
constructor(public http: Http, private store: Store<RootState>) {
1419
this.authenticated$ = store.select(s => s.auth.authenticated);
1520
}
@@ -18,4 +23,45 @@ export class AuthService {
1823
return this.authenticated$;
1924
}
2025

26+
public authenticate(username: string, password: string): void {
27+
28+
console.log('Authentication pending...');
29+
30+
if (!username.trim()) {
31+
this.store.dispatch(new AuthErrorAction('Username cannot be blank'));
32+
return;
33+
}
34+
if (!password.trim()) {
35+
this.store.dispatch(new AuthErrorAction('Password cannot be blank'));
36+
return;
37+
}
38+
39+
const headers = new Headers();
40+
headers.append('Content-Type', `application/x-www-form-urlencoded`);
41+
42+
const payload = 'username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password);
43+
44+
this.http
45+
.post('/api/authentication', payload, {headers: headers})
46+
.subscribe(
47+
data => {
48+
const jwtToken = data.text();
49+
localStorage.setItem('tokenData', jwtToken);
50+
this.store.dispatch(new ProcessTokenAction(jwtToken));
51+
},
52+
err => {
53+
console.log(err);
54+
this.store.dispatch(new AuthErrorAction('Username and password doesn\'t match'));
55+
return;
56+
}
57+
);
58+
59+
}
60+
61+
public logout(): void {
62+
localStorage.removeItem('tokenData');
63+
this.store.dispatch(new LogoutAction());
64+
}
65+
66+
2167
}

src/main/typescript/app/core/store/auth/auth.state.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ export interface AuthState {
33
tokenExpirationDate: Date;
44
userData: any;
55
jwtToken: string;
6+
error: string;
67
}
78

89
export const initialState: AuthState = {
910
authenticated: false,
1011
tokenExpirationDate: null,
1112
userData: null,
12-
jwtToken: null
13+
jwtToken: null,
14+
error: null
1315
};
1416

1517

src/main/typescript/app/core/store/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const reducers = {
2424

2525
const developmentReducer: ActionReducer<RootState> = compose(
2626
storeFreeze,
27-
localStorageSync(['counter', 'auth'], true),
27+
localStorageSync(['counter', 'auth', 'router'], true),
2828
combineReducers)(reducers);
2929

3030
const productionReducer: ActionReducer<RootState> = compose(combineReducers)(reducers);

0 commit comments

Comments
 (0)