Browse Source

Added simple auth

pull/2/head
Per-Arne 5 years ago
parent
commit
b725c585b0
  1. 23
      wg_dashboard_backend/db/user.py
  2. 143
      wg_dashboard_backend/main.py
  3. 21
      wg_dashboard_backend/schemas.py
  4. 21
      wg_dashboard_frontend/src/app/app-routing.module.ts
  5. 10
      wg_dashboard_frontend/src/app/app.component.ts
  6. 11
      wg_dashboard_frontend/src/app/app.module.ts
  7. 11
      wg_dashboard_frontend/src/app/interfaces/user.ts
  8. 22
      wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.html
  9. 17
      wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.ts
  10. 1
      wg_dashboard_frontend/src/app/pages/pages/login/index.ts
  11. 85
      wg_dashboard_frontend/src/app/pages/pages/login/login.component.html
  12. 4
      wg_dashboard_frontend/src/app/pages/pages/pages.module.ts
  13. 48
      wg_dashboard_frontend/src/app/pages/user/edit/edit.component.html
  14. 0
      wg_dashboard_frontend/src/app/pages/user/edit/edit.component.scss
  15. 51
      wg_dashboard_frontend/src/app/pages/user/edit/edit.component.ts
  16. 40
      wg_dashboard_frontend/src/app/pages/user/login/login.component.html
  17. 0
      wg_dashboard_frontend/src/app/pages/user/login/login.component.scss
  18. 25
      wg_dashboard_frontend/src/app/pages/user/login/login.component.spec.ts
  19. 24
      wg_dashboard_frontend/src/app/pages/user/login/login.component.ts
  20. 23
      wg_dashboard_frontend/src/app/pages/user/user.module.ts
  21. 2
      wg_dashboard_frontend/src/app/services/auth/auth.guard.ts
  22. 23
      wg_dashboard_frontend/src/app/services/auth/auth.interceptor.ts
  23. 82
      wg_dashboard_frontend/src/app/services/auth/auth.service.ts
  24. 72
      wg_dashboard_frontend/src/app/services/auth/fakebackend.interceptor.ts
  25. 1
      wg_dashboard_frontend/src/app/services/auth/index.ts
  26. 3
      wg_dashboard_frontend/src/theme/components/sidebar/menu-link-item.component.ts

23
wg_dashboard_backend/db/user.py

@ -1,7 +1,11 @@
from typing import Optional
from sqlalchemy.orm import Session
import models
from passlib.context import CryptContext
import schemas
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@ -13,13 +17,22 @@ def get_password_hash(password):
return pwd_context.hash(password)
def authenticate_user(sess, username: str, password: str):
def update_user(sess: Session, form_data: schemas.UserInDB):
user = get_user_by_name(sess, form_data.username)
user.password = form_data.password
user.full_name = form_data.full_name
user.email = form_data.email # TOD this section should be updated
sess.add(user)
sess.commit()
return get_user_by_name(sess, form_data.username)
def authenticate_user(sess, username: str, password: str) -> Optional[models.User]:
user = get_user_by_name(sess, username)
if not user:
return False
if not verify_password(password, user.password):
return False
if user and verify_password(password, user.password):
return user
return None
def get_user_by_name(db: Session, username: str) -> models.User:

143
wg_dashboard_backend/main.py

@ -1,6 +1,7 @@
import logging
import os
from sqlalchemy.exc import OperationalError
from sqlalchemy_utils import database_exists
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
@ -21,7 +22,7 @@ from datetime import datetime, timedelta
import db.wireguard
import db.user
import jwt
from fastapi import Depends, FastAPI, HTTPException, status, Form
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt import PyJWTError
import script.wireguard
@ -35,16 +36,24 @@ engine = sqlalchemy.create_engine(
const.DATABASE_URL, connect_args={"check_same_thread": False}
)
try:
models.Base.metadata.create_all(engine)
except OperationalError as e:
pass
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
app = FastAPI()
if not database_exists(engine.url):
models.Base.metadata.create_all(engine)
# Create default user
_db: Session = SessionLocal()
_db.add(models.User(
username=os.getenv("ADMIN_USERNAME", "admin"),
password=db.user.get_password_hash(os.getenv("ADMIN_PASSWORD", "admin")),
full_name="Admin",
role="admin",
email=""
))
_db.commit()
_db.close()
app = FastAPI()
# Dependency
def get_db():
@ -66,28 +75,45 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
return encoded_jwt
def get_current_user(token: str = Depends(oauth2_scheme), sess: Session = Depends(get_db)):
def auth(token: str = Depends(oauth2_scheme), sess: Session = Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, const.SECRET_KEY, algorithms=[const.ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = schemas.TokenData(username=username)
except PyJWTError:
raise credentials_exception
user = db.user.get_user_by_name(sess, token_data.username)
user = db.user.get_user_by_name(sess, username)
if user is None:
raise credentials_exception
return user
@app.post("/api/token", response_model=schemas.Token)
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), sess: Session = Depends(get_db)):
@app.get("/api/logout")
def logout(user: schemas.User = Depends(auth)):
# TODO
return {}
@app.post("/api/user/edit", response_model=schemas.User)
def edit(form_data: schemas.UserInDB, user: schemas.User = Depends(auth), sess: Session = Depends(get_db)):
form_data.password = db.user.get_password_hash(form_data.password)
db_user = db.user.update_user(sess, form_data)
return schemas.User.from_orm(db_user)
@app.post("/api/login", response_model=schemas.Token)
def login(form_data: OAuth2PasswordRequestForm = Depends(), sess: Session = Depends(get_db)):
user = db.user.authenticate_user(sess, form_data.username, form_data.password)
if not user:
@ -96,17 +122,23 @@ def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), ses
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Create token
access_token_expires = timedelta(minutes=const.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
return {"access_token": access_token, "token_type": "bearer", "user": schemas.User.from_orm(user)}
# @app.post("/wg/update/", response_model=List[schemas.WireGuard])
@app.get("/api/wg/server/all", response_model=typing.List[schemas.WGServer])
def get_interfaces(sess: Session = Depends(get_db)):
def get_interfaces(
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
interfaces = db.wireguard.server_get_all(sess)
for iface in interfaces:
iface.is_running = script.wireguard.is_running(iface)
@ -115,7 +147,11 @@ def get_interfaces(sess: Session = Depends(get_db)):
@app.post("/api/wg/server/add", response_model=schemas.WGServer)
def add_interface(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
def add_interface(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
if form_data.interface is None or form_data.listen_port is None or form_data.address is None:
raise HTTPException(status_code=400,
detail="Interface, Listen-Port and Address must be included in the schema.")
@ -136,7 +172,10 @@ def add_interface(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/edit", response_model=schemas.WGServer)
def edit_server(data: dict, sess: Session = Depends(get_db)):
def edit_server(
data: dict, sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
interface = data["interface"]
server = schemas.WGServer(**data["server"])
@ -156,7 +195,7 @@ def edit_server(data: dict, sess: Session = Depends(get_db)):
@app.get("/api/wg/generate_keypair", response_model=schemas.KeyPair)
def generate_key_pair():
def generate_key_pair(user: schemas.User = Depends(auth)):
private_key, public_key = script.wireguard.generate_keys()
return schemas.KeyPair(
private_key=private_key,
@ -165,21 +204,28 @@ def generate_key_pair():
@app.get("/api/wg/generate_psk", response_model=schemas.PSK)
def generate_psk():
def generate_psk(user: schemas.User = Depends(auth)):
return schemas.PSK(
psk=script.wireguard.generate_psk()
)
@app.post("/api/wg/server/stop", response_model=schemas.WGServer)
def start_server(form_data: schemas.WGServer, ):
def start_server(
form_data: schemas.WGServer,
user: schemas.User = Depends(auth)
):
script.wireguard.stop_interface(form_data)
form_data.is_running = script.wireguard.is_running(form_data)
return form_data
@app.post("/api/wg/server/start", response_model=schemas.WGServer)
def start_server(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
def start_server(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
db.wireguard.server_generate_config(sess, form_data)
script.wireguard.start_interface(form_data)
form_data.is_running = script.wireguard.is_running(form_data)
@ -187,7 +233,11 @@ def start_server(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/restart", response_model=schemas.WGServer)
def start_server(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
def start_server(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
db.wireguard.server_generate_config(sess, form_data)
script.wireguard.restart_interface(form_data)
form_data.is_running = script.wireguard.is_running(form_data)
@ -195,7 +245,11 @@ def start_server(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/delete", response_model=schemas.WGServer)
def delete_server(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
def delete_server(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
# Stop if running
if script.wireguard.is_running(form_data):
script.wireguard.stop_interface(form_data)
@ -206,7 +260,11 @@ def delete_server(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/peer/add", response_model=schemas.WGPeer)
def add_peer(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
def add_peer(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
wg_peer = schemas.WGPeer(server=form_data.interface)
# Insert initial peer
@ -222,7 +280,11 @@ def add_peer(form_data: schemas.WGServer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/peer/delete", response_model=schemas.WGPeer)
def delete_peer(form_data: schemas.WGPeer, sess: Session = Depends(get_db)):
def delete_peer(
form_data: schemas.WGPeer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
if not db.wireguard.peer_remove(sess, form_data):
raise HTTPException(400, detail="Were not able to delete peer %s (%s)" % (form_data.name, form_data.public_key))
@ -234,7 +296,11 @@ def delete_peer(form_data: schemas.WGPeer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/peer/edit", response_model=schemas.WGPeer)
def edit_peer(form_data: schemas.WGPeer, sess: Session = Depends(get_db)):
def edit_peer(
form_data: schemas.WGPeer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
wg_peer = db.wireguard.peer_update(sess, form_data)
db.wireguard.peer_generate_config(sess, wg_peer)
@ -242,13 +308,20 @@ def edit_peer(form_data: schemas.WGPeer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/stats")
def edit_peer(form_data: schemas.WGServer):
def edit_peer(
form_data: schemas.WGServer,
user: schemas.User = Depends(auth)
):
stats = script.wireguard.get_stats(form_data)
return JSONResponse(content=stats)
@app.post("/api/wg/server/peer/config", response_model=schemas.WGPeerConfig)
def config_peer(form_data: schemas.WGPeer, sess: Session = Depends(get_db)):
def config_peer(
form_data: schemas.WGPeer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
db_peer = db.wireguard.peer_query_get_by_address(sess, form_data.address, form_data.server).one()
with open(const.PEER_FILE(db_peer), "r") as f:
@ -258,7 +331,10 @@ def config_peer(form_data: schemas.WGPeer, sess: Session = Depends(get_db)):
@app.post("/api/wg/server/config", response_model=schemas.WGPeerConfig)
def config_server(form_data: schemas.WGServer):
def config_server(
form_data: schemas.WGServer,
user: schemas.User = Depends(auth)
):
with open(const.SERVER_FILE(form_data.interface), "r") as f:
conf_file = f.read()
@ -266,7 +342,11 @@ def config_server(form_data: schemas.WGServer):
@app.post("/api/users/create/")
def create_user(form_data: schemas.UserInDB, sess: Session = Depends(get_db)):
def create_user(
form_data: schemas.UserInDB,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
user = db.user.get_user_by_name(sess, form_data.username)
# User already exists
@ -293,6 +373,7 @@ def create_user(form_data: schemas.UserInDB, sess: Session = Depends(get_db)):
), sess)
@app.on_event("startup")
async def startup():
await database.connect()

21
wg_dashboard_backend/schemas.py

@ -3,21 +3,15 @@ from pydantic import BaseModel, typing
import models
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str = None
class User(BaseModel):
username: str = None
email: str = None
full_name: str = None
role: str = None
class Config:
orm_mode = True
class UserInDB(User):
password: str
@ -26,6 +20,15 @@ class UserInDB(User):
orm_mode = True
class Token(BaseModel):
access_token: str
token_type: str
user: User
class Config:
orm_mode = True
class WGPeer(BaseModel):
name: str = None
address: str = None

21
wg_dashboard_frontend/src/app/app-routing.module.ts

@ -4,7 +4,11 @@ import { RouterModule } from '@angular/router';
import { LayoutsModule } from './layouts';
import { CommonLayoutComponent } from './layouts/common-layout';
import { DashboardComponent } from './pages/dashboard';
import {LoginComponent} from "./pages/pages/login";
import {AuthGuard} from "@services/*";
import {EditComponent} from "./pages/user/edit/edit.component";
import {LoginComponent} from "./pages/user/login/login.component";
@NgModule({
@ -14,12 +18,21 @@ import {LoginComponent} from "./pages/pages/login";
{ path: '', redirectTo: 'app/dashboard', pathMatch: 'full' },
{ path: 'app', component: CommonLayoutComponent, children:
[
{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full'}, // canActivate: [AuthGuard]
{ path: 'login', component: LoginComponent, pathMatch: 'full'},
{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
{ path: '**', redirectTo: '/pages/404'},
]
},
{ path: 'pages', loadChildren: () => import('./pages/pages/pages.module').then(m => m.PagesModule) },
{ path: 'user', component: CommonLayoutComponent, children:
[
{ path: 'login', component: LoginComponent, pathMatch: 'full'},
{ path: 'edit', component: EditComponent, pathMatch: 'full', canActivate: [AuthGuard]},
]
},
{ path: '**', redirectTo: '/pages/404' },
],
{ useHash: true },

10
wg_dashboard_frontend/src/app/app.component.ts

@ -1,7 +1,15 @@
import { Component } from '@angular/core';
import {AuthService} from "@services/*";
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`,
})
export class AppComponent { }
export class AppComponent {
constructor(private auth: AuthService) {
auth.init()
}
}

11
wg_dashboard_frontend/src/app/app.module.ts

@ -2,7 +2,7 @@ import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AuthInterceptor, AuthService, FakeBackendInterceptor } from '@services/*';
import { AuthInterceptor, AuthService } from '@services/*';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -11,6 +11,7 @@ import { DashboardModule } from './pages/dashboard';
import { VarDirective } from './directives/var.directive';
import { QRCodeModule } from 'angularx-qrcode';
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {UserModule} from "./pages/user/user.module";
@NgModule({
declarations: [AppComponent, VarDirective],
@ -19,6 +20,7 @@ import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
AppRoutingModule,
ComponentsModule,
DashboardModule,
UserModule,
HttpClientModule,
NgbModule,
QRCodeModule
@ -29,12 +31,7 @@ import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: FakeBackendInterceptor,
multi: true,
},
}
],
bootstrap: [AppComponent],
exports: [

11
wg_dashboard_frontend/src/app/interfaces/user.ts

@ -0,0 +1,11 @@
import {Peer} from "./peer";
export interface User {
full_name: string;
email: string;
role: string;
username: string;
access_token: string,
token_type: string,
}

22
wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.html

@ -26,28 +26,28 @@
<div class="avatar-dropdown" id="icon">
<span>Logged in as Admin</span>
<div class="avatar-dropdown" id="icon" *ngIf="auth.user">
<span>Logged in as {{auth.user?.username}}</span>
<!--<img src="assets/images/Icon_header.png">-->
</div>
<!--<ul
<ul
class="mdl-menu mdl-list mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect mdl-shadow--2dp account-dropdown"
for="icon">
<li class="mdl-list__item mdl-list__item--two-line">
<span class="mdl-list__item-primary-content">
<span class="material-icons mdl-list__item-avatar"></span>
<span>{{ user.username }}</span>
<span class="mdl-list__item-sub-title">{{ user.email }}</span>
<span>{{ auth.user?.username }}</span>
<span class="mdl-list__item-sub-title">{{ auth.user?.email }}</span>
</span>
</li>
<li class="list__item--border-top"></li>
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content">
<span class="mdl-list__item-primary-content" (click)="router.navigate(['/user/edit'])">
<i class="material-icons mdl-list__item-icon">account_circle</i>
My account
</span>
</li>
<li class="mdl-menu__item mdl-list__item">
<!--<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">check_box</i>
My tasks
@ -61,21 +61,21 @@
<i class="material-icons mdl-list__item-icon">perm_contact_calendar</i>
My events
</span>
</li>
<li class="list__item--border-top"></li>
</li>-->
<!--<li class="list__item--border-top"></li>
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">settings</i>
Settings
</span>
</li>
</li>-->
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content" (click)="logout()">
<i class="material-icons mdl-list__item-icon text-color--secondary">exit_to_app</i>
Log out
</span>
</li>
</ul>-->
</ul>
</base-page-top>
</div>

17
wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.ts

@ -10,22 +10,19 @@ import { AuthService } from '@services/*';
export class CommonLayoutComponent implements OnInit {
public title = 'Wireguard Manager';
public menu = [
{ name: 'Dashboard', link: 'dashboard', icon: 'dashboard' },
{ name: 'Dashboard', link: '/app/dashboard', icon: 'dashboard' },
];
public user;
constructor(private authService: AuthService,
private router: Router) {}
constructor(public auth: AuthService,
public router: Router) {}
public ngOnInit() {
this.authService.userData.subscribe(user => this.user = user ? user : {
username: 'Luke',
email: 'Luke@skywalker.com',
});
}
public logout() {
this.authService.logout()
.subscribe(res => this.router.navigate(['/pages/login']));
this.auth.logout()
.subscribe(res => this.router.navigate(['/user/login']));
}
}

1
wg_dashboard_frontend/src/app/pages/pages/login/index.ts

@ -1 +0,0 @@
export { LoginComponent } from './login.component';

85
wg_dashboard_frontend/src/app/pages/pages/login/login.component.html

@ -1,85 +0,0 @@
<div class="mdl-card mdl-shadow--2dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">Login to Wireguard Manager</h2>
</div>
<div class="mdl-card__supporting-text">
<form class="login-form" [formGroup]="loginForm" (submit)="login()" autocomplete="off" novalidate>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label full-size"
[class.is-invalid]="email.invalid && (email.dirty || email.touched)"
[class.is-valid]="email.valid && (email.dirty || email.touched)">
<input formControlName="email"
pattern="{{emailPattern}}"
(change)="onInputChange($event)"
class="mdl-textfield__input" type="text" id="email">
<label class="mdl-textfield__label" for="email">Email</label>
<div *ngIf="email.invalid && (email.dirty || email.touched)">
<span *ngIf="email.errors.required" class="mdl-textfield__error">
Email is required. <span class="color-text--orange"> Please, write any valid email.</span>
</span>
<span *ngIf="email.errors.pattern" class="mdl-textfield__error">
Email is invalid.
</span>
</div>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label full-size"
[class.is-invalid]="password.invalid && (password.dirty || password.touched)"
[class.is-valid]="password.valid && (password.dirty || password.touched)"
id="forPass">
<input formControlName="password"
(change)="onInputChange($event)"
class="mdl-textfield__input" type="password" id="password">
<label class="mdl-textfield__label" for="password">Password</label>
<div *ngIf="password.invalid && (password.dirty || password.touched)">
<span *ngIf="password.errors.required" class="mdl-textfield__error">
Password is required. <span class="color-text--orange"> Please, write any password.</span>
</span>
</div>
</div>
<div class="full-size color-text--red" *ngIf="error"> {{ error }}</div>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone submit-cell">
</div>
</form>
</div>
<div class="mdl-card__actions mdl-card--border">
<a class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">
<a routerLink="/pages/forgot-password" class="blank-layout-card-link">Forgot password?</a>
<a routerLink="/pages/sign-up" class="blank-layout-card-link">Don't have account?</a>
<button class="mdl-button mdl-js-button mdl-button--raised color--light-blue"
type="submit" [disabled]="loginForm.invalid">
SIGN IN
</button>
</a>
</div>
<div class="mdl-card__menu">
<button class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<i class="material-icons">share</i>
</button>
</div>
</div>

4
wg_dashboard_frontend/src/app/pages/pages/pages.module.ts

@ -7,10 +7,11 @@ import { ThemeModule } from 'theme';
import { TooltipModule } from '../../../theme/directives/tooltip/tooltip.module';
import { ErrorComponent } from './error';
import { ForgotPasswordComponent } from './forgot-password';
import { LoginComponent } from './login';
import { PagesRoutingModule } from './pages-routing.module';
import { SignUpComponent } from './sign-up';
@NgModule({
imports: [
CommonModule,
@ -22,7 +23,6 @@ import { SignUpComponent } from './sign-up';
],
declarations: [
ErrorComponent,
LoginComponent,
SignUpComponent,
ForgotPasswordComponent,
],

48
wg_dashboard_frontend/src/app/pages/user/edit/edit.component.html

@ -0,0 +1,48 @@
<div class="container">
<base-card>
<base-card-title>
<h2 class="mdl-card__title-text">Edit User</h2>
</base-card-title>
<base-card-body>
<form [formGroup]="editForm" (ngSubmit)="editForm.valid && edit()" class="form">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="full_name" class="mdl-textfield__input" type="text" id="full_name" value=""/>
<label class="mdl-textfield__label" for="full_name">Full Name</label>
</div>
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="username" class="mdl-textfield__input" type="text" id="username" />
<label class="mdl-textfield__label" for="username">Username</label>
</div>
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="password" class="mdl-textfield__input" type="text" id="password"/>
<label class="mdl-textfield__label" for="password">Password</label>
</div>
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="email" class="mdl-textfield__input" type="text" id="email"/>
<label class="mdl-textfield__label" for="email">Email</label>
</div>
</div>
<button [disabled]="!editForm.valid" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect button--colored-light-blue">
Edit User
</button>
</form>
</base-card-body>
</base-card>
</div>

0
wg_dashboard_frontend/src/app/pages/user/edit/edit.component.scss

51
wg_dashboard_frontend/src/app/pages/user/edit/edit.component.ts

@ -0,0 +1,51 @@
import { Component, OnInit } from '@angular/core';
import {FormControl, FormGroup, Validators} from "@angular/forms";
import {AuthService} from "@services/*";
import {Router} from "@angular/router";
@Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.scss']
})
export class EditComponent implements OnInit {
public editForm: FormGroup = new FormGroup({
full_name: new FormControl(''),
password: new FormControl('', Validators.required),
email: new FormControl('', [
Validators.required,
Validators.pattern('^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$'),
Validators.maxLength(20),
]),
username: new FormControl('', [Validators.required, Validators.maxLength(20)]),
});
public user: any;
public error: string;
constructor(private authService: AuthService,
private router: Router) {
}
public ngOnInit() {
this.user = this.authService.user;
this.editForm.setValue({
full_name: this.user.full_name,
password: "",
email: this.user.email,
username: this.user.username
})
}
public edit() {
if (this.editForm.valid) {
this.authService.edit(this.editForm.getRawValue())
.subscribe(res => this.router.navigate(['/app/dashboard']),
error => this.error = error.message);
}
}
}

40
wg_dashboard_frontend/src/app/pages/user/login/login.component.html

@ -0,0 +1,40 @@
<div class="container">
<base-card>
<base-card-title>
<h2 class="mdl-card__title-text">Edit User</h2>
</base-card-title>
<base-card-body>
<form [formGroup]="loginForm" (ngSubmit)="loginForm.valid && login()" class="form">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="username" class="mdl-textfield__input" type="text" id="username" />
<label class="mdl-textfield__label" for="username">Username</label>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="password" class="mdl-textfield__input" type="text" id="password"/>
<label class="mdl-textfield__label" for="password">Password</label>
</div>
</div>
<button [disabled]="!loginForm.valid" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect button--colored-light-blue">
SIGN IN
</button>
</form>
</base-card-body>
</base-card>
</div>

0
wg_dashboard_frontend/src/app/pages/user/login/login.component.scss

25
wg_dashboard_frontend/src/app/pages/user/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<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

24
wg_dashboard_frontend/src/app/pages/pages/login/login.component.ts → wg_dashboard_frontend/src/app/pages/user/login/login.component.ts

@ -1,37 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { BlankLayoutCardComponent } from 'app/components/blank-layout-card';
import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
import {AuthService} from "@services/*";
import {Router} from "@angular/router";
@Component({
selector: 'app-login',
styleUrls: ['../../../components/blank-layout-card/blank-layout-card.component.scss'],
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent extends BlankLayoutCardComponent implements OnInit {
export class LoginComponent implements OnInit {
public loginForm: FormGroup;
public email;
public username;
public password;
public emailPattern = '^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$';
public error: string;
constructor(private authService: AuthService,
private fb: FormBuilder,
private router: Router) {
super();
this.loginForm = this.fb.group({
password: new FormControl('', Validators.required),
email: new FormControl('', [
username: new FormControl('', [
Validators.required,
Validators.pattern(this.emailPattern),
Validators.maxLength(20),
]),
});
this.email = this.loginForm.get('email');
this.username = this.loginForm.get('username');
this.password = this.loginForm.get('password');
}
@ -54,4 +49,5 @@ export class LoginComponent extends BlankLayoutCardComponent implements OnInit {
public onInputChange(event) {
event.target.required = true;
}
}

23
wg_dashboard_frontend/src/app/pages/user/user.module.ts

@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EditComponent } from './edit/edit.component';
import {ReactiveFormsModule} from "@angular/forms";
import {CardModule} from "../../../theme/components/card";
import { LoginComponent } from './login/login.component';
@NgModule({
declarations: [
EditComponent,
LoginComponent
],
imports: [
CommonModule,
ReactiveFormsModule,
CardModule
]
})
export class UserModule {
}

2
wg_dashboard_frontend/src/app/services/auth/auth.guard.ts

@ -16,7 +16,7 @@ export class AuthGuard implements CanActivate {
return true;
}
// Navigate to the login page with extras
this.router.navigate(['/login']);
this.router.navigate(['/user/login']);
return false;
}

23
wg_dashboard_frontend/src/app/services/auth/auth.interceptor.ts

@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/index';
import { Observable } from 'rxjs';
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
@ -9,19 +10,31 @@ import {
} from '@angular/common/http';
import { AuthService } from './auth.service';
import {tap} from "rxjs/operators";
import {Router} from "@angular/router";
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {
}
constructor(private auth: AuthService, private router: Router) {}
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization token for full api requests
if (request.url.includes('api') && this.auth.isLoggedIn) {
request = request.clone({
setHeaders: { Authorization: `Bearer ${this.auth.authToken}` },
setHeaders: { Authorization: `Bearer ${this.auth.user.access_token}`},
});
}
return next.handle(request);
return next.handle(request).pipe( tap(() => {},
(err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status !== 401 && err.status !== 403) {
return;
}
this.auth.clearData();
this.router.navigate(['user/login']);
}
}));
}
}

82
wg_dashboard_frontend/src/app/services/auth/auth.service.ts

@ -4,6 +4,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import {User} from "../../interfaces/user";
const tokenName = 'token';
@ -12,74 +13,65 @@ const tokenName = 'token';
})
export class AuthService {
private isLogged$ = new BehaviorSubject(false);
private url = `${environment.apiBaseUrl}/api/auth`;
private user = { username: 'Luke', email: 'Luke@skywalker.com' }; // some data about user
public user: User = null;
private url = `${environment.apiBaseUrl}/api`;
constructor(private http: HttpClient) {
}
constructor(private http: HttpClient) {}
public get isLoggedIn(): boolean {
return this.isLogged$.value;
return !!this.user?.access_token
}
public login(data): Observable<any> {
return this.http.post(`${this.url}/login`, data)
// Create form
let formData: FormData = new FormData();
formData.append('username', data.username);
formData.append('password', data.password);
return this.http.post(`${this.url}/login`, formData)
.pipe(
map((res: { user: any, token: string }) => {
this.user = res.user;
localStorage.setItem(tokenName, res.token);
// only for example
localStorage.setItem('username', res.user.username);
localStorage.setItem('email', res.user.email);
this.isLogged$.next(true);
return this.user;
map((res: any) => {
this._handleUser(res);
}));
}
public edit(formData: any){
return this.http.post(`${this.url}/user/edit`, formData)
.pipe(map((res: any) => {
this._handleUser(res);
}));
}
_handleUser(res: any){
const user: any = res.user;
user.access_token = res.access_token;
user.token_type = res.token_type;
localStorage.setItem("session", JSON.stringify(user));
this.init();
}
public logout() {
return this.http.get(`${this.url}/logout`)
.pipe(map((data) => {
localStorage.clear();
this.user = null;
this.isLogged$.next(false);
this.clearData();
return of(false);
}));
}
public signup(data) {
return this.http.post(`${this.url}/signup`, data)
.pipe(
map((res: { user: any, token: string }) => {
this.user = res.user;
localStorage.setItem(tokenName, res.token);
// only for example
localStorage.setItem('username', res.user.username);
localStorage.setItem('email', res.user.email);
this.isLogged$.next(true);
return this.user;
}));
public clearData(){
this.user = null;
localStorage.clear();
}
public get authToken(): string {
return localStorage.getItem(tokenName);
}
public get userData(): Observable<any> {
// send current user or load data from backend using token
return this.loadUser();
}
private loadUser(): Observable<any> {
// use request to load user data with token
// it's fake and useing only for example
if (localStorage.getItem('username') && localStorage.getItem('email')) {
this.user = {
username: localStorage.getItem('username'),
email: localStorage.getItem('email'),
};
}
return of(this.user);
public init() {
this.user = JSON.parse(localStorage.getItem('session'));
}
}

72
wg_dashboard_frontend/src/app/services/auth/fakebackend.interceptor.ts

@ -1,72 +0,0 @@
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, dematerialize, materialize, mergeMap } from 'rxjs/operators';
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
private username = 'Luke';
private email = 'Luke@skywalker.com';
constructor() { }
// with real backend you don't need it at all
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return of(null).pipe(mergeMap(() => {
// signup
if (request.url.endsWith('/api/auth/signup') && request.method === 'POST') {
const body = {
token: 'token_' + this.makeID(),
user: {
username: request.body['username'],
email: request.body['email'],
},
};
return of(new HttpResponse({ body, status: 200 }));
}
// login
if (request.url.endsWith('/api/auth/login') && request.method === 'POST') {
const body = {
token: 'token_' + this.makeID(),
user: {
username: this.username,
email: request.body['email'],
},
};
return of(new HttpResponse({ body, status: 200 }));
}
// logout
if (request.url.endsWith('/api/auth/logout') && request.method === 'GET') {
return of(new HttpResponse({ body: { success: true }, status: 200 }));
}
// at default just process the request
return next.handle(request);
}))
.pipe(materialize())
.pipe(delay(500))
.pipe(dematerialize());
}
// generate random token
private makeID(): string {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 25; i = i + 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
}

1
wg_dashboard_frontend/src/app/services/auth/index.ts

@ -1,4 +1,3 @@
export { AuthGuard } from './auth.guard';
export { AuthService } from './auth.service';
export { AuthInterceptor } from './auth.interceptor';
export { FakeBackendInterceptor } from './fakebackend.interceptor';

3
wg_dashboard_frontend/src/theme/components/sidebar/menu-link-item.component.ts

@ -30,7 +30,8 @@ export class MenuLinkItemComponent {
private navigate() {
const layout = (document.querySelector('.mdl-layout') as any).MaterialLayout;
if (layout.drawer_.getAttribute('aria-hidden') !== 'true') {
if (layout.drawer_ && layout.drawer_.getAttribute('aria-hidden') !== 'true') {
layout.toggleDrawer();
}
}

Loading…
Cancel
Save