add jwt & crypto service
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"argon2": "^0.44.0",
|
||||
"dayjs": "^1.11.13",
|
||||
@@ -34,6 +35,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"i18next": "^25.3.2",
|
||||
"i18next-fs-backend": "^2.6.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"kysely": "^0.28.3",
|
||||
"nodemailer": "^7.0.5",
|
||||
"passport": "^0.7.0",
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthRegistry } from './auth-registry';
|
||||
import { generateSecureToken } from '../../../lib/crypto';
|
||||
import * as dayjs from 'dayjs';
|
||||
import { isDefined, ResetAuth } from '@my-monorepo/common';
|
||||
import { CryptoService } from '../../../services/core/crypto/crypto.service';
|
||||
|
||||
const RESET_TOKEN_VALIDITY_MINUTES = 15;
|
||||
|
||||
@Injectable()
|
||||
export class AuthCoreService {
|
||||
constructor(private authRegistry: AuthRegistry) {}
|
||||
constructor(
|
||||
private authRegistry: AuthRegistry,
|
||||
private cryptoService: CryptoService,
|
||||
) {}
|
||||
|
||||
async addNewResetToken(
|
||||
userId: string,
|
||||
): Promise<{ token: string; user_id: string }> {
|
||||
const res = await this.authRegistry.addResetToken({
|
||||
token: generateSecureToken(),
|
||||
token: this.cryptoService.generateSecureToken(),
|
||||
userId,
|
||||
validUntil: dayjs().add(RESET_TOKEN_VALIDITY_MINUTES, 'minutes'),
|
||||
});
|
||||
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
import { UsersCoreService } from '../../users/users-core/users-core.service';
|
||||
import { AuthCoreService } from '../auth-core/auth-core.service';
|
||||
import { ResetAuth } from '@my-monorepo/common';
|
||||
import { hashPassword } from '../../../lib/crypto';
|
||||
import { CryptoService } from '../../../services/core/crypto/crypto.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthControllerService {
|
||||
constructor(
|
||||
private userCoreService: UsersCoreService,
|
||||
private authCoreService: AuthCoreService,
|
||||
private cryptoService: CryptoService,
|
||||
) {}
|
||||
|
||||
async verifyCredentials() {
|
||||
@@ -30,7 +31,7 @@ export class AuthControllerService {
|
||||
}
|
||||
await this.userCoreService.setHashPassword(
|
||||
resetData.userId,
|
||||
await hashPassword(resetData.newPassword),
|
||||
await this.cryptoService.hashPassword(resetData.newPassword),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as argon2 from 'argon2';
|
||||
|
||||
export function generateSecureToken(length = 32): string {
|
||||
return crypto.randomBytes(length).toString('hex');
|
||||
}
|
||||
|
||||
export function hashPassword(password: string): Promise<string> {
|
||||
return argon2.hash(password);
|
||||
}
|
||||
|
||||
export function verifyPassword(
|
||||
passwordHash: string,
|
||||
clearTextPassword: string,
|
||||
): Promise<boolean> {
|
||||
return argon2.verify(passwordHash, clearTextPassword);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { LoggerService } from './logger/logger.service';
|
||||
import { MailerService } from './mailer/mailer.service';
|
||||
import { TemplateRendererService } from './template-renderer/template-renderer.service';
|
||||
import { LinkBuilderService } from './link-builder/link-builder.service';
|
||||
import { CryptoService } from './crypto/crypto.service';
|
||||
|
||||
const DECLARATIONS = [
|
||||
DatabaseService,
|
||||
@@ -15,6 +16,7 @@ const DECLARATIONS = [
|
||||
MailerService,
|
||||
TemplateRendererService,
|
||||
LinkBuilderService,
|
||||
CryptoService,
|
||||
];
|
||||
|
||||
@Global()
|
||||
|
||||
18
apps/backend/src/services/core/crypto/crypto.service.spec.ts
Normal file
18
apps/backend/src/services/core/crypto/crypto.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CryptoService } from './crypto.service';
|
||||
|
||||
describe('CryptoService', () => {
|
||||
let service: CryptoService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CryptoService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CryptoService>(CryptoService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
41
apps/backend/src/services/core/crypto/crypto.service.ts
Normal file
41
apps/backend/src/services/core/crypto/crypto.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as crypto from 'crypto';
|
||||
import * as argon2 from 'argon2';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { EnvService } from '../env/env.service';
|
||||
import { isDefined } from '@my-monorepo/common';
|
||||
|
||||
@Injectable()
|
||||
export class CryptoService {
|
||||
constructor(private envService: EnvService) {}
|
||||
|
||||
generateSecureToken(length = 32): string {
|
||||
return crypto.randomBytes(length).toString('hex');
|
||||
}
|
||||
|
||||
hashPassword(password: string): Promise<string> {
|
||||
return argon2.hash(password);
|
||||
}
|
||||
|
||||
verifyPassword(
|
||||
passwordHash: string,
|
||||
clearTextPassword: string,
|
||||
): Promise<boolean> {
|
||||
return argon2.verify(passwordHash, clearTextPassword);
|
||||
}
|
||||
|
||||
createJwt(payload: string | object): string {
|
||||
return jwt.sign(payload, this.envService.config.secrets.authSign);
|
||||
}
|
||||
|
||||
async verifyJwt(token: string): Promise<string | object | undefined> {
|
||||
return await new Promise<string | object | undefined>((resolve) => {
|
||||
jwt.verify(
|
||||
token,
|
||||
this.envService.config.secrets.authSign,
|
||||
(err, decoded) =>
|
||||
resolve(!isDefined(err) && isDefined(decoded) ? decoded : undefined),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ const envParser = z
|
||||
MAIL_USER: z.string().email(),
|
||||
MAIL_PASSWORD: z.string(),
|
||||
APP_FRONTEND_URL: z.string(),
|
||||
SECRET_AUTH_SIGN: z.string(),
|
||||
})
|
||||
.transform((parsed) => ({
|
||||
database: {
|
||||
@@ -32,6 +33,9 @@ const envParser = z
|
||||
app: {
|
||||
frontendUrl: parsed.APP_FRONTEND_URL,
|
||||
},
|
||||
secrets: {
|
||||
authSign: parsed.SECRET_AUTH_SIGN,
|
||||
},
|
||||
}));
|
||||
|
||||
export type EnvConfig = Readonly<z.infer<typeof envParser>>;
|
||||
|
||||
Reference in New Issue
Block a user