@fdm-monster/server
Version:
FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.
105 lines (104 loc) • 4.79 kB
JavaScript
import { AuthenticationError } from "../../exceptions/runtime.exceptions.js";
import { AUTH_ERROR_REASON } from "../../constants/authorization.constants.js";
import { comparePasswordHash } from "../../utils/crypto.utils.js";
import { captureException } from "@sentry/node";
//#region src/services/authentication/auth.service.ts
var AuthService = class AuthService {
logger;
/**
* When users are blacklisted at runtime, this cache can make quick work of rejecting them
*/
blacklistedJwtCache = {};
constructor(loggerFactory, userService, jwtService, settingsStore, refreshTokenService) {
this.userService = userService;
this.jwtService = jwtService;
this.settingsStore = settingsStore;
this.refreshTokenService = refreshTokenService;
this.logger = loggerFactory(AuthService.name);
}
async loginUser(username, password) {
const userDoc = await this.userService.findRawByUsername(username);
if (!userDoc) throw new AuthenticationError("Login incorrect", AUTH_ERROR_REASON.IncorrectCredentials);
if (!comparePasswordHash(password, userDoc.passwordHash)) throw new AuthenticationError("Login incorrect", AUTH_ERROR_REASON.IncorrectCredentials);
const userId = userDoc.id;
const token = await this.signJwtToken(userId);
await this.refreshTokenService.purgeOutdatedRefreshTokensByUserId(userId);
await this.purgeOutdatedBlacklistedJwtCache();
return {
token,
refreshToken: await this.refreshTokenService.createRefreshTokenForUserId(userId)
};
}
async logoutUserId(userId, jwtToken) {
await this.refreshTokenService.deleteRefreshTokenByUserId(userId);
if (jwtToken?.length) {
this.blacklistedJwtCache[jwtToken] = {
userId,
createdAt: Date.now()
};
await this.purgeOutdatedBlacklistedJwtCache();
}
}
async purgeOutdatedBlacklistedJwtCache() {
try {
const { jwtExpiresIn } = await this.settingsStore.getCredentialSettings();
const now = Date.now();
const keys = Object.keys(this.blacklistedJwtCache);
for (const key of keys) {
const { createdAt } = this.blacklistedJwtCache[key];
if (now - createdAt > jwtExpiresIn) delete this.blacklistedJwtCache[key];
}
} catch (err) {
this.logger.error("Failed to purge blacklisted jwt cache", err);
captureException(err);
}
}
async logoutUserRefreshToken(refreshToken) {
const userRefreshToken = await this.getValidRefreshToken(refreshToken);
await this.refreshTokenService.deleteRefreshTokenByUserId(userRefreshToken.userId);
}
async renewLoginByRefreshToken(refreshToken) {
const userRefreshToken = await this.getValidRefreshToken(refreshToken);
const userId = userRefreshToken.userId;
if (!await this.userService.getUser(userId)) {
await this.refreshTokenService.deleteRefreshToken(refreshToken);
throw new AuthenticationError("User not found", AUTH_ERROR_REASON.InvalidOrExpiredRefreshToken);
}
const token = await this.signJwtToken(userId);
await this.increaseRefreshTokenAttemptsUsed(userRefreshToken.refreshToken);
return token;
}
isJwtTokenBlacklisted(jwtToken) {
return this.blacklistedJwtCache[jwtToken];
}
async getValidRefreshToken(refreshToken) {
const userRefreshToken = await this.refreshTokenService.getRefreshToken(refreshToken);
if (Date.now() > userRefreshToken.expiresAt) {
await this.refreshTokenService.deleteRefreshTokenByUserId(userRefreshToken.userId);
throw new AuthenticationError("Refresh token expired, login required", AUTH_ERROR_REASON.InvalidOrExpiredRefreshToken);
}
return userRefreshToken;
}
async increaseRefreshTokenAttemptsUsed(refreshToken) {
const { refreshTokenAttempts } = await this.settingsStore.getCredentialSettings();
const userRefreshToken = await this.getValidRefreshToken(refreshToken);
const attemptsUsed = userRefreshToken.refreshAttemptsUsed;
if (refreshTokenAttempts !== -1) {
if (attemptsUsed >= refreshTokenAttempts) {
await this.refreshTokenService.deleteRefreshTokenByUserId(userRefreshToken.userId);
throw new AuthenticationError("Refresh token attempts exceeded, login required", AUTH_ERROR_REASON.InvalidOrExpiredRefreshToken);
}
}
await this.refreshTokenService.updateRefreshTokenAttempts(refreshToken, attemptsUsed + 1);
}
async signJwtToken(userId) {
const user = await this.userService.getUser(userId);
if (!user) throw new AuthenticationError("User not found", AUTH_ERROR_REASON.InvalidOrExpiredRefreshToken);
if (user.needsPasswordChange) throw new AuthenticationError("Password change required", AUTH_ERROR_REASON.PasswordChangeRequired);
if (!user.isVerified) throw new AuthenticationError("User is not verified yet", AUTH_ERROR_REASON.AccountNotVerified);
return this.jwtService.signJwtToken(userId, user.username);
}
};
//#endregion
export { AuthService };
//# sourceMappingURL=auth.service.js.map