Unverified Commit 92cd910a authored by Almouhannad Hafez's avatar Almouhannad Hafez Committed by GitHub

Merge pull request #12 from Almouhannad/F_Add-authentication-service

F add authentication service
parents 9bec7b49 92c4497b
using Domain.Entities.Identity.Users;
using Domain.Entities.People.Shared;
namespace Application.Abstractions.JWT;
public interface IJWTProvider
{
string Generate(User user);
string Generate(User user, PersonalInfo? personalInfo = null);
}
......@@ -33,37 +33,38 @@ public class LoginCommandHandler : CommandHandlerBase<LoginCommand, LoginRespons
return Result.Failure<LoginResponse>(IdentityErrors.PasswordMismatch);
#endregion
#region 2. Generate JWT
User user = loginResult.Value!;
string token = _jwtProvider.Generate(user);
#endregion
#region 3. Generate Response
#region 2. Generate Response
#region 3.1. Admin
#region 2.1. Admin
if (user.Role == Roles.Admin)
{
return LoginResponse.GetResponse(user, token);
var token = _jwtProvider.Generate(user);
return LoginResponse.GetResponse(token);
}
#endregion
#region 3.2. Doctor
#region 2.2. Doctor
if (user.Role == Roles.Doctor)
{
var doctorUserResult = await _userRepository.GetDoctorUserByUserNameFullAsync(user.UserName);
if (doctorUserResult.IsFailure)
return Result.Failure<LoginResponse>(doctorUserResult.Error);
return LoginResponse.GetResponse(doctorUserResult.Value.User, token, doctorUserResult.Value.Doctor.PersonalInfo);
var token = _jwtProvider.Generate(user, doctorUserResult.Value.Doctor.PersonalInfo);
return LoginResponse.GetResponse(token);
}
#endregion
#region 3.3. Receptionist user
#region 2.3. Receptionist user
if (user.Role == Roles.Receptionist)
{
var receptionistUser = await _userRepository.GetReceptionistUserByUserNameFullAsync(user.UserName);
if (receptionistUser.IsFailure)
return Result.Failure<LoginResponse>(receptionistUser.Error);
return LoginResponse.GetResponse(receptionistUser.Value.User, token, receptionistUser.Value.PersonalInfo);
var token = _jwtProvider.Generate(user, receptionistUser.Value.PersonalInfo);
return LoginResponse.GetResponse(token);
}
#endregion
......
......@@ -8,27 +8,16 @@ namespace Application.Users.Commands.Login;
public class LoginResponse
{
public int Id { get; set; }
public string UserName { get; set; } = null!;
public string JWT { get; set; } = null!;
public string FullName { get; set; } = null!;
public static Result<LoginResponse> GetResponse(User user, string jwt, PersonalInfo? personalInfo = null)
public static Result<LoginResponse> GetResponse(string jwt)
{
if (jwt is null)
return Result.Failure< LoginResponse>(IdentityErrors.NotFound);
var response = new LoginResponse
{
Id = user.Id,
UserName = user.UserName,
JWT = jwt
};
if (personalInfo is null)
{
if (user.Role != Roles.Admin)
return Result.Failure<LoginResponse>(IdentityErrors.NotFound);
response.FullName = Roles.AdminName;
return response;
}
response.FullName = personalInfo.FullName;
return response;
}
}
using Application.Abstractions.JWT;
using Domain.Entities.Identity.Users;
using Domain.Entities.People.Shared;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
......@@ -17,15 +18,20 @@ public sealed class JWTProvider : IJWTProvider
_options = options.Value;
}
public string Generate(User user)
public string Generate(User user, PersonalInfo? personalInfo = null)
{
var claims = new Claim[]
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.Role, user.Role.Name)
new("id", user.Id.ToString()),
new("userName", user.UserName),
new("role", user.Role.Name)
};
if (personalInfo is not null)
{
claims.Add(new Claim("fullName", personalInfo.FullName));
}
var signingCredentials = new SigningCredentials(
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_options.SecretKey)),
......@@ -34,7 +40,7 @@ public sealed class JWTProvider : IJWTProvider
var token = new JwtSecurityToken(
_options.Issuer,
_options.Audience,
claims,
claims.ToArray(),
null,
DateTime.UtcNow.AddDays(30),
signingCredentials);
......
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { RoleGuard } from './services/authentication/guards/role-guard';
import { Roles } from './classes/Authentication/roles';
import { ForbiddenComponent } from './components/errors/forbidden/forbidden.component';
import { NotFoundComponent } from './components/errors/not-found/not-found.component';
const routes: Routes = [
{ path: '', component: HomeComponent }
];
{
path: '',
redirectTo: 'home',
pathMatch: 'full',
},
{
path: 'home',
component: HomeComponent,
canActivate: [RoleGuard],
data: { role: Roles.NotRegistered }
},
{
path: 'errors/forbidden',
component: ForbiddenComponent,
canActivate: [RoleGuard],
data: { role: Roles.NotRegistered }
},
// Everything else
{
path: '**',
component: NotFoundComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
......
......@@ -13,6 +13,12 @@ import { FooterComponent } from './components/template/footer/footer.component';
import { HomeComponent } from './components/home/home.component';
import { LoginFormComponent } from './components/Authentication/login-form/login-form.component';
import { FormsModule } from '@angular/forms';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { AuthenticationService } from './services/authentication/authentication.service';
import { AuthenticationInterceptor } from './services/authentication/interceptor/authentication.interceptor';
import { ForbiddenComponent } from './components/errors/forbidden/forbidden.component';
import { NotFoundComponent } from './components/errors/not-found/not-found.component';
@NgModule({
imports: [
......@@ -22,12 +28,16 @@ import { FormsModule } from '@angular/forms';
NgbModule,
ToastrModule.forRoot(),
FormsModule,
HttpClientModule,
],
// creators of services that this module contributes to the
// global collection of services; they become accessible in
// all parts of the app
providers: [],
providers: [
AuthenticationService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true},
],
// components and directives that belong to this module
// the subset of declarations that should be visible and usable in
......@@ -38,7 +48,9 @@ import { FormsModule } from '@angular/forms';
HeaderComponent,
FooterComponent,
HomeComponent,
LoginFormComponent
LoginFormComponent,
ForbiddenComponent,
NotFoundComponent
],
// identifies the root component that Angular should
......
export class LoginCommand {
export class LoginCommand
{
public userName!: string;
public password!: string;
}
export class LoginResponse
{
public jwt!: string;
}
export class LoginResult {
constructor(status: boolean, errorMessage?: string)
{
this.status = status;
this.errorMessage = errorMessage;
}
public status: Boolean;
public errorMessage?: string;
}
\ No newline at end of file
export class Roles {
public static readonly Admin: string = "admin";
public static readonly Doctor: string = "doctor";
public static readonly Receptionist: string = "receptionist";
public static readonly NotRegistered: string = "notRegistered";
}
\ No newline at end of file
export class UserData {
public id: number;
public userName!: string;
public role!: string;
public fullName?: string;
constructor(id: number, userName: string, role: string, fullName?: string){
this.id = id;
this.userName = userName;
this.role = role;
this.fullName = fullName;
}
}
\ No newline at end of file
<div class="custom-child" dir="rtl">
<div>
<div class="text-center" >
<div class="text-center">
<h3 style="font-weight: 800;">تسجيل الدخول</h3>
</div>
<hr>
<div *ngIf="isFailure" class="d-grid mb-2">
<button type="button" class="btn btn-danger" style="cursor: auto;">خطأ: {{errorMessage}}</button>
</div>
<form #loginForm="ngForm" (ngSubmit)="onSubmit()" class="text-center" autocomplete="off">
<!-- To avoid first field auto focus -->
......@@ -15,36 +19,34 @@
</div>
<div class="form-group mb-4">
<label for="username" class="col-form-label custom-label mb-2">اسم المستخدم <span class="text-danger">*</span></label>
<input type="text" class="form-control text-center custom-input"
placeholder="ادخل اسم المستخدم" dir="ltr"
[(ngModel)]="formModel.userName" name="userName"
#userName="ngModel" required maxlength="50"
>
<label for="username" class="col-form-label custom-label mb-2">اسم المستخدم <span
class="text-danger">*</span></label>
<input type="text" class="form-control text-center custom-input" placeholder="ادخل اسم المستخدم"
dir="ltr" [(ngModel)]="formModel.userName" name="userName" #userName="ngModel" required
maxlength="50">
<div *ngIf="(userName.touched || userName.dirty) && userName.errors" class="mt-2">
<span class="text-danger">
{{
userName.errors['required'] ? 'هذا الحقل مطلوب'
: ''
userName.errors['required'] ? 'هذا الحقل مطلوب'
: ''
}}
</span>
</div>
</div>
<div class="form-group mb-4">
<label for="password" class="col-form-label custom-label mb-2">كلمة المرور <span class="text-danger">*</span></label>
<input type="password" class="form-control text-center custom-input mb-2"
placeholder="ادخل كلمة المرور" dir="ltr"
[(ngModel)]="formModel.password" name="password"
#password="ngModel" required maxlength="50"
>
<label for="password" class="col-form-label custom-label mb-2">كلمة المرور <span
class="text-danger">*</span></label>
<input type="password" class="form-control text-center custom-input mb-2" placeholder="ادخل كلمة المرور"
dir="ltr" [(ngModel)]="formModel.password" name="password" #password="ngModel" required
maxlength="50">
<div *ngIf="(password.touched || password.dirty) && password.errors" class="mt-2">
<span class="text-danger">
{{
password.errors['required'] ? 'هذا الحقل مطلوب'
: ''
password.errors['required'] ? 'هذا الحقل مطلوب'
: ''
}}
</span>
</div>
......@@ -52,12 +54,11 @@
</div>
<div class="d-grid gap-3">
<button
type="submit" class="btn btn-outline-primary"
[disabled]="!loginForm.dirty || loginForm.invalid">دخول</button>
<button type="submit" class="btn btn-outline-primary"
[disabled]="!loginForm.dirty || loginForm.invalid">دخول</button>
<button type="button" class="btn btn-outline-secondary custom-cancel-button"
(click)="parentModal.dismiss()">الغاء</button>
(click)="parentModal.dismiss()">الغاء</button>
</div>
</form>
</div>
......
import { Component, Input, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { LoginCommand } from '../../../classes/Authentication/login-command';
import { LoginCommand } from '../../../classes/Authentication/Login/login-command';
import { AuthenticationService } from '../../../services/authentication/authentication.service';
import { UserData } from '../../../classes/Authentication/user-data';
@Component({
selector: 'app-login-form',
......@@ -9,18 +11,48 @@ import { LoginCommand } from '../../../classes/Authentication/login-command';
})
export class LoginFormComponent {
//#region CTOR DI
constructor(private authenticationService: AuthenticationService) {}
//#endregion
//#region Inputs
@Input("parentModal") parentModal : any;
//#endregion
//#region Outputs
@Output("loggedIn") loggedIn: EventEmitter<UserData> = new EventEmitter();
//#endregion
//#region Variables
@ViewChild("loginForm") loginForm: NgForm;
formModel: LoginCommand = new LoginCommand();
isFailure: boolean = false;
errorMessage: string = '';
//#endregion
//#region On submit
onSubmit(): void {}
onSubmit(): void
{
if (this.loginForm.valid){
this.isFailure = false;
this.errorMessage = '';
this.authenticationService.login(this.formModel)
.subscribe(
result => {
if (result.status === false){
this.isFailure = true;
this.errorMessage = result.errorMessage!;
}
else{
this.loggedIn.emit(this.authenticationService.getUserData()!);
}
}
);
this.loginForm.form.markAsPristine();
}
}
//#endregion
}
<div class="cutom-child text-center" style="padding: 6em;">
<i class="bi bi-exclamation-triangle-fill text-danger" style="font-size: 4em;"></i>
<h1 class="text-danger text-center" style="font-family: Cairo;">خطأ: لا يمكنك الوصول إلى الصفحة التي طلبتها</h1>
</div>
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'app-forbidden',
templateUrl: './forbidden.component.html',
styleUrl: './forbidden.component.css'
})
export class ForbiddenComponent {
}
<div class="cutom-child text-center" style="padding: 6em;">
<i class="bi bi-exclamation-triangle-fill text-danger" style="font-size: 4em;"></i>
<h1 class="text-danger text-center" style="font-family: Cairo;">خطأ: الصفحة المطلوبة غير موجودة</h1>
</div>
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'app-not-found',
templateUrl: './not-found.component.html',
styleUrl: './not-found.component.css'
})
export class NotFoundComponent {
}
......@@ -15,21 +15,7 @@
<div class="content row gy-4">
<div class="col-lg-4 d-flex align-items-stretch">
<div class="why-box">
<h3 class="text-center">تسجيل الدخول</h3>
<p class="text-center text-light">
للاستفادة من خدمات هذا التطبيق، يحب عليك تسجيل الدخول أولاً
</p>
<div class="text-center">
<button (click)="openLoginForm(loginModal)" class="btn more-btn">
<a>تسجيل الدخول<i class="bi bi-chevron-right"></i></a>
</button>
</div>
</div>
</div>
<div class="col-lg-8 d-flex align-items-stretch">
<div class="col-lg-11 d-flex align-items-stretch">
<div class="d-flex flex-column justify-content-center">
<div class="row gy-4">
......@@ -37,7 +23,7 @@
<div class="col-xl-4 d-flex align-items-stretch">
<div class="icon-box">
<i class="bi bi-clipboard-data"></i>
<h4>التاريخ المرضي</h4>
<h4>استعراض التاريخ المرضي</h4>
<p class="text-center" style="font-weight: 500; color:var(--heading-color);">يوفر
التطبيق امكانية استعراض التاريخ المرضي لموظفي المركز وأفراد عائلتهم
</p>
......@@ -47,9 +33,9 @@
<div class="col-xl-4 d-flex align-items-stretch">
<div class="icon-box">
<i class="bi bi-graph-up"></i>
<h4>احصائيات</h4>
<h4>استخلاص الاحصائيات الطبيّة</h4>
<p class="text-center" style="font-weight: 500; color:var(--heading-color);">من
مزايا هذا التطبيق امكانية عرض احصائيات طبيّة لموظفي المركز
مزايا هذا التطبيق امكانية عرض احصائيات طبيّة عن موظفي المركز
</p>
</div>
</div>
......@@ -57,12 +43,11 @@
<div class="col-xl-4 d-flex align-items-stretch">
<div class="icon-box">
<i class="bi bi-clock-history"></i>
<h4>دور الانتظار</h4>
<h4>معالجة قائمة الانتظار</h4>
<p style="font-weight: 500; color:var(--heading-color);">يتيح هذا التطبيق إمكانية
تنظيم أدوار انتظار المرضى </p>
</div>
</div>
</div>
</div>
</div>
......@@ -199,12 +184,4 @@
</section>
<!-- #endregion -->
<!-- #region login pop-up -->
<ng-template #loginModal let-modal>
<div class="modal-body">
<app-login-form [parentModal]="modal"></app-login-form>
</div>
</ng-template>
<!-- #endregion -->
</div>
\ No newline at end of file
import { Component } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrl: './home.component.css'
})
export class HomeComponent {
export class HomeComponent{
//#region CTOR DI
constructor (private modalService: NgbModal) {}
//#endregion
//#region Login
openLoginForm (content: any): void {
this.modalService.open(content);
}
//#endregion
}
......@@ -5,21 +5,46 @@
color: var(--heading-color);
}
.custom-buttons .btn-outline-primary{
font-weight: 650;
.custom-buttons .btn-outline-primary {
font-weight: 700;
}
.custom-buttons .btn-outline-secondary {
color: var(--heading-color);
font-weight: 650;
font-weight: 700;
border: none;
}
.custom-buttons .btn-outline-secondary:hover {
box-shadow: inset 0 0 0 2px gray ;
box-shadow: inset 0 0 0 2px gray;
background-color: inherit;
}
.navmenu .dropdown ul li a {
cursor: pointer;
font-weight: 700;
}
.navmenu .dropdown ul li a {
display: block;
text-align: center;
}
.navmenu .dropdown ul li:not(:last-child) {
border-bottom: 1px solid var(--heading-color);
}
.custom-selected-dropdown {
box-shadow: inset 0 0 0 2px gray;
}
.app-name-container {
text-align: center; /* or any other alignment you prefer */
}
/* #endregion */
/* #region Header*/
......@@ -219,7 +244,7 @@
.navmenu .dropdown ul {
margin: 0;
padding: 10px 0;
background: var(--nav-dropdown-background-color);
background: var(--background-color);
display: block;
position: absolute;
visibility: hidden;
......@@ -253,22 +278,15 @@
color: var(--nav-dropdown-hover-color);
}
.navmenu .dropdown:hover>ul {
opacity: 1;
.navmenu .dropdown ul {
opacity: 0;
top: 100%;
visibility: visible;
}
.navmenu .dropdown .dropdown ul {
top: 0;
left: -90%;
visibility: hidden;
}
.navmenu .dropdown .dropdown:hover>ul {
.navmenu .dropdown ul.show {
opacity: 1;
top: 0;
left: -100%;
top: 100%;
visibility: visible;
}
}
......
<div class="custom-child">
<div class="header">
<div class="branding d-flex align-items-center">
......@@ -7,24 +8,119 @@
<img src="assets/images/logo.png" alt="Logo" width="100em" height="100em">
</div>
<h1 class="h3 m-auto custom-app-name">المركز الطبي - برنامج الأطباء</h1>
<div class="container app-name-container m-auto">
<h1 class="h3 custom-app-name mb-4">المركز الطبي - برنامج الأطباء</h1>
<h3 dir="rtl" *ngIf="userData!==null" class="h5 text-primary">أهلاَ <span *ngIf="userData.role===DOCTOR">د.</span> {{userData.fullName}}!</h3>
</div>
<!-- #region unregistered buttons -->
<nav class="navmenu custom-buttons" *ngIf="userData === null">
<nav class="navmenu custom-buttons">
<ul>
<li><a href="#about"><button class="btn"
<li><a><button class="btn" [class]="{'btn-outline-secondary': true}"
(click)="openLoginForm(loginModal)">تسجيل الدخول</button></a></li>
<li><a href="home#about"><button class="btn"
[class]="{'btn-outline-primary': isSelected('About'), 'btn-outline-secondary': !isSelected('About')}"
(click)="selectButton('About')">عن المستوصف</button></a></li>
<li><a href="#doctors"><button class="btn"
<li><a href="home#doctors"><button class="btn"
[class]="{'btn-outline-primary': isSelected('Doctors'), 'btn-outline-secondary': !isSelected('Doctors')}"
(click)="selectButton('Doctors')">الفريق الطبي</button></a></li>
<li><a href="#"><button class="btn"
<li><a href="home#"><button class="btn"
[class]="{'btn-outline-primary': isSelected('Home'), 'btn-outline-secondary': !isSelected('Home')}"
(click)="selectButton('Home')">الصفحة الرئيسية</button></a></li>
</ul>
</nav>
<!-- #endregion -->
<!-- #region admin buttons -->
<nav class="navmenu custom-buttons" *ngIf="userData?.role === ADMIN">
<ul>
<li class="dropdown">
<button
[ngClass]="{'btn': true, 'btn-outline-secondary': true, 'custom-selected-dropdown': showDropdown}"
(click)="showDropdown = !showDropdown">خيارات <i
class="bi bi-chevron-down toggle-dropdown"></i></button>
<ul [ngClass]="{'show': showDropdown}">
<li (click)="onLogout();"><a>تسجيل الخروج</a></li>
</ul>
</li>
<li><a><button class="btn"
[class]="{'btn-outline-primary': isSelected('Admin-Receptionists'), 'btn-outline-secondary': !isSelected('Admin-Receptionists')}"
(click)="selectButton('Admin-Receptionists')">موظفو الاستقبال</button></a></li>
<li><a><button class="btn"
[class]="{'btn-outline-primary': isSelected('Admin-Dorctors'), 'btn-outline-secondary': !isSelected('Admin-Dorctors')}"
(click)="selectButton('Admin-Dorctors')">الأطباء</button></a></li>
<li><a [routerLink]="'home'"><button class="btn"
[class]="{'btn-outline-primary': isSelected('Home'), 'btn-outline-secondary': !isSelected('Home')}"
(click)="selectButton('Home')">الصفحة الرئيسية</button></a></li>
</ul>
</nav>
<!-- #endregion -->
<!-- #region doctor buttons -->
<nav class="navmenu custom-buttons" *ngIf="userData?.role === DOCTOR">
<ul>
<li class="dropdown">
<button
[ngClass]="{'btn': true, 'btn-outline-secondary': true, 'custom-selected-dropdown': showDropdown}"
(click)="showDropdown = !showDropdown">خيارات <i
class="bi bi-chevron-down toggle-dropdown"></i></button>
<ul [ngClass]="{'show': showDropdown}">
<li (click)="onLogout();"><a>تسجيل الخروج</a></li>
</ul>
</li>
<li><a [routerLink]="'home'"><button class="btn"
[class]="{'btn-outline-primary': isSelected('Home'), 'btn-outline-secondary': !isSelected('Home')}"
(click)="selectButton('Home')">الصفحة الرئيسية</button></a></li>
</ul>
</nav>
<!-- #endregion -->
<!-- #region receptionist buttons -->
<nav class="navmenu custom-buttons" *ngIf="userData?.role === RECEPTIONIST">
<ul>
<li class="dropdown">
<button
[ngClass]="{'btn': true, 'btn-outline-secondary': true, 'custom-selected-dropdown': showDropdown}"
(click)="showDropdown = !showDropdown">خيارات <i
class="bi bi-chevron-down toggle-dropdown"></i></button>
<ul [ngClass]="{'show': showDropdown}">
<li (click)="onLogout();"><a>تسجيل الخروج</a></li>
</ul>
</li>
<li><a [routerLink]="'home'"><button class="btn"
[class]="{'btn-outline-primary': isSelected('Home'), 'btn-outline-secondary': !isSelected('Home')}"
(click)="selectButton('Home')">الصفحة الرئيسية</button></a></li>
</ul>
</nav>
<!-- #endregion -->
</div>
</div>
</div>
<!-- #region login pop-up -->
<ng-template #loginModal let-modal>
<div *ngIf="userData===null" class="modal-body">
<app-login-form (loggedIn)="onLogin($event); modal.dismiss();" [parentModal]="modal"></app-login-form>
</div>
</ng-template>
<!-- #endregion -->
</div>
\ No newline at end of file
import { Component } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { UserData } from '../../../classes/Authentication/user-data';
import { AuthenticationService } from '../../../services/authentication/authentication.service';
import { Router } from '@angular/router';
import { Roles } from '../../../classes/Authentication/roles';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-header',
......@@ -7,6 +12,57 @@ import { Component } from '@angular/core';
})
export class HeaderComponent {
//#region CTOR DI
constructor(private authenticationService: AuthenticationService,
private router: Router,
private modalService: NgbModal) { }
//#endregion
//#region Inputs
@Input("userData") userData: UserData | null = null;
//#endregion
//#region Variables
//#region Roles
readonly ADMIN: string = Roles.Admin;
readonly DOCTOR: string = Roles.Doctor;
readonly RECEPTIONIST: string = Roles.Receptionist;
//#endregion
//#region Dropdown
showDropdown: boolean = false;
//#endregion
//#endregion
//#region Login
//#region Login form pop-up
openLoginForm(content: any): void {
this.modalService.open(content);
}
//#endregion
//#region Login event
onLogin(userData: UserData)
{
this.userData = userData;
}
//#endregion
//#endregion
//#region On logout
onLogout(): void {
this.authenticationService.logout();
this.userData = null;
this.showDropdown = false;
this.router.navigate(['']);
}
//#endregion
//#region Selected button
private selectedButton: string = 'Home';
......
/* #region Custom */
.custom-parent {
display: flex;
flex-direction:column;
min-height:100vh;
}
app-footer {
margin-top: auto;
}
/* #endregion */
/* #region General */
::ng-deep .custom-child{
color: var(--default-color);
......
<app-header class="sticky-top"></app-header>
<div class="custom-parent">
<app-header class="sticky-top" [userData]="userData"></app-header>
<router-outlet></router-outlet>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>
<app-footer></app-footer>
\ No newline at end of file
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { UserData } from '../../../classes/Authentication/user-data';
import { AuthenticationService } from '../../../services/authentication/authentication.service';
@Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
styleUrl: './layout.component.css'
})
export class LayoutComponent {
export class LayoutComponent implements OnInit {
//#region CTOR DI
constructor(private authenticationService: AuthenticationService){}
//#endregion
//#region On init
ngOnInit(): void {
this.userData = this.authenticationService.getUserData();
}
//#endregion
//#region Variables
userData: UserData | null = null;
//#endregion
}
import { Injectable } from '@angular/core';
import { AppModule } from '../../app.module';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpResponse } from '@angular/common/http';
import * as config from "../../../../config";
import { LoginCommand } from '../../classes/Authentication/Login/login-command';
import { LoginResponse } from '../../classes/Authentication/Login/login-response';
import { catchError, map, Observable, of } from 'rxjs';
import { LoginResult } from '../../classes/Authentication/Login/login-result';
import { JWTHandler } from './jwtHandler';
import { UserData } from '../../classes/Authentication/user-data';
import { Roles } from '../../classes/Authentication/roles';
@Injectable({
providedIn: AppModule
})
export class AuthenticationService {
//#region CTOR DI
constructor(private http: HttpClient) { }
//#endregion
//#region Constants
private readonly USERS_ENDPOINT: string = `${config.apiUrl}/Users`
//#endregion
//#region Login
private postLogin(loginCommand: LoginCommand): Observable<LoginResponse> {
return this.http.post<LoginResponse>(
this.USERS_ENDPOINT, loginCommand);
}
login(loginCommand: LoginCommand): Observable<LoginResult> {
return this.postLogin(loginCommand).pipe(
map((loginResponse: LoginResponse) => {
JWTHandler.storeJWT(loginResponse.jwt);
return new LoginResult(true, '');
}),
catchError((error: HttpErrorResponse) => {
return of(new LoginResult(false, error.error.detail));
})
);
}
//#endregion
//#region Logout
logout(): void {
JWTHandler.deleteJwtCookie();
}
//#endregion
//#region Get user data
getUserData(): UserData | null{
let jwt = JWTHandler.getJwtFromCookie();
if (jwt)
{
let jwtJson = JWTHandler.decodeJWT(jwt);
let id = jwtJson.id;
let userName = jwtJson.userName;
let role = jwtJson.role;
let fullName = jwtJson.fullName;
if (fullName)
return new UserData(id, userName, role, fullName);
if (role == Roles.Admin)
return new UserData(id, userName, role, Roles.Admin);
return null;
}
return null;
}
//#endregion
}
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthenticationService } from '../authentication.service';
import { Roles } from '../../../classes/Authentication/roles';
import { UserData } from '../../../classes/Authentication/user-data';
@Injectable({
providedIn: 'root'
})
export class RoleGuard implements CanActivate {
constructor(private authenticationService: AuthenticationService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const userData: UserData | null = this.authenticationService.getUserData();
const requiredRole: string = route.data['role'];
// No required role
if (!requiredRole)
return true;
// No required role
if (requiredRole === Roles.NotRegistered)
return true;
// Reauired role, but not registered user
if (!userData)
{
this.router.navigate(["errors/forbidden"]);
return false;
}
if (userData.role !== requiredRole)
{
this.router.navigate(["errors/forbidden"]);
return false;
}
return true;
}
}
\ No newline at end of file
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
import { JWTHandler } from '../jwtHandler';
import { Observable } from 'rxjs';
@Injectable()
export class AuthenticationInterceptor implements HttpInterceptor {
//#region HTTP headers
private readonly HTTP_HEADERS: HttpHeaders = this.getHeaders();
getHeaders(): HttpHeaders {
return new HttpHeaders().set('Content-Type', 'application/json');
}
//#endregion
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const jwt = JWTHandler.getJwtFromCookie();
// console.log(req);
req = req.clone({
headers: this.HTTP_HEADERS
});
if (jwt !== null) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${jwt}`
}
});
}
// console.log(req);
return next.handle(req);
}
}
\ No newline at end of file
export class JWTHandler {
//#region JWT Decoder
public static decodeJWT(jwt: string) {
const payload = jwt.split('.')[1];
const decodedPayload = atob(payload);
const textDecoder = new TextDecoder('utf-8');
const claimsJson = JSON.parse(
textDecoder.decode(
new Uint8Array(
decodedPayload.split('').map(c => c.charCodeAt(0))
)
)
);
return claimsJson;
}
//#endregion
//#region Sotre JWT in a cookie
public static storeJWT(jwt: string): void {
const claims = this.decodeJWT(jwt);
const expiresAt = claims.exp * 1000; // convert seconds to milliseconds
const cookieOptions = {
expires: new Date(expiresAt),
path: '/'
};
let cookieString = `jwt=${jwt};`;
if (cookieOptions.expires) {
cookieString += `expires=${cookieOptions.expires.toUTCString()};`;
}
if (cookieOptions.path) {
cookieString += `path=${cookieOptions.path};`;
}
document.cookie = cookieString;
}
//#endregion
//#region Get JWT from cookie
public static getJwtFromCookie(): string | null {
const cookieString = document.cookie;
const cookiePairs = cookieString.split(';');
for (const cookie of cookiePairs) {
const [key, value] = cookie.trim().split('=');
if (key === 'jwt') {
return value
}
}
return null;
}
//#endregion
//#region Delete JWT cookie
public static deleteJwtCookie(): void {
document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 GMT;';
}
//#endregion
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment