52 lines
1.8 KiB
TypeScript
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 };
|
|
}
|
|
}
|