manicanldes-backend/src/identity/auth.service.ts

52 lines
1.8 KiB
TypeScript

import { Inject, Injectable } from '@nestjs/common';
import { ENVIRONMENT } from '../config/environment.module';
import type { Environment } from '../config/environment';
import { AppError } from '../common/errors/app-error';
import { AuthStore } from './auth.store';
import { PasswordService } from './password.service';
import { RateLimitService } from './rate-limit.service';
import { issueToken } from './tokens';
import type { LoginInput } from './identity.schemas';
@Injectable()
export class AuthService {
constructor(
private readonly store: AuthStore,
private readonly passwords: PasswordService,
private readonly limits: RateLimitService,
@Inject(ENVIRONMENT) private readonly env: Environment,
) {}
async login(input: LoginInput) {
await this.limits.consume(
`login:${input.organizationId}:${input.email}`,
10,
900,
);
const user = await this.store.findUser(input.organizationId, input.email);
let valid = false;
if (user)
valid = await this.passwords.verify(input.password, user.passwordHash);
else await this.passwords.dummyVerify(input.password);
if (!valid || !user || user.status !== 'ACTIVE') {
if (user) await this.store.failedLogin(user.organizationId, user.id);
const reason = !user
? 'ACCOUNT_UNKNOWN'
: !valid
? 'PASSWORD_MISMATCH'
: 'ACCOUNT_INACTIVE';
throw new AppError('AUTH_INVALID_CREDENTIALS', reason);
}
const { token, tokenHash } = issueToken();
const expiresAt = new Date(
Date.now() + this.env.SESSION_TTL_MINUTES * 60_000,
);
await this.store.createSession(
user.id,
user.passwordHash,
tokenHash,
expiresAt,
);
return { accessToken: token, tokenType: 'Bearer', expiresAt };
}
}