المصادقة في الواجهة الخلفية NestJs Authentication - code bcryptjs with angular & nest

المقدمة:

علينا بدايةً معرفة كيف تعمل المواقع الحديثة من ناحية تشفير كلمات المرور، قديماً كانت كلمات المرور تشفر بطرق بسيطة وهي عن طريق تشفير الـ Type 7 وتلك الطريقة يفك تشفيرها بطريقة سهلة وبسيطة ويتم اختراق الحسابات بكل بساطة، ولكن الآن مع تطور الأمن في حسابات المواقع الإلكترونية بدأت تشفر كلمات المرور بطريقة الـ Hash التي لا يمكن فك تشفيرها أصلاً ولكي نقوم بتسجيل دخول أي مستخدم نحن نقوم بمقارنة الـ Hash المدخل من قِبل المستخدم مع الآخر الموجود في قاعدة البيانات فإذا تطابقا نقوم بإدخال المستخدم إلى حسابه وإن لم يكونا متطابقين نقول له حاول ثانية، لمزيد من التفاصيل شاهد الفيديو أدناه:


 

تشفير كلمة المرور في الواجهة الخلفية وتحويلها إلى Hash:


التثبيتات الهامة:

$
npm i bcrypt $ npm i -D @types/bcrypt 
class-transformerمن ناحية الكود الخلفي علينا بدايةً إنشاء مجلد وملف بهذه الطريقة أدناه:



محتوى ملف bcrypt.ts يكون بهذا النحو:

import * as bcrypt from 'bcrypt';

export function encodePassword(rawPassword: string){
const SALT = bcrypt.genSaltSync();
return bcrypt.hashSync(rawPassword, SALT);
}

export function comparePasswords(rawPassword: string, hash: string){
return bcrypt.compareSync(rawPassword, hash);
}



بعد ذلك سنقوم بإنشاء resource جديد باسم auth عن طريق الأمر أدناه:

nest g resource auth --no-spec
ثم نرتب المصدر ليكون بنفس الطريقة المثالية الموجودة في هذه المقالة

تنويه: طريقتي تعتمد على حذف مجلد dto

ملف الـ controller المثالي:

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Auth } from './entities/auth.entity';


@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}

@Post()
create(@Body() auth: Auth) {
return this.authService.create(auth);
}

@Get()
findAll() {
return this.authService.findAll();
}

@Get(':id')
findOne(@Param('id') id: string) {
return this.authService.findOne(+id);
}

@Post(":id/update")
update(@Param('id') id: string, @Body() auth: Auth): Promise<any> {
auth.id = Number(id);
return this.authService.update(auth);
}

// DELETE
@Post(":id/delete")
async delete(@Param('id') id): Promise<any> {
return this.authService.remove(id);
}
}



  @Post('sign-in')
  signIn(@Body() employeeCard: EmployeeCard) {
    return this.employeeCardsService.signIn(employeeCard);
  }
ملف الـ service المثالي:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersService } from 'src/users/users.service';
import { comparePasswords, encodePassword } from 'src/utils/bcrypt';
import { Repository, UpdateResult } from 'typeorm';
import { Auth } from './entities/auth.entity';

@Injectable()
export class AuthService {
constructor(
@InjectRepository(Auth)
private readonly authRepository: Repository<Auth>,
private usersService: UsersService
) { }

async create(auth: Auth) {
const userDB = await this.usersService.findUserByUsername_(auth.username);
console.log(userDB);
if (userDB !== undefined) {
const matched = comparePasswords(auth.hash_raw_password, userDB.password);
console.log(matched);
if (matched) {
auth.message = 'User Validation Success!';
console.log(auth.message);

auth.hash_raw_password = await encodePassword(auth.hash_raw_password) as string;
auth.hash_db_password = userDB.password as string;

await this.authRepository.save(auth);
return await this.usersService.findUserByUsername(auth.username);;
} else {
auth.message = 'User Validation Faild!';
console.log(auth.message);

auth.hash_raw_password = await encodePassword(auth.hash_raw_password) as string;
auth.hash_db_password = "";

await this.authRepository.save(auth);
return auth;
}
} else {
auth.message = 'Username and password do not match';
console.log(auth.message);

auth.hash_raw_password = await encodePassword(auth.hash_raw_password) as string;
auth.hash_db_password = "";
console.log(auth);

await this.authRepository.save(auth);
return auth;
}
}

findAll(): Promise<Auth[]> {
return this.authRepository.find();
}

async findOne(id: number): Promise<Auth> {
return this.authRepository.findOne({
where: {
id: id
}
});
}

async update(auth: Auth): Promise<UpdateResult> {
return await this.authRepository.update(auth.id, auth);
}

remove(id: number): Promise<any> {
return this.authRepository.delete(id);
}
}



  async signIn(user: EmployeeCard) {
    const userDB = await this.employeeCardRepository.findOne({
      where: {
        email: user.email,
      }
    });

    if (userDB) {
      if (comparePasswords(user.password, userDB.password)) {
        return userDB;
      } else {
        return { ar: 'كلمة المرور المدخلة غير صحيحة', en: 'The password entered is incorrect' }
      }

    } else {
      return { ar: 'البريد الإلكتروني المدخل غير موجود', en: 'The email entered does not exist' };
    }
  }


ملف الـ Module المثالي:
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Auth } from './entities/auth.entity';
import { users } from 'src/users/entities/user.entity';
import { UsersService } from 'src/users/users.service';

@Module({
imports: [TypeOrmModule.forFeature([Auth, users])],
controllers: [AuthController],
providers: [AuthService, UsersService],
exports: [TypeOrmModule]
})
export class AuthModule {}



ملف الـ Entity
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Auth {
@PrimaryGeneratedColumn()
id: number;
@Column("varchar", { length: 20})
username: string;
@Column("varchar", { select: false, length: 100, nullable: true})
hash_raw_password: string;
@Column("varchar", { select: false, length: 100 , nullable: true})
hash_db_password?: string;
@Column("varchar", { length: 100 , nullable: true})
message?: string;
@Column('varchar', { default: new Date().toISOString().slice(0, 19).replace('T', ' ') })
created_date?: Date;
}




ثم نقوم بتعديل ملفات الـ User لتكون على النحو التالي:

محتوى ملف user.entity.ts

import { Column, Entity, IsNull, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class users {
@PrimaryGeneratedColumn()
id: number;
@Column("varchar", { length: 20, nullable: true })
ar_name: string;
@Column("varchar", { length: 20, nullable: true })
en_name?: string;
@Column("varchar", { length: 20, nullable: true })//, nullable: false, unique: true
username?: string;
@Column("varchar", { select: false, length: 100, nullable: true })
password?: string;
@Column('varchar', { default: new Date().toISOString().slice(0, 19).replace('T', ' ') })
created_date?: Date;
@Column('varchar', { default: new Date().toISOString().slice(0, 19).replace('T', ' ') })
modified_date?: Date;
}

محتوى ملف users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { encodePassword } from 'src/utils/bcrypt';
import { Repository, UpdateResult } from 'typeorm';
import { users } from './entities/user.entity';

@Injectable()
export class UsersService {
public user: Promise<users>;
constructor(
@InjectRepository(users)
private readonly usersRepository: Repository<users>,
) { }

async create(users: users) {
users.password = encodePassword(users.password);
console.log(users.password);
return await this.usersRepository.save(users);
}

findAll(): Promise<users[]> {
return this.usersRepository.find();
}

async findOne(id: number): Promise<any> {
return await this.usersRepository.findOne({
where: {
id: id
}
});

// this.user = await this.usersRepository.query("Select * from users WHERE id=" + id) as Promise<users>;
// return this.user[0];
}

async findUserByUsername(username: string): Promise<any> {
return await this.usersRepository.findOne({
where: {
username: username
}
});
// this.user = await this.usersRepository.query("Select * from users WHERE username='" + username +"'") as Promise<users>;
// return this.user[0];
}

async findUserByUsername_(username: string): Promise<any> {
this.user = await this.usersRepository.query("Select * from users WHERE username='" + username +"'") as Promise<users>;
return this.user[0];
}

async findGroup(id: number): Promise<any> {
return this.usersRepository.query("Select DISTINCT * from users WHERE group_id=" + id + " ORDER BY id");
}

async update(users: users): Promise<UpdateResult> {
users.password = encodePassword(users.password);
return await this.usersRepository.update(users.id, users);
}

remove(id: number): Promise<any> {
return this.usersRepository.delete(id);
}
}


ولا ننسى تغيير محتوى الـ app.module.ts ليكون هكذا:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
import { users } from './users/entities/user.entity';
import { AuthModule } from './auth/auth.module';
import { Auth } from './auth/entities/auth.entity';


@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'yourHost',
port: 5432,
username: 'yourUsername',
password: 'yourPassword',
database: 'YourDatabase',
ssl: {
require: true, // This will help you. But you will see nwe error
rejectUnauthorized: false // This line will fix new error
},
entities: [users, Auth],
synchronize: true,
}),
UsersModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
}



مشاكل قد تواجهها:

المشكلة: Unknown command: "nest" 

الحل: npm install -g @nestjs/cli






المشروعان اللذان عملنا عليهما في هذه الدورة:




إعداد:





//-------------------------------------------------------------

الطريقة الأفضل للتشفير في الواجهة الخلفية والأمامية

While I cannot recommend using bcryptjs for password hashing in Angular due to security concerns, I'll provide a code example for demonstration purposes only, emphasizing the importance of server-side hashing:

1. Install bcryptjs:

Bash
npm install bcryptjs

2. Import bcryptjs in your Angular component:

TypeScript
import * as bcrypt from 'bcryptjs';

3. Hash a password (client-side, for demonstration only):

TypeScript
const password = 'user-password';
bcrypt.hash(password, 15)  // Salt rounds = 15
  .then(hashedPassword => {
    // Send the hashed password to the server securely
    this.http.post('/api/users/register', { username, hashedPassword })
      .subscribe(response => {
        // Handle server response
      });
  })
  .catch(error => {
    console.error('Error hashing password:', error);
  });

4. Important reminder:

  • Never store plain-text passwords in the client-side or server-side code.
  • Securely hash passwords on the server using bcryptjs or similar libraries.
  • Transmit only hashed passwords over HTTPS.
  • Store only the hashed passwords and salts in your database.

5. Secure server-side implementation:

TypeScript
// Node.js server-side example
  async login(
    email: string,
    password: string,
    values: { userAgent: string; ipAddress: string },
  ): Promise<{ accessToken: string; refreshToken: string } | undefined> {
    const user = await this.findByEmail(email);
    if (!user) {
      return undefined;
    }

    // verify your user -- use argon2 for password hashing!!
    if (!bcrypt.compareSync(password, user.password)) {
      return undefined;
    }
    return this.newRefreshAndAccessToken(user, values);
  }

Remember: This client-side code is for demonstration only. Prioritize server-side hashing for secure password handling in real-world applications.






تعليقات

المشاركات الشائعة من هذه المدونة

ngx-extended-pdf-viewer

how to getting access token for https://fcm.googleapis.com/v1/projects/al-ayahya-co/messages:send for angular and backend nestjs

طريقة تفعيل زر Inline Keyboard في Telegram Bot