الواجهة الخلفية
التثبيتات في الواجهة الخلفية:
npm install bcryptjs
npm install passport-jwt
npm i @nestjs/passport@10.0.2*
npm @types/bcrypt@5.0.0
npm install jsonwebtoken
https://g.co/gemini/share/0b4dc960b41f
الخطوة الأولى: إنشاء ملف login.dto.ts داخل مجلد dto
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDto {
@IsEmail()
email: string;
@IsNotEmpty()
password: string;
}
الخطوة الثانية: إنشاء مجلد refresh-token.dto.ts داخل مجلد dto أيضاً
import { IsNotEmpty } from 'class-validator';
class RefreshTokenDto {
@IsNotEmpty()
refreshToken: string;
}
export default RefreshTokenDto;
الخطوة الثالثة: إنشاء ملف refresh-token.entity.ts في مجلد entities
import { sign } from 'jsonwebtoken';
class RefreshToken {
constructor(init?: Partial<RefreshToken>) {
Object.assign(this, init);
}
id: number;
userId: number;
userAgent: string;
ipAddress: string;
sign(): string {
return sign({ ...this }, 'DAA5761515184B7221F531515533C1F23F31FD391515149');
}
}
export default RefreshToken;
الخطوة الرابعة: إنشاء مجلد باسم guards وإنشاء ملف jwt-auth.guard.ts بداخله:
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JsonWebTokenError } from 'jsonwebtoken';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err: any, user: any, info: any, context: any, status: any) {
if (info instanceof JsonWebTokenError) {
throw new UnauthorizedException('Invalid JWT');
}
return super.handleRequest(err, user, info, context, status);
}
}
الخطوة الخامسة: إنشاء مجلد باسم strategies وإنشاء ملف jwt.strategy.ts بداخله:
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: '7A12548484AS4455D67315515E2D5E29',
});
}
validate(payload) {
return {
userId: payload.userId,
};
}
}
الخطوة السادسة: تعديل ملف الـ Module ليكون بهذا النحو:
import { Module } from '@nestjs/common';
import { EmployeeCardService } from './employee-card.service';
import { EmployeeCardController } from './employee-card.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { EmployeeCard } from './entities/employee-card.entity';
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
imports: [
TypeOrmModule.forFeature([EmployeeCard])],
controllers: [EmployeeCardController],
providers: [EmployeeCardService, JwtStrategy],
exports: [EmployeeCardService, EmployeeCardService]
})
export class EmployeeCardModule { }
الخطوة السابعة: إضافة الدوال التالية إلى الخدمة Service.ts
const bcrypt = require('bcryptjs');
private refreshTokens: RefreshToken[] = [];
private async newRefreshAndAccessToken(
user: EmployeeCard,
values: { userAgent: string; ipAddress: string },
): Promise<{ accessToken: string; refreshToken: string }> {
const refreshObject = new RefreshToken({
id:
this.refreshTokens.length === 0
? 0
: this.refreshTokens[this.refreshTokens.length - 1].id + 1,
...values,
userId: user.id,
});
this.refreshTokens.push(refreshObject);
return {
refreshToken: refreshObject.sign(),
accessToken: sign(
{
userId: user.id,
},
'7A12548484AS4455D67315515E2D5E29',
{
expiresIn: '24h',
},
),
};
}
async signIn(user: EmployeeCard,
values: { userAgent: string; ipAddress: string },
): Promise<{ accessToken: string; refreshToken: string } | undefined> {
const userDB = await this.employeeCardRepository.findOne({
where: {
email: user.email.toString(),
}
});
if (!userDB) {
return {accessToken: null, refreshToken: null};
}
// verify your user -- use argon2 for password hashing!!
if (!bcrypt.compareSync(user.password, userDB.password)) {
return undefined;
}
return this.newRefreshAndAccessToken(user, values);
}
async _findOneByEmail(email: string): Promise<any> {
return await this.employeeCardRepository.findOne({
where: {
email: email,
},
relations: ['branch']
, select: {
// قم بعدم تحديد كلمة المرور
id: true,
type_of_card: true,
employee_id: true,
ar_employee_name: true,
en_employee_name: true,
national_id: true,
blood_type: true,
front_card: true,
buffer: true,
language: true,
template: true,
mobile: true,
email: true,
hiring_date: true,
expireDate: true,
completed_tasks: true,
monthly_target: true,
query_user: true,
document_type: true,
education_level: true,
status: true,
gender: true,
marital_status: true,
nationality: true,
phone_no: true,
fax_no: true,
telegram_chat_id: true,
address: true,
insurance_pin: true,
basic_salary: true,
salary_allowances: true,
bank_name: true,
bank_branch: true,
bank_account_number: true,
bank_account_iban: true,
birth_date: true,
query_password: true,
technician: true,
created_at: true,
modified_at: true,
created_by: true,
modified_by: true
}
});
}
async patchPassword(user: EmployeeCard) {
bcrypt.hash(user.password, 10) // Salt rounds = 10
.then(hashedPassword => {
user.password = hashedPassword;
return this.employeeCardRepository.save(user);
});
}
الخطوة الثامنة: قم بإضافة الدوال التالية إلى المراقب Controller.ts
//------------------- jwt -----------------------
@UseGuards(JwtAuthGuard)
@Get('me')
me(@Headers() headers: Headers) {
// `allHeaders` will contain all the request headers
const allHeaders:any = headers;
return this.employeeCardsService._findOneByEmail(allHeaders.email);
}
@Post('login')
signIn(@Req() request?, @Ip() ip?: string, @Body() item?: any) {
return this.employeeCardsService.signIn(item, {
ipAddress: ip,
userAgent: request.headers['user-agent'],
});
}
@Patch('change-password')
changePassword(@Body() employeeCard: EmployeeCard): Promise<any> {
return this.employeeCardsService.patchPassword(employeeCard);
}
الواجهة الأمامية
الخطوة الأولى: قم بإنشاء مجلد اسم helpers وقم بإنشاء المجلدات التالية فيه:
1- auth.guard.ts
2- auth.interceptor.ts
3- basic-auth.interceptor.ts
4- error.interceptor.ts
5- index.ts
6- remove-headers-interceptor.ts
محتويات الملفات كالتالي:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if (localStorage.getItem('user') != null)
return true;
this.router.navigate(['/login']);
return false;
}
}
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse, HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor() { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<unknown>> {
// Get the access token from local storage or any other source
const accessToken: any = localStorage.getItem('user') || '{}';
let a = JSON.parse(accessToken);
// Clone the request and add the authorization header
const authRequest = request.clone({
setHeaders: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `bearer ${a.accessToken}`,
},
});
// Pass the cloned request with the authorization header to the next handler
return next.handle(authRequest);
}
}
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class BasicAuthInterceptor implements HttpInterceptor {
constructor() { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const accessToken: any = localStorage.getItem('user') || '{}';
let a = JSON.parse(accessToken);
// Get the username and password for basic authentication
// Add the Authorization header with the base64 encoded credentials
let authRequest = request.clone({
setHeaders: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `bearer ${a.accessToken}`,
},
});
// Pass the modified request to the next interceptor or to the HTTP handler
return next.handle(authRequest);
}
clearIntercept(request: HttpRequest<any>, next: HttpHandler) {
const authRequest = request.clone({
setHeaders: {
'Content-Type': '',
'Accept': '',
'Authorization': ``,
},
});
// Pass the modified request to the next interceptor or to the HTTP handler
return next.handle(authRequest);
}
}
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { EmployeeCardsService } from '../services/employee-cards.service';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private employeesService:EmployeeCardsService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Client-side error
this.employeesService?.logout();
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
this.employeesService?.logout();
}
console.error(errorMessage);
return throwError(errorMessage);
})
);
}
}
export * from './auth.guard';
export * from './basic-auth.interceptor';
export * from './error.interceptor';
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpRequest, HttpHeaders, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class RemoveHeadersInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
const newReq = req.clone({ headers: new HttpHeaders() });
return next.handle(newReq);
}
}
الخطوة الثانية: تعديل دوال الخدمة للتوافق مع الواجهة الخلفية
constructor(
private http: HttpClient,
private router: Router,
private localStorageService: LocalStorageService,
private snackBarService: SnackBarService,
private activityService: ActivitiesService,
private handleTranslateService: HandleTranslateService,
private translateService: TranslateService
) {
this.userSubject = new BehaviorSubject<any>(JSON.parse(localStorage.getItem('user') || '{}'));
this.user = this.userSubject.asObservable();
}
private userSubject: BehaviorSubject<any>;
public user: Observable<any>;
bearerToken = JSON.parse(localStorage.getItem('user') || '{}');
public get userValue(): any {
if (this.userSubject.value != '{}') {
return this.userSubject.value;
} else {
this.router.navigate(['/login']);
return null;
}
}
//login
public login(item: any): any {
this.http.post(`${this.backEndURL}/${this.table}/login`, item, { withCredentials: false }).subscribe((res: any) => {
if (res.accessToken) {
this.localStorageService.setUser(res);
this.getUserInfo(item).subscribe(res => {
this.localStorageService.setCurrentUserInfo(res);
this.userSubject.next(res);
this.router.navigate(['/']);
this.snackBarService.success(this.translateService.instant('SUCCESS.LOGIN'), '');
});
} else {
this.snackBarService.danger(this.translateService.instant('ERRORS.USER_NOT_FOUND'), '');
}
});
}
//---------------------------- Logout ------------------------------------
logout() {
this.localStorageService.removeUserItem();
this.userSubject.next(null);
this.router.navigate(['/login']);
}
getUserInfo(user: any) {
// Get the access token from local storage or any other source
// Replace 'YOUR_BEARER_TOKEN' with the actual bearer token
let accessToken = this.bearerToken.accessToken;
// Create the headers object
const headers = new HttpHeaders().set('Content-Type', 'application/json');
const options = { headers: headers.set('Authorization', `bearer ${accessToken}`).set('email', user.email), };
return this.http.get(`${this.backEndURL}/${this.table}/me`, options);
}
الخطوة الثالثة: إقفال الصفحات التي تحتاج أذن لتسجل الدخول من خلال ملف app.routing.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { PagesComponent } from './pages/pages.component';
import { BlankComponent } from './pages/blank/blank.component';
import { SearchComponent } from './pages/search/search.component';
import { NotFoundComponent } from './pages/errors/not-found/not-found.component';
import { ErrorComponent } from './pages/errors/error/error.component';
import { AuthGuard } from './helpers';
export const routes: Routes = [
{
path: '', canActivate: [AuthGuard],
component: PagesComponent, children: [
{ path: '', loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.DashboardModule), data: { breadcrumb: 'لوحة التحكم' } },
{ path: 'users', loadChildren: () => import('./pages/users/users.module').then(m => m.UsersModule), data: { breadcrumb: 'Users' } },
{ path: 'dynamic-menu', loadChildren: () => import('./pages/dynamic-menu/dynamic-menu.module').then(m => m.DynamicMenuModule), data: { breadcrumb: 'Dynamic Menu' } },
{ path: 'ui', loadChildren: () => import('./pages/ui/ui.module').then(m => m.UiModule), data: { breadcrumb: 'UI' } },
{ path: 'mailbox', loadChildren: () => import('./pages/mailbox/mailbox.module').then(m => m.MailboxModule), data: { breadcrumb: 'Mailbox' } },
{ path: 'chat', loadChildren: () => import('./pages/chat/chat.module').then(m => m.ChatModule), data: { breadcrumb: 'Chat' } },
{ path: 'form-controls', loadChildren: () => import('./pages/form-controls/form-controls.module').then(m => m.FormControlsModule), data: { breadcrumb: 'Form Controls' } },
{ path: 'tables', loadChildren: () => import('./pages/tables/tables.module').then(m => m.TablesModule), data: { breadcrumb: 'Tables' } },
{ path: 'schedule', loadChildren: () => import('./pages/schedule/schedule.module').then(m => m.ScheduleModule), data: { breadcrumb: 'Schedule' } },
{ path: 'maps', loadChildren: () => import('./pages/maps/maps.module').then(m => m.MapsModule), data: { breadcrumb: 'Maps' } },
{ path: 'charts', loadChildren: () => import('./pages/charts/charts.module').then(m => m.ChartsModule), data: { breadcrumb: 'Charts' } },
{ path: 'drag-drop', loadChildren: () => import('./pages/drag-drop/drag-drop.module').then(m => m.DragDropModule), data: { breadcrumb: 'Drag & Drop' } },
{ path: 'icons', loadChildren: () => import('./pages/icons/icons.module').then(m => m.IconsModule), data: { breadcrumb: 'Material Icons' } },
{ path: 'profile', loadChildren: () => import('./pages/profile/profile.module').then(m => m.ProfileModule), data: { breadcrumb: 'Profile' } },
{ path: 'blank', component: BlankComponent, data: { breadcrumb: 'Blank page' } },
{ path: 'search', component: SearchComponent, data: { breadcrumb: 'Search' } },
{ path: 'search/:name', component: SearchComponent, data: { breadcrumb: 'Search' } }
]
},
{ path: 'landing', loadChildren: () => import('./pages/landing/landing.module').then(m => m.LandingModule) },
{ path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule) },
{ path: 'register', loadChildren: () => import('./pages/register/register.module').then(m => m.RegisterModule) },
{ path: 'error', component: ErrorComponent, data: { breadcrumb: 'Error' } },
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})
],
exports: [
RouterModule
]
})
export class AppRoutingModule { }
الخطوة الرابعة: طريقة الاستدعاء في صفحة الـ Login
constructor(
private http: HttpClient,
private router: Router,
private localStorageService: LocalStorageService,
private snackBarService: SnackBarService,
private activityService: ActivitiesService,
private handleTranslateService: HandleTranslateService,
private translateService: TranslateService
) {
this.userSubject = new BehaviorSubject<any>(JSON.parse(localStorage.getItem('user') || '{}'));
this.user = this.userSubject.asObservable();
this.backEndURL = this.getBackEndUrl();
if (this.translateService.currentLang === 'ar') {
this.handleTranslateService.getArLangFile().subscribe(res => {
this.handleLang = res;
})
} else {
this.handleTranslateService.getEnLangFile().subscribe(res => {
this.handleLang = res;
})
}
this.successMsg = this.handleLang['SUCCESS'];
}
private userSubject: BehaviorSubject<any>;
public user: Observable<any>;
bearerToken = JSON.parse(localStorage.getItem('user') || '{}');
public get userValue(): any {
if (this.userSubject.value != '{}') {
return this.userSubject.value;
} else {
this.router.navigate(['/login']);
return null;
}
}
//login
public login(item: any): any {
this.http.post(`${this.backEndURL}/${this.table}/login`, item, { withCredentials: false }).subscribe((res: any) => {
if (res.accessToken) {
this.localStorageService.setUser(res);
this.bearerToken = JSON.parse(localStorage.getItem('user') || '{}');
setTimeout(() => {
this.getUserInfo(item).subscribe(res => {
this.localStorageService.setCurrentUserInfo(res);
this.userSubject.next(res);
this.router.navigate(['/']);
this.snackBarService.success(this.translateService.instant('SUCCESS.LOGIN'), '');
});
}, 500);
} else {
this.snackBarService.danger(this.translateService.instant('ERRORS.USER_NOT_FOUND'), '');
}
});
}
//---------------------------- Logout ------------------------------------
logout() {
this.localStorageService.removeUserItem();
this.userSubject.next(null);
this.router.navigate(['/login']);
}
getUserInfo(user: any) {
// Get the access token from local storage or any other source
// Replace 'YOUR_BEARER_TOKEN' with the actual bearer token
let accessToken = this.bearerToken.accessToken;
// Create the headers object
const headers = new HttpHeaders().set('Content-Type', 'application/json');
const options = { headers: headers.set('Authorization', `bearer ${accessToken}`).set('email', user.email), };
return this.http.get(`${this.backEndURL}/${this.table}/me`, options);
}
الخطوة الأخيرة تسجيل الخروج
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { EmployeeCardsService } from 'src/app/services/employee-cards.service';
@Component({
selector: 'app-user-menu',
templateUrl: './user-menu.component.html',
styleUrls: ['./user-menu.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class UserMenuComponent implements OnInit {
public userImage = "assets/img/users/user.jpg";
constructor(private employeeCardsService: EmployeeCardsService) { }
ngOnInit() {
}
logOut(){
this.employeeCardsService.logout();
}
}
<button mat-icon-button [matMenuTriggerFor]="userMenu" #userMenuTrigger="matMenuTrigger" >
<mat-icon>account_circle</mat-icon>
</button>
<mat-menu #userMenu="matMenu" [overlapTrigger]="false" class="toolbar-dropdown-menu user-menu">
<!--<span (mouseleave)="userMenuTrigger.closeMenu()">-->
<mat-card fxLayout="row" fxLayoutAlign="space-around center" class="user-info p-0">
<img [src]="userImage" alt="user-image">
<div fxLayout="column" fxLayoutAlign="center center">
<h2>Emilio Verdines</h2>
<h3 class="secondary-text-color">Web developer</h3>
<p class="secondary-text-color">Member since May. 2016</p>
</div>
</mat-card>
<mat-divider></mat-divider>
<a mat-menu-item routerLink="/profile/projects">
<mat-icon>person</mat-icon>
<span>Profile</span>
</a>
<a mat-menu-item routerLink="/profile/user-info">
<mat-icon>edit</mat-icon>
<span>Edit Profile</span>
</a>
<a mat-menu-item routerLink="settings">
<mat-icon>settings</mat-icon>
<span>Settings</span>
</a>
<a mat-menu-item routerLink="/">
<mat-icon>lock</mat-icon>
<span>Lock screen</span>
</a>
<a mat-menu-item routerLink="help">
<mat-icon>help</mat-icon>
<span>Help</span>
</a>
<mat-divider></mat-divider>
<a (click)="logOut()" mat-menu-item routerLink="/login">
<mat-icon>power_settings_new</mat-icon>
<span>Log out</span>
</a>
<!--</span>-->
</mat-menu>
تعليقات
إرسال تعليق