45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { ENVIRONMENT } from '../config/environment.module';
|
|
import type { Environment } from '../config/environment';
|
|
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);
|
|
const valid = user
|
|
? await this.passwords.verify(input.password, user.passwordHash)
|
|
: (await this.passwords.dummyVerify(input.password), false);
|
|
if (!valid || !user || user.status !== 'ACTIVE') {
|
|
if (user) await this.store.failedLogin(user.organizationId, user.id);
|
|
throw new UnauthorizedException('Invalid credentials');
|
|
}
|
|
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 };
|
|
}
|
|
}
|