@underpostnet/underpost
Version:
Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc
101 lines (85 loc) • 3.48 kB
JavaScript
import mongoose from 'mongoose';
import { UserDto } from './user.model.js';
import { ValkeyAPI } from '../../server/valkey.js';
import { hashPassword, getBearerToken, jwtSign } from '../../server/auth.js';
// ─── TTL ──────────────────────────────────────────────────────────────────────
const _guestTtlMs = () => {
const minutes = Number.parseInt(process.env.REFRESH_EXPIRE_MINUTES || '60', 10);
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : 60 * 60 * 1000;
};
// ─── Domain helpers ───────────────────────────────────────────────────────────
/**
* Constructs a new ephemeral guest user object.
* This is domain logic specific to the guest lifecycle; it does not belong
* in the generic Valkey storage module.
*
* @param {{ host?: string }} options
* @returns {object}
*/
const buildGuestUser = (options) => {
const now = new Date().toISOString();
const objectId = new mongoose.Types.ObjectId().toString();
const role = 'guest';
const username = `${role}${objectId.slice(-5)}`;
return {
_id: `${role}${objectId}`,
username,
email: `${username}@${options.host || 'localhost'}`,
password: hashPassword(process.env.JWT_SECRET),
role,
emailConfirmed: false,
profileImageId: null,
publicKey: [],
phoneNumbers: [],
activeSessions: [],
failedLoginAttempts: 0,
recoverTimeOut: null,
lastLoginDate: null,
createdAt: now,
updatedAt: now,
guestSessionExpiresAt: Date.now() + _guestTtlMs(),
};
};
/**
* Projects a user object to the public-safe fields defined by UserDto.select.get().
* Keeps this logic next to the guest domain instead of in a generic utility.
*
* @param {object} user
* @returns {object}
*/
const _toPublicUser = (user) => {
const select = UserDto.select.get();
return Object.fromEntries(
Object.keys(select)
.filter((k) => select[k] === 1 && k in user)
.map((k) => [k, user[k]]),
);
};
const _withRefreshedExpiry = (user) => ({ ...user, guestSessionExpiresAt: Date.now() + _guestTtlMs() });
// ─── Service ──────────────────────────────────────────────────────────────────
class GuestService {
static async create(req, options) {
const user = buildGuestUser(options);
await ValkeyAPI.set(options, user.email, user, _guestTtlMs());
return {
token: jwtSign(
UserDto.auth.payload(user, null, req.ip, req.headers['user-agent'], options.host, options.path),
options,
),
user: _toPublicUser(user),
};
}
static async auth(req, options) {
const user = await ValkeyAPI.get(options, req.auth.user.email);
if (!user) throw new Error('guest user expired');
const expiresAt = Number(user.guestSessionExpiresAt || 0);
if (expiresAt && expiresAt <= Date.now()) throw new Error('guest user expired');
const refreshed = _withRefreshedExpiry(user);
await ValkeyAPI.set(options, refreshed.email, refreshed, _guestTtlMs());
return {
user: _toPublicUser(refreshed),
token: getBearerToken(req),
};
}
}
export { GuestService };