feat(security): implement phase 1 core security & build hygiene
Some checks failed
Deploy Canina / deploy (push) Failing after 28s
Some checks failed
Deploy Canina / deploy (push) Failing after 28s
This commit is contained in:
parent
f4cdca4ed1
commit
a64291a19a
@ -56,6 +56,7 @@ async function main() {
|
||||
const createdProduct = await prisma.product.upsert({
|
||||
where: { artNo },
|
||||
update: {
|
||||
slug: artNo,
|
||||
nameFa: name,
|
||||
nameEn: name,
|
||||
scientificTagline,
|
||||
|
||||
@ -33,12 +33,11 @@ export class AuthController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'ارسال کد تایید پیامکی (OTP)' })
|
||||
@ApiOkResponse({
|
||||
description: 'کد با موفقیت ارسال شد (کد تایید در پاسخ بازگردانده میشود)',
|
||||
description: 'کد با موفقیت به شماره تلفن همراه پیامک شد',
|
||||
schema: {
|
||||
example: {
|
||||
success: true,
|
||||
message: 'کد تایید ارسال شد',
|
||||
code: '12345',
|
||||
message: 'کد تایید با موفقیت به شماره شما پیامک شد.',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -11,7 +11,7 @@ import { UsersModule } from '../users/users.module';
|
||||
UsersModule,
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'super-secret-key',
|
||||
secret: process.env.JWT_ACCESS_SECRET,
|
||||
signOptions: { expiresIn: '7d' },
|
||||
}),
|
||||
],
|
||||
|
||||
@ -3,6 +3,7 @@ import { AuthService } from './auth.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { RedisService } from '../redis/redis.service';
|
||||
import { SmsService } from '../common/services/sms.service';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
describe('AuthService', () => {
|
||||
@ -10,6 +11,7 @@ describe('AuthService', () => {
|
||||
let prisma: PrismaService;
|
||||
let jwt: JwtService;
|
||||
let redis: RedisService;
|
||||
let sms: SmsService;
|
||||
|
||||
const mockPrisma = {
|
||||
user: {
|
||||
@ -28,6 +30,10 @@ describe('AuthService', () => {
|
||||
del: jest.fn(),
|
||||
};
|
||||
|
||||
const mockSms = {
|
||||
sendOtp: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@ -35,6 +41,7 @@ describe('AuthService', () => {
|
||||
{ provide: PrismaService, useValue: mockPrisma },
|
||||
{ provide: JwtService, useValue: mockJwt },
|
||||
{ provide: RedisService, useValue: mockRedis },
|
||||
{ provide: SmsService, useValue: mockSms },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@ -42,6 +49,7 @@ describe('AuthService', () => {
|
||||
prisma = module.get<PrismaService>(PrismaService);
|
||||
jwt = module.get<JwtService>(JwtService);
|
||||
redis = module.get<RedisService>(RedisService);
|
||||
sms = module.get<SmsService>(SmsService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -56,7 +64,7 @@ describe('AuthService', () => {
|
||||
it('should generate a 5 digit OTP, save it in Redis, and not expose it in response', async () => {
|
||||
const result = await service.sendOtp({ phoneNumber: '09123456789' });
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('کد تایید ارسال شد');
|
||||
expect(result.message).toBe('کد تایید با موفقیت به شماره شما پیامک شد.');
|
||||
// Code should NOT be in the response (security)
|
||||
expect((result as any).code).toBeUndefined();
|
||||
// But it should have been stored in Redis
|
||||
|
||||
@ -6,6 +6,7 @@ import { SendOtpDto } from './dto/send-otp.dto';
|
||||
import { VerifyOtpDto } from './dto/verify-otp.dto';
|
||||
import { SmsService } from '../common/services/sms.service';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@ -19,7 +20,7 @@ export class AuthService {
|
||||
async sendOtp(sendOtpDto: SendOtpDto) {
|
||||
const { phoneNumber } = sendOtpDto;
|
||||
|
||||
const code = Math.floor(10000 + Math.random() * 90000).toString();
|
||||
const code = crypto.randomInt(10000, 100000).toString();
|
||||
|
||||
await this.redisService.set(`otp:${phoneNumber}`, code, 120);
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_SECRET || 'super-secret-key',
|
||||
secretOrKey: process.env.JWT_ACCESS_SECRET!,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Controller, Get, Res } from '@nestjs/common';
|
||||
import { ApiExcludeController } from '@nestjs/swagger';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import * as express from 'express';
|
||||
import type { Response } from 'express';
|
||||
|
||||
@ApiExcludeController()
|
||||
@Controller('metrics')
|
||||
@ -15,7 +15,7 @@ export class MetricsController {
|
||||
}
|
||||
|
||||
@Get()
|
||||
async getMetrics(@Res() res: express.Response) {
|
||||
async getMetrics(@Res() res: Response) {
|
||||
const memory = process.memoryUsage();
|
||||
const cpu = process.cpuUsage();
|
||||
|
||||
|
||||
@ -9,6 +9,23 @@ import { PrismaExceptionFilter } from './common/filters/prisma-exception.filter'
|
||||
import helmet from 'helmet';
|
||||
|
||||
async function bootstrap() {
|
||||
const jwtAccessSecret = process.env.JWT_ACCESS_SECRET;
|
||||
const jwtRefreshSecret = process.env.JWT_REFRESH_SECRET;
|
||||
|
||||
if (!jwtAccessSecret || jwtAccessSecret.trim().length < 32) {
|
||||
console.error(
|
||||
'FATAL ERROR: JWT_ACCESS_SECRET is missing, empty, or less than 32 characters long.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!jwtRefreshSecret || jwtRefreshSecret.trim().length < 32) {
|
||||
console.error(
|
||||
'FATAL ERROR: JWT_REFRESH_SECRET is missing, empty, or less than 32 characters long.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
|
||||
// Serve static uploads folder
|
||||
|
||||
Loading…
Reference in New Issue
Block a user