Compare commits

...

3 Commits

Author SHA1 Message Date
Florian Hartwich aa96f49ae1 Add admin panel and app-user route 2017-06-08 19:46:36 +02:00
Florian Hartwich 0d576b7268 Adjust menu for different permissions 2017-06-08 16:58:28 +02:00
Florian Hartwich b5bcc676ed backend register & activation route 2017-06-08 16:03:20 +02:00
25 changed files with 646 additions and 135 deletions

View File

@ -10,4 +10,5 @@ module.exports = {
signatures: '/signatures', signatures: '/signatures',
squads: '/squads', squads: '/squads',
users: '/users', users: '/users',
account: '/account'
}; };

View File

@ -5,7 +5,6 @@ let check = (requiredPermission, actualPermission, res, next) => {
if (actualPermission >= requiredPermission) { if (actualPermission >= requiredPermission) {
return next(); return next();
} }
return res.status(403).send({ return res.status(403).send({
success: false, success: false,
message: 'permission denied' message: 'permission denied'

View File

@ -13,6 +13,11 @@ const AppUserSchema = new Schema({
type: String, type: String,
required: true required: true
}, },
squad: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Squad',
default: null
},
permission: { permission: {
type: Number, type: Number,
get: v => Math.round(v), get: v => Math.round(v),
@ -20,6 +25,14 @@ const AppUserSchema = new Schema({
min: 0, min: 0,
max: 4, max: 4,
default: 0 default: 0
},
secret: {
type: String,
required: true
},
activated: {
type: Boolean,
default: false
} }
}, { }, {
collection: 'app_user', collection: 'app_user',

90
api/routes/account.js Normal file
View File

@ -0,0 +1,90 @@
"use strict";
// modules
const express = require('express');
const logger = require('debug')('cc:awardings');
// HTTP status codes by name
const codes = require('./http-codes');
const routerHandling = require('../middleware/router-handling');
// Mongoose Model using mongoDB
const AppUserModel = require('../models/app-user');
const account = express.Router();
account.route('/')
.get((req, res, next) => {
AppUserModel.find({}).populate('squad').exec((err, items) => {
if (err) {
err.status = codes.servererror;
return next(err);
}
res.locals.items = items;
res.locals.processed = true;
next();
})
})
.all(
routerHandling.httpMethodNotAllowed
);
// routes **********************
account.route('/:id')
.patch((req, res, next) => {
if (!req.body || (req.body._id && req.body._id !== req.params.id)) {
// little bit different as in PUT. :id does not need to be in data, but if the _id and url id must match
const err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + " " + req.body._id);
err.status = codes.notfound;
next(err);
return; // prevent node to process this function further after next() has finished.
}
// increment version manually as we do not use .save(.)
req.body.updatedAt = new Date();
req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing.
AppUserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}).populate('squad').exec((err, item) => {
if (err) {
err.status = codes.wrongrequest;
}
else if (!item) {
err = new Error("appUser not found");
err.status = codes.notfound;
}
else {
res.locals.items = item;
}
next(err);
})
})
.delete((req, res, next) => {
AppUserModel.findByIdAndRemove(req.params.id, (err, item) => {
if (err) {
err.status = codes.wrongrequest;
}
else if (!item) {
err = new Error("item not found");
err.status = codes.notfound;
}
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..)
res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter
});
})
.all(
routerHandling.httpMethodNotAllowed
);
// this middleware function can be used, if you like or remove it
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
account.use(routerHandling.emptyResponse);
module.exports = account;

View File

@ -8,6 +8,9 @@ const Q = require('q');
const _ = require('lodash'); const _ = require('lodash');
const logger = require('debug')('cc:authenticate'); const logger = require('debug')('cc:authenticate');
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
const checkAdmin = require('../middleware/permission-check').checkAdmin;
// HTTP status codes by name // HTTP status codes by name
const codes = require('./http-codes'); const codes = require('./http-codes');
@ -22,7 +25,7 @@ const authenticate = express.Router();
// routes ********************** // routes **********************
authenticate.route('/') authenticate.route('/')
.post((req, res, next) => { .post((req, res, next) => {
authCheck(req.body.username, req.body.password) authCheck(req.body.username, req.body.password, res)
.then((user) => { .then((user) => {
if (user) { if (user) {
// authentication successful // authentication successful
@ -41,7 +44,7 @@ authenticate.route('/')
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
let authCheck = (username, password) => { let authCheck = (username, password, res) => {
const deferred = Q.defer(); const deferred = Q.defer();
AppUserModel.findOne({username: username}, (err, user) => { AppUserModel.findOne({username: username}, (err, user) => {
@ -49,11 +52,15 @@ let authCheck = (username, password) => {
const diff = 3 * 60 * 24; // time till expiration [minutes] const diff = 3 * 60 * 24; // time till expiration [minutes]
if (user && bcrypt.compareSync(password, user.password)) { if (user && !user.activated) {
res.status(codes.unauthorized).send('Account is not yet activated');
}
if (user && user.activated && bcrypt.compareSync(password, user.password)) {
// authentication successful // authentication successful
deferred.resolve({ deferred.resolve({
_id: user._id, _id: user._id,
username: user.username, username: user.username,
permission: user.permission,
token: jwt.sign({sub: user._id}, config.secret, {expiresIn: diff * 60}), token: jwt.sign({sub: user._id}, config.secret, {expiresIn: diff * 60}),
tokenExpireDate: new Date(Date.now().valueOf() + diff * 60000 - 1000) tokenExpireDate: new Date(Date.now().valueOf() + diff * 60000 - 1000)
}); });
@ -66,58 +73,57 @@ let authCheck = (username, password) => {
return deferred.promise; return deferred.promise;
}; };
// ******************************** SIGNUP ************************
//******************************** SIGNUP ************************ authenticate.route('/signup')
// .post((req, res, next) => {
// authenticate.route('/signup') create(req.body)
// .post((req, res, next) => { .then(() => {
// create(req.body) res.sendStatus(200);
// .then(() => { })
// res.sendStatus(200); .catch((err) => {
// }) res.status(400).send(err);
// .catch((err) => { });
// res.status(400).send(err); })
// });
// }) .all(
// routerHandling.httpMethodNotAllowed
// .all( );
// routerHandling.httpMethodNotAllowed
// ); let create = (userParam) => {
// const deferred = Q.defer();
// let create = (userParam) => {
// const deferred = Q.defer(); // validation
// AppUserModel.findOne(
// // validation {username: userParam.username},
// AppUserModel.findOne( (err, user) => {
// {username: userParam.username}, if (err) deferred.reject(err.name + ': ' + err.message);
// (err, user) => {
// if (err) deferred.reject(err.name + ': ' + err.message); if (user) {
// // username already exists
// if (user) { deferred.reject('Username "' + userParam.username + '" is already taken');
// // username already exists } else {
// deferred.reject('Username "' + userParam.username + '" is already taken'); createUser();
// } else { }
// createUser(); });
// }
// }); let createUser = () => {
// // set user object to userParam without the cleartext password
// let createUser = () => { const user = _.omit(userParam, 'password');
// // set user object to userParam without the cleartext password
// const user = _.omit(userParam, 'password'); // add hashed password to user object
// user.password = bcrypt.hashSync(userParam.password, 10);
// // add hashed password to user object
// user.password = bcrypt.hashSync(userParam.password, 10); const newUser = new AppUserModel(user);
// newUser.save((err, doc) => {
// const newUser = new AppUserModel(user); if (err) deferred.reject(err.name + ': ' + err.message);
// newUser.save((err, doc) => {
// if (err) deferred.reject(err.name + ': ' + err.message); deferred.resolve();
// });
// deferred.resolve(); };
// });
// }; return deferred.promise;
// };
// return deferred.promise;
// };
authenticate.use(routerHandling.emptyResponse); authenticate.use(routerHandling.emptyResponse);

View File

@ -187,7 +187,7 @@ users.route('/:id')
return; // prevent node to process this function further after next() has finished. return; // prevent node to process this function further after next() has finished.
} }
// main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema // main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema
var video = new UserModel(req.body); var user = new UserModel(req.body);
UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) { UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) {
// with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one. // with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one.
if (err) { if (err) {
@ -200,7 +200,7 @@ users.route('/:id')
return next(err); return next(err);
} }
// optional task 3b: check that version is still accurate // optional task 3b: check that version is still accurate
else if (video.__v !== item.__v) { else if (user.__v !== item.__v) {
err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again") err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again")
err.status = codes.conflict; err.status = codes.conflict;
return next(err); return next(err);
@ -209,7 +209,7 @@ users.route('/:id')
for (var field in UserModel.schema.paths) { for (var field in UserModel.schema.paths) {
if ((field !== '_id') && (field !== '__v')) { if ((field !== '_id') && (field !== '__v')) {
// this includes undefined. is important to reset attributes that are missing in req.body // this includes undefined. is important to reset attributes that are missing in req.body
item.set(field, video[field]); item.set(field, user[field]);
} }
} }

View File

@ -21,6 +21,7 @@ const signatureCronJob = require('./cron-job/update-signatures');
// router modules // router modules
const authenticateRouter = require('./routes/authenticate'); const authenticateRouter = require('./routes/authenticate');
const accountRouter = require('./routes/account');
const overviewRouter = require('./routes/overview'); const overviewRouter = require('./routes/overview');
const userRouter = require('./routes/users'); const userRouter = require('./routes/users');
const squadRouter = require('./routes/squads'); const squadRouter = require('./routes/squads');
@ -73,6 +74,7 @@ app.use(urls.ranks, rankRouter);
app.use(urls.decorations, decorationRouter); app.use(urls.decorations, decorationRouter);
app.use(urls.awards, apiAuthenticationMiddleware, checkHl, awardingRouter); app.use(urls.awards, apiAuthenticationMiddleware, checkHl, awardingRouter);
app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter); app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter);
app.use(urls.account, apiAuthenticationMiddleware, checkAdmin, accountRouter);
// send index.html on all different paths // send index.html on all different paths
app.use(function (req, res) { app.use(function (req, res) {

View File

@ -0,0 +1,34 @@
.overview {
position: fixed;
overflow-y: scroll;
overflow-x: hidden;
bottom: 20px;
width: 100%;
padding-left: 50px;
padding-top: 190px;
margin-left: 10px;
height: 100vh;
}
.trash {
cursor: pointer;
}
.table {
overflow-wrap: break-word;
table-layout: fixed;
}
.table-container {
margin-top: 10px;
overflow-x: auto;
}
.table-head {
background: #222222;
color: white;
}
.cell-outline {
outline: 1px solid #D4D4D4;
}

View File

@ -0,0 +1,71 @@
<div class="overview">
<h2>Admin Panel</h2>
<span *ngIf="showSuccessLabel"
class="label label-success label-small"
style="margin-left: inherit">
Erfolgreich gespeichert
</span>
<div class="pull-left" style="margin-top:20px;">
<div class="table-container" style="width: 75%; min-width: 500px">
<table class="table table-hover">
<thead>
<tr class="table-head">
<th class="col-sm-1" style="border-radius: 10px 0 0 0;">Username</th>
<th class="col-sm-1">Activated</th>
<th class="col-sm-1">Secret</th>
<th class="col-sm-1">Fraktion/ Squad</th>
<th class="col-sm-1">Permission</th>
<th class="col-sm-1 text-center" style="border-radius: 0 10px 0 0;"></th>
</tr>
</thead>
<tbody *ngFor="let user of users$ | async">
<tr class="cell-outline">
<td>
{{user.username}}
</td>
<td style="font-weight: bold">
<select id="activated" name="activated" class="form-control btn dropdown-toggle"
[(ngModel)]="user.activated" (change)="updateAppUser(user)">
<option value="true">activated</option>
<option value="false">deactivated</option>
</select>
</td>
<td>
{{user.secret}}
</td>
<td>
<select class="form-control"
name="squad"
id="squad"
[(ngModel)]="user.squad"
[compareWith]="equals"
(change)="updateAppUser(user)">
<option [value]="0">Ohne Fraktion/ Squad</option>
<option *ngFor="let squad of squads" [ngValue]="squad">
{{squad.fraction == 'BLUFOR'? 'NATO' : 'CSAT'}}: {{squad.name}}
</option>
</select>
</td>
<td>
<select id="permission" name="permission" class="form-control btn dropdown-toggle"
[(ngModel)]="user.permission" (change)="updateAppUser(user)">
<option value="0">User</option>
<option value="1">SQL</option>
<option value="2">HL</option>
<option value="3">MT</option>
<option value="4">Admin</option>
</select>
</td>
<td class="text-center">
<span class="glyphicon glyphicon-trash trash" title="Löschen" (click)="deleteUser(user)"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,70 @@
import {Component} from "@angular/core";
import {AppUser, Squad} from "../models/model-interfaces";
import {Observable} from "rxjs/Observable";
import {AppUserService} from "../services/app-user-service/app-user.service";
import {SquadService} from "../services/squad-service/squad.service";
@Component({
selector: 'admin-panel',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.css']
})
export class AdminComponent {
users$: Observable<AppUser[]>;
squads: Squad[] = [];
showSuccessLabel = false;
constructor(private appUserService: AppUserService,
private squadService: SquadService) {
}
ngOnInit() {
this.users$ = this.appUserService.getUsers();
this.squadService.findSquads().subscribe(squads => {
this.squads = squads;
});
}
updateAppUser(user) {
let updateObject = {
_id: user._id,
squad: user.squad,
activated: user.activated,
permission: user.permission
};
if (updateObject.squad === "0") {
updateObject.squad = null;
}
this.appUserService.updateUser(updateObject)
.subscribe(user => {
this.showSuccessLabel = true;
setTimeout(() => {
this.showSuccessLabel = false;
}, 2000)
})
}
deleteUser(user) {
if (confirm('Soll der Nutzer "' + user.username + '" wirklich gelöscht werden?')) {
this.appUserService.deleteUser(user)
.subscribe((res) => {
})
}
}
/**
* compare ngValue with ngModel to assign selected element
*/
equals(o1: Squad , o2: Squad) {
if (o1 && o2) {
return o1._id === o2._id;
}
}
}

View File

@ -21,23 +21,41 @@
<li routerLinkActive="active"> <li routerLinkActive="active">
<a routerLink='/cc-overview' class="link">Armeeübersicht</a> <a routerLink='/cc-overview' class="link">Armeeübersicht</a>
</li> </li>
<li *ngIf="loginService.isLoggedIn()" routerLinkActive="active"> <li *ngIf="loginService.hasPermission(2)" routerLinkActive="active">
<a routerLink='/cc-users' class="link">Teilnehmer</a> <a routerLink='/cc-users' class="link">Teilnehmer</a>
</li> </li>
<li *ngIf="loginService.isLoggedIn()" routerLinkActive="active"> <li *ngIf="loginService.hasPermission(2)" routerLinkActive="active">
<a routerLink='/cc-squads' class="link">Squads</a> <a routerLink='/cc-squads' class="link">Squads</a>
</li> </li>
<li *ngIf="loginService.isLoggedIn()" routerLinkActive="active"> <li *ngIf="loginService.hasPermission(2)" routerLinkActive="active">
<a routerLink='/cc-decorations' class="link">Auszeichnungen</a> <a routerLink='/cc-decorations' class="link">Auszeichnungen</a>
</li> </li>
<li *ngIf="loginService.isLoggedIn()" routerLinkActive="active"> <li *ngIf="loginService.hasPermission(2)" routerLinkActive="active">
<a routerLink='/cc-ranks' class="link">Ränge</a> <a routerLink='/cc-ranks' class="link">Ränge</a>
</li> </li>
<li *ngIf="loginService.hasPermission(1) && !loginService.hasPermission(2)" class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
Beantragen
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a [routerLink]="['/request-promotion']">Beförderung</a>
</li>
<li>
<a [routerLink]="['/request-award']">Orden/ Auszeichnung</a>
</li>
</ul>
</li>
</ul> </ul>
<ul class="nav navbar-nav" style="float: right"> <ul class="nav navbar-nav" style="float: right">
<li *ngIf="loginService.hasPermission(4)" routerLinkActive="active">
<a routerLink='/admin-panel' class="link">Admin Panel</a>
</li>
<li *ngIf="authEnabled" class="link" style="cursor: pointer"> <li *ngIf="authEnabled" class="link" style="cursor: pointer">
<a *ngIf="loginService.isLoggedIn()" (click)="logout()" >Abmelden</a> <a *ngIf="loginService.isLoggedIn()" (click)="logout()">Abmelden</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -3,9 +3,11 @@ export class AppConfig {
public readonly apiUrl = ''; public readonly apiUrl = '';
public readonly apiAppUserPath = '/account/';
public readonly apiAwardPath = '/awardings/'; public readonly apiAwardPath = '/awardings/';
public readonly apiDecorationPath = '/decorations/'; public readonly apiDecorationPath = '/decorations/';
public readonly apiAuthenticationPath = '/authenticate'; public readonly apiAuthenticationPath = '/authenticate';
public readonly apiSignupPath = '/authenticate/signup';
public readonly apiRankPath = '/ranks/'; public readonly apiRankPath = '/ranks/';
public readonly apiSquadPath = '/squads/'; public readonly apiSquadPath = '/squads/';
public readonly apiUserPath = '/users/'; public readonly apiUserPath = '/users/';

View File

@ -22,19 +22,25 @@ import {RankStore} from "./services/stores/rank.store";
import {RankService} from "./services/rank-service/rank.service"; import {RankService} from "./services/rank-service/rank.service";
import {DecorationItemComponent} from "./decorations/decoration-list/decoration-item.component"; import {DecorationItemComponent} from "./decorations/decoration-list/decoration-item.component";
import {AppConfig} from "./app.config"; import {AppConfig} from "./app.config";
import {LoginGuard} from "./login/login.guard"; import {LoginGuardAdmin, LoginGuardHL, LoginGuardSQL} from "./login/login.guard";
import {AwardingService} from "./services/awarding-service/awarding.service"; import {AwardingService} from "./services/awarding-service/awarding.service";
import {HttpClient} from "./services/http-client"; import {HttpClient} from "./services/http-client";
import {ArmyService} from "./services/army-service/army.service"; import {ArmyService} from "./services/army-service/army.service";
import { ClipboardModule } from 'ngx-clipboard'; import { ClipboardModule } from 'ngx-clipboard';
import {AppUserService} from "./services/app-user-service/app-user.service";
import {AppUserStore} from "./services/stores/app-user.store";
@NgModule({ @NgModule({
imports: [BrowserModule, FormsModule, ReactiveFormsModule, appRouting, HttpModule, ClipboardModule], imports: [BrowserModule, FormsModule, ReactiveFormsModule, appRouting, HttpModule, ClipboardModule],
providers: [ providers: [
HttpClient, HttpClient,
LoginService, LoginService,
LoginGuard, LoginGuardSQL,
LoginGuardHL,
LoginGuardAdmin,
ArmyService, ArmyService,
AppUserService,
AppUserStore,
UserService, UserService,
UserStore, UserStore,
SquadService, SquadService,

View File

@ -1,12 +1,14 @@
import {Routes, RouterModule} from '@angular/router'; import {Routes, RouterModule} from '@angular/router';
import {LoginComponent} from './login/index'; import {LoginComponent} from './login/index';
import {NotFoundComponent} from './not-found/not-found.component'; import {NotFoundComponent} from './not-found/not-found.component';
import {LoginGuard} from './login/login.guard'; import {LoginGuardAdmin, LoginGuardHL} from './login/login.guard';
import {usersRoutes, usersRoutingComponents} from "./users/users.routing"; import {usersRoutes, usersRoutingComponents} from "./users/users.routing";
import {squadsRoutes, squadsRoutingComponents} from "./squads/squads.routing"; import {squadsRoutes, squadsRoutingComponents} from "./squads/squads.routing";
import {decorationsRoutes, decorationsRoutingComponents} from "./decorations/decoration.routing"; import {decorationsRoutes, decorationsRoutingComponents} from "./decorations/decoration.routing";
import {ranksRoutes, ranksRoutingComponents} from "./ranks/ranks.routing"; import {ranksRoutes, ranksRoutingComponents} from "./ranks/ranks.routing";
import {armyRoutes, armyRoutingComponents} from "./army/army.routing"; import {armyRoutes, armyRoutingComponents} from "./army/army.routing";
import {SignupComponent} from "./login/signup.component";
import {AdminComponent} from "./admin/admin.component";
export const appRoutes: Routes = [ export const appRoutes: Routes = [
@ -15,10 +17,13 @@ export const appRoutes: Routes = [
{path: '', redirectTo: '/cc-overview', pathMatch: 'full'}, {path: '', redirectTo: '/cc-overview', pathMatch: 'full'},
{path: 'login', component: LoginComponent}, {path: 'login', component: LoginComponent},
{path: 'cc-users', children: usersRoutes, canActivate: [LoginGuard]}, {path: 'signup', component: SignupComponent},
{path: 'cc-squads', children: squadsRoutes, canActivate: [LoginGuard]}, {path: 'cc-users', children: usersRoutes, canActivate: [LoginGuardHL]},
{path: 'cc-decorations', children: decorationsRoutes, canActivate: [LoginGuard]}, {path: 'cc-squads', children: squadsRoutes, canActivate: [LoginGuardHL]},
{path: 'cc-ranks', children: ranksRoutes, canActivate: [LoginGuard]}, {path: 'cc-decorations', children: decorationsRoutes, canActivate: [LoginGuardHL]},
{path: 'cc-ranks', children: ranksRoutes, canActivate: [LoginGuardHL]},
{path: 'admin-panel', component: AdminComponent, canActivate: [LoginGuardAdmin]},
/** Redirect Konfigurationen **/ /** Redirect Konfigurationen **/
{path: '404', component: NotFoundComponent}, {path: '404', component: NotFoundComponent},
@ -27,7 +32,7 @@ export const appRoutes: Routes = [
export const appRouting = RouterModule.forRoot(appRoutes); export const appRouting = RouterModule.forRoot(appRoutes);
export const routingComponents = [LoginComponent, ...armyRoutingComponents , NotFoundComponent, ...usersRoutingComponents, export const routingComponents = [LoginComponent, SignupComponent, AdminComponent, ...armyRoutingComponents , NotFoundComponent, ...usersRoutingComponents,
...squadsRoutingComponents, ...decorationsRoutingComponents, ...ranksRoutingComponents]; ...squadsRoutingComponents, ...decorationsRoutingComponents, ...ranksRoutingComponents];
export const routingProviders = [LoginGuard]; export const routingProviders = [LoginGuardHL];

View File

@ -4,7 +4,7 @@
margin: 0 auto; margin: 0 auto;
} }
.form-signin .form-signin-heading, .form-signin .checkbox { .form-signin .form-signin-heading, .form-signin .checkbox, #inputEmail {
margin-bottom: 10px; margin-bottom: 10px;
} }

View File

@ -17,7 +17,7 @@
</button> </button>
<span *ngIf="showErrorLabel" <span *ngIf="showErrorLabel"
class="center-block label label-danger" style="font-size: medium; padding: 2px; margin-top: 2px"> class="center-block label label-danger" style="font-size: medium; padding: 2px; margin-top: 2px">
Login fehlgeschlagen {{error}}
</span> </span>
</div> </div>

View File

@ -13,6 +13,8 @@ export class LoginComponent implements OnInit {
showErrorLabel = false; showErrorLabel = false;
error: string;
loading = false; loading = false;
returnUrl: string; returnUrl: string;
@ -25,8 +27,8 @@ export class LoginComponent implements OnInit {
ngOnInit() { ngOnInit() {
// reset login status // reset login status
this.loginService.logout(); this.loginService.logout();
// redirect to user overview on success // redirect on success
this.returnUrl = '/cc-users' this.returnUrl = '/cc-overview'
} }
login(username: string, password: string) { login(username: string, password: string) {
@ -38,6 +40,8 @@ export class LoginComponent implements OnInit {
this.router.navigate([this.returnUrl]); this.router.navigate([this.returnUrl]);
}, },
error => { error => {
console.log(error)
this.error = error._body;
this.showErrorLabel = true; this.showErrorLabel = true;
setTimeout(() => { setTimeout(() => {
this.showErrorLabel = false; this.showErrorLabel = false;

View File

@ -2,15 +2,58 @@ import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
@Injectable() @Injectable()
export class LoginGuard implements CanActivate { export class LoginGuardSQL implements CanActivate {
constructor(private router: Router) { } constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('currentUser')) { if (localStorage.getItem('currentUser')) {
// logged in so return true let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser.permission === 1) {
// logged and correct permission so return true
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;
}
}
@Injectable()
export class LoginGuardHL implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('currentUser')) {
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser.permission >= 2) {
// logged and correct permission 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;
}
}
@Injectable()
export class LoginGuardAdmin implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('currentUser')) {
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser.permission === 4) {
// logged and correct permission so return true
return true;
}
}
// not logged in so redirect to login page with the return url // not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }}); this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});

View File

@ -0,0 +1,39 @@
<form class="form-signin" (ngSubmit)="login(userName.value, password.value, secret.value)">
<div class="row">
<h2 style="text-align: center;" class="form-signin-heading">Registrieren</h2>
<p>Dieses Formular nur ausfüllen wenn du einer <b>HL</b> angehörst oder <b>SQL</b> bist. Dabei den Nutzernamen aus dem OPT Forum verwenden!
Im Forum eine Nachricht an <a href="https://opt-dev.de/dashboard/index.php?conversation-add/&userID=9" target="_blank">HardiReady</a>
senden, in welcher der 'geheime Text' drin steht, den du bei der Registrierung nutzt.<br>
Dabei kann es sich um irgend eine willkürliche Zeichenfolge oder einen Satz handeln - dient nur dem Abgleich.
Anschließend wird dein Account aktiviert und du wirst darüber per PN informiert.</p>
<label for="inputEmail" class="sr-only">Benutzername</label>
<input #userName id="inputEmail" class="form-control" placeholder="Benutzername" required="" autofocus="">
<label for="inputPassword" class="sr-only">Passwort</label>
<input #password type="password" id="inputPassword" class="form-control" placeholder="Passwort" required="">
<label for="inputSecret" class="sr-only">Secret</label>
<input #secret type="text" id="inputSecret" class="form-control" placeholder="Geheimer Text für PN Abgleich"
required="">
<div class="form-group">
<button type="submit" class="btn btn-lg btn-block btn-primary">
<span *ngIf="!loading">Registrieren</span>
<span *ngIf="loading" class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
</button>
<span *ngIf="showErrorLabel"
class="center-block label label-danger" style="font-size: medium; padding: 2px; margin-top: 2px">
Login fehlgeschlagen
</span>
</div>
</div>
</form>

View File

@ -0,0 +1,50 @@
import {Component, OnInit} from "@angular/core";
import {ActivatedRoute, Router} from "@angular/router";
import {LoginService} from "../services/login-service/login-service";
@Component({
moduleId: module.id,
templateUrl: './signup.component.html',
styleUrls: ['./login.component.css']
})
export class SignupComponent implements OnInit {
showErrorLabel = false;
loading = false;
returnUrl: string;
constructor(private route: ActivatedRoute,
private router: Router,
private loginService: LoginService) {
}
ngOnInit() {
// reset login status
this.loginService.logout();
// redirect on success
this.returnUrl = '/cc-overview'
}
login(username: string, password: string, secret: string) {
if (username.length > 0 && password.length > 0 && secret.length > 0) {
this.loading = true;
this.loginService.signUp(username, password, secret)
.subscribe(
data => {
console.log(data)
//this.router.navigate([this.returnUrl]);
},
error => {
this.showErrorLabel = true;
setTimeout(() => {
this.showErrorLabel = false;
}, 4000);
this.loading = false;
});
}
}
}

View File

@ -1,3 +1,12 @@
export interface AppUser {
_id?: string;
username?: string;
squad?: Squad;
secret?: string;
activated: boolean;
permission: number;
}
export interface User { export interface User {
_id?: string; _id?: string;
boardUserId?: number; boardUserId?: number;
@ -68,59 +77,3 @@ export interface Army {
}, },
} }
export interface Tag {
label: string;
}
export interface Assignee {
name?: string;
email?: string;
}
export interface Task {
id?: number;
title?: string;
description?: string;
tags?: Tag[];
favorite?: boolean;
state?: string;
assignee?: Assignee;
}
export const states = ['BACKLOG', 'IN_PROGRESS', 'TEST', 'COMPLETED'];
export function createInitialTask(): Task {
return {
assignee: {},
tags: [],
state: states[0]
};
}
export const stateGroups = [
{
label: 'Planung',
states: ['BACKLOG']
},
{
label: 'Entwicklung',
states: ['IN_PROGRESS', 'TEST']
},
{
label: 'In Produktion',
states: ['COMPLETED']
}
];
export const stateTexts = {
'BACKLOG': 'Backlog',
'IN_PROGRESS': 'In Bearbeitung',
'TEST': 'Im Test',
'COMPLETED': 'Abgeschlossen'
};
export const statesAsObjects = [{name: 'BACKLOG', text: 'Backlog'},
{name: 'IN_PROGRESS', text: 'In Bearbeitung'},
{name: 'TEST', text: 'Test'},
{name: 'COMPLETED', text: 'Abgeschlossen'}];

View File

@ -0,0 +1,48 @@
import {Injectable} from "@angular/core";
import {AppUser, User} from "../../models/model-interfaces";
import {URLSearchParams} from "@angular/http";
import {Observable} from "rxjs/Observable";
import {ADD, EDIT, LOAD, REMOVE} from "../stores/user.store";
import {AppConfig} from "../../app.config";
import {HttpClient} from "../http-client";
import {AppUserStore} from "../stores/app-user.store";
@Injectable()
export class AppUserService {
users$: Observable<AppUser[]>;
constructor(private http: HttpClient,
private appUserStore: AppUserStore,
private config: AppConfig) {
this.users$ = appUserStore.items$;
}
getUsers() {
this.http.get(this.config.apiUrl + this.config.apiAppUserPath)
.map(res => res.json())
.do((users) => {
this.appUserStore.dispatch({type: LOAD, data: users});
}).subscribe(_ => {
});
return this.users$;
}
updateUser(user: AppUser) {
return this.http.patch(this.config.apiUrl + this.config.apiAppUserPath + user._id, user)
.map(res => res.json())
.do(savedUser => {
const action = {type: EDIT, data: savedUser};
this.appUserStore.dispatch(action);
});
}
deleteUser(user) {
return this.http.delete(this.config.apiUrl + this.config.apiAppUserPath + user._id)
.do(res => {
this.appUserStore.dispatch({type: REMOVE, data: user});
});
}
}

View File

@ -24,6 +24,17 @@ export class LoginService {
}); });
} }
signUp(username: string, password: string, secret: string) {
return this.http.post(this.config.apiUrl + this.config.apiSignupPath, {username: username, password: password, secret: secret})
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user) {
//TODO
}
});
}
logout() { logout() {
// remove user from local storage // remove user from local storage
localStorage.removeItem('currentUser'); localStorage.removeItem('currentUser');
@ -33,4 +44,9 @@ export class LoginService {
return !this.authEnabled || localStorage.getItem('currentUser') != null; return !this.authEnabled || localStorage.getItem('currentUser') != null;
} }
hasPermission(level : number) {
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
return this.isLoggedIn() && currentUser.permission >= level;
}
} }

View File

@ -0,0 +1,41 @@
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import {AppUser, User} from "../../models/model-interfaces";
export const LOAD = 'LOAD';
export const ADD = 'ADD';
export const EDIT = 'EDIT';
export const REMOVE = 'REMOVE';
export class AppUserStore {
private appUsers: AppUser[] = [];
items$ = new BehaviorSubject<AppUser[]>([]);
dispatch(action) {
this.appUsers = this._reduce(this.appUsers, action);
this.items$.next(this.appUsers);
}
_reduce(users: AppUser[], action) {
switch (action.type) {
case LOAD:
return [...action.data];
case ADD:
return [...users, action.data];
case EDIT:
return users.map(user => {
const editedUser = action.data;
if (user._id !== editedUser._id) {
return user;
}
return editedUser;
});
case REMOVE:
return users.filter(user => user._id !== action.data._id);
default:
return users;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB