@mrhsp/auth-backend
Version:
Gate Keeper Backend Authentication Package
84 lines (83 loc) • 3.04 kB
JavaScript
import jwt from 'jsonwebtoken';
const { sign, verify } = jwt;
import { compare, hash } from "bcrypt";
import { validatePassword } from "./utils/validate-password.js";
import { InMemoryAdapter } from "./in-memory-adapter.js";
import { InvalidCredentialsError, AccountAlreadyExistsError, AccountNotFoundError, } from "./errors.js";
const DEFAULT_JWT_OPTIONS = {
secret: "",
expiresIn: "1h",
algorithm: "HS256",
};
export class AuthService {
constructor(options) {
// Initialize adapter
this.adapter =
options.adapter === "in-memory"
? new InMemoryAdapter()
: options.adapter;
// Validate and configure token options
if (!options.token?.secret) {
throw new Error("JWT secret is required");
}
const { secret, ...jwtOptions } = options.token;
this.jwtSecret = secret;
this.jwtOptions = { ...DEFAULT_JWT_OPTIONS, ...jwtOptions };
// Configure password policy
this.passwordPolicy = options.passwordPolicy;
}
async register(registrationData) {
// Destructure password from registration data
const { password, ...accountData } = registrationData;
// Check existing account
const existingAccount = await this.adapter.getAccount(accountData.id);
if (existingAccount) {
throw new AccountAlreadyExistsError(accountData.id);
}
// Validate password policy
validatePassword(password, this.passwordPolicy);
// Create account object with proper typing
const account = {
...accountData,
passwordHash: await this.hashPassword(password),
createdAt: new Date(),
updatedAt: new Date(),
};
await this.adapter.addAccount(account);
}
async authenticate(id, password) {
const account = await this.adapter.getAccount(id);
if (!account) {
throw new AccountNotFoundError(id);
}
const isValid = await compare(password, account.passwordHash);
if (!isValid) {
throw new InvalidCredentialsError();
}
return this.generateToken(account.id);
}
async resetPassword(id, newPassword) {
validatePassword(newPassword, this.passwordPolicy);
const user = await this.adapter.getAccount(id);
if (!user)
throw new AccountNotFoundError(id);
const updatedAccount = {
...user,
passwordHash: await this.hashPassword(newPassword),
updatedAt: new Date(),
};
await this.adapter.updateAccount(updatedAccount);
}
verifyToken(token) {
const payload = verify(token, this.jwtSecret, {
algorithms: [this.jwtOptions.algorithm],
});
return payload.sub;
}
async hashPassword(password) {
return hash(password, 10);
}
generateToken(accountId) {
return sign({ sub: accountId }, this.jwtSecret, this.jwtOptions);
}
}