fix: align password policy to 8-72 printable ASCII on register (WealthLedger/backend#60)
Register accepts only printable ASCII (letters, digits, symbols, space), 8-72 characters, so characters equal bytes and the bcrypt 72-byte limit holds without silent truncation. Login only caps the password at 72 bytes so accounts created under the previous policy can still sign in. CredentialsDto is split into RegisterDto and LoginDto; OpenAPI publishes the register pattern and length bounds.
This commit is contained in:
@@ -2,7 +2,9 @@ import { INestApplication } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { PARAMS_PROVIDER_TOKEN } from 'nestjs-pino';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import request from 'supertest';
|
||||
import { App } from 'supertest/types';
|
||||
|
||||
@@ -265,6 +267,45 @@ describe('Authentication (e2e)', () => {
|
||||
expect(await app.get(PrismaService).user.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('applies the ASCII 8..72 password policy on register and keeps login lenient', async () => {
|
||||
const validation = (detail: string) => ({
|
||||
status: 400,
|
||||
code: 'VALIDATION_ERROR',
|
||||
errors: [{ pointer: '#/password', detail }],
|
||||
});
|
||||
for (const [index, password] of ['a'.repeat(8), ' '.repeat(2) + 'b'.repeat(70)].entries()) {
|
||||
const credentials = { email: `user${index}@example.com`, password };
|
||||
await request(app.getHttpServer()).post('/api/v1/auth/register').send(credentials).expect(200);
|
||||
await request(app.getHttpServer()).post('/api/v1/auth/login').send(credentials).expect(200);
|
||||
}
|
||||
for (const password of ['a'.repeat(7), 'a'.repeat(73), 'пароль-пароль', 'passw😀rd', 'pass\tword1']) {
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/v1/auth/register')
|
||||
.send({ email: 'rejected@example.com', password })
|
||||
.expect(400)
|
||||
.expect('Content-Type', /application\/problem\+json/)
|
||||
.expect(({ body }: { body: unknown }) =>
|
||||
expect(body).toEqual(validation('must be 8-72 printable ASCII characters')),
|
||||
);
|
||||
}
|
||||
expect(await app.get(PrismaService).user.count()).toBe(2);
|
||||
|
||||
// An account created under the previous policy (Cyrillic, 12+ chars) must still log in.
|
||||
const legacy = { email: 'legacy@example.com', password: 'пароль-пароль' };
|
||||
const prisma = app.get(PrismaService);
|
||||
await prisma.user.create({
|
||||
data: { id: randomUUID(), email: legacy.email, passwordHash: await bcrypt.hash(legacy.password, 4) },
|
||||
});
|
||||
await request(app.getHttpServer()).post('/api/v1/auth/login').send(legacy).expect(200);
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/v1/auth/login')
|
||||
.send({ email: legacy.email, password: 'a'.repeat(73) })
|
||||
.expect(400)
|
||||
.expect(({ body }: { body: unknown }) =>
|
||||
expect(body).toEqual(validation('must not exceed 72 UTF-8 bytes')),
|
||||
);
|
||||
});
|
||||
|
||||
it('guards T-Bank connection and never returns a token', async () => {
|
||||
const registration = await request(app.getHttpServer())
|
||||
.post('/api/v1/auth/register')
|
||||
|
||||
Reference in New Issue
Block a user