UNPKG

nuxt-users

Version:

A comprehensive user management module for Nuxt 3 and Nuxt 4 applications with authentication, authorization, database support, and CLI tools

216 lines (215 loc) 8.4 kB
import { useDb } from "./db.js"; import bcrypt from "bcrypt"; import { validatePassword, getPasswordValidationOptions } from "../../../utils.js"; export const createUser = async (userData, options) => { const db = await useDb(options); const usersTable = options.tables.users; const passwordOptions = getPasswordValidationOptions(options); const passwordValidation = validatePassword(userData.password, passwordOptions); if (!passwordValidation.isValid) { throw new Error(`Password validation failed: ${passwordValidation.errors.join(", ")}`); } const hashedPassword = await bcrypt.hash(userData.password, 10); const role = userData.role || "user"; await db.sql` INSERT INTO {${usersTable}} (email, name, password, role, created_at, updated_at) VALUES (${userData.email}, ${userData.name}, ${hashedPassword}, ${role}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) `; const result = await db.sql`SELECT id, email, name, role, created_at, updated_at FROM {${usersTable}} WHERE email = ${userData.email}`; if (result.rows.length === 0) { throw new Error("Failed to retrieve created user."); } const user = result.rows[0]; return { id: user.id, email: user.email, name: user.name, role: user.role, created_at: user.created_at instanceof Date ? user.created_at.toISOString() : user.created_at, updated_at: user.updated_at instanceof Date ? user.updated_at.toISOString() : user.updated_at }; }; export const findUserByEmail = async (email, options) => { const db = await useDb(options); const usersTable = options.tables.users; const result = await db.sql`SELECT * FROM {${usersTable}} WHERE email = ${email}`; if (result.rows.length === 0) { return null; } const user = result.rows[0]; return { id: user.id, email: user.email, name: user.name, password: user.password, role: user.role, created_at: user.created_at instanceof Date ? user.created_at.toISOString() : user.created_at, updated_at: user.updated_at instanceof Date ? user.updated_at.toISOString() : user.updated_at }; }; export const findUserById = async (id, options, withPass = false) => { const db = await useDb(options); const usersTable = options.tables.users; const result = await db.sql`SELECT * FROM {${usersTable}} WHERE id = ${id}`; if (result.rows.length === 0) { return null; } const user = result.rows[0]; const normalizedUser = { ...user, created_at: typeof user.created_at === "object" ? user.created_at.toISOString() : user.created_at, updated_at: typeof user.updated_at === "object" ? user.updated_at.toISOString() : user.updated_at }; if (withPass === true) { return normalizedUser; } const { password, ...userWithoutPassword } = normalizedUser; return userWithoutPassword; }; export const updateUser = async (id, userData, options) => { const db = await useDb(options); const usersTable = options.tables.users; const allowedFields = ["name", "email", "role"]; const updates = []; const values = []; for (const field of allowedFields) { if (userData[field] !== void 0) { updates.push(`${field} = ?`); values.push(userData[field]); } } if (updates.length === 0) { const currentUser = await findUserById(id, options); if (!currentUser) { throw new Error("User not found."); } return currentUser; } updates.push("updated_at = CURRENT_TIMESTAMP"); values.push(id); const setClause = updates.map((update) => update.replace(" = ?", "")).join(", "); await db.sql`UPDATE {${usersTable}} SET {${setClause}} WHERE id = ${id}`; const updatedUser = await findUserById(id, options); if (!updatedUser) { throw new Error("Failed to retrieve updated user."); } return updatedUser; }; export const deleteUser = async (id, options) => { const db = await useDb(options); const usersTable = options.tables.users; const result = await db.sql`DELETE FROM {${usersTable}} WHERE id = ${id}`; if (result.rows?.length === 0) { throw new Error("User not found or could not be deleted."); } }; export const updateUserPassword = async (email, newPassword, options) => { const db = await useDb(options); const usersTable = options.tables.users; const passwordOptions = getPasswordValidationOptions(options); const passwordValidation = validatePassword(newPassword, passwordOptions); if (!passwordValidation.isValid) { throw new Error(`Password validation failed: ${passwordValidation.errors.join(", ")}`); } const hashedPassword = await bcrypt.hash(newPassword, 10); await db.sql` UPDATE {${usersTable}} SET password = ${hashedPassword}, updated_at = CURRENT_TIMESTAMP WHERE email = ${email} `; }; export const hasAnyUsers = async (options) => { try { const db = await useDb(options); const users = await db.sql`SELECT COUNT(*) as count FROM {${options.tables.users}}`; return users.rows?.[0]?.count > 0; } catch { return false; } }; export const getCurrentUserFromToken = async (token, options, withPass = false) => { const db = await useDb(options); const personalAccessTokensTable = options.tables.personalAccessTokens; const usersTable = options.tables.users; const tokenResult = await db.sql` SELECT tokenable_id, expires_at FROM {${personalAccessTokensTable}} WHERE token = ${token} AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP) `; if (tokenResult.rows.length === 0) { return null; } const userId = tokenResult.rows[0].tokenable_id; await db.sql` UPDATE {${personalAccessTokensTable}} SET last_used_at = CURRENT_TIMESTAMP WHERE token = ${token} `; const userResult = await db.sql` SELECT * FROM {${usersTable}} WHERE id = ${userId} `; if (userResult.rows.length === 0) { return null; } const user = userResult.rows[0]; if (withPass === true) { return user; } const { password: _, ...userWithoutPassword } = user; return userWithoutPassword; }; export const deleteExpiredPersonalAccessTokens = async (options) => { const db = await useDb(options); const personalAccessTokensTable = options.tables.personalAccessTokens; const result = await db.sql` DELETE FROM {${personalAccessTokensTable}} WHERE expires_at IS NOT NULL AND expires_at <= CURRENT_TIMESTAMP `; const deletedCount = result.rows?.length || 0; console.log(`[Nuxt Users] ${deletedCount} expired personal access tokens deleted.`); return deletedCount; }; export const deleteTokensWithoutExpiration = async (options) => { const db = await useDb(options); const personalAccessTokensTable = options.tables.personalAccessTokens; const result = await db.sql` DELETE FROM {${personalAccessTokensTable}} WHERE expires_at IS NULL `; const deletedCount = result.rows?.length || 0; console.log(`[Nuxt Users] ${deletedCount} tokens without expiration deleted.`); return deletedCount; }; export const cleanupPersonalAccessTokens = async (options, includeNoExpiration = true) => { const expiredCount = await deleteExpiredPersonalAccessTokens(options); const noExpirationCount = includeNoExpiration ? await deleteTokensWithoutExpiration(options) : 0; const totalCount = expiredCount + noExpirationCount; console.log(`[Nuxt Users] Token cleanup completed: ${expiredCount} expired + ${noExpirationCount} without expiration = ${totalCount} total tokens removed.`); return { expiredCount, noExpirationCount, totalCount }; }; export const revokeUserTokens = async (userId, options) => { const db = await useDb(options); const personalAccessTokensTable = options.tables.personalAccessTokens; await db.sql` DELETE FROM {${personalAccessTokensTable}} WHERE tokenable_type = 'user' AND tokenable_id = ${userId} `; console.log(`[Nuxt Users] All tokens revoked for user ID: ${userId}`); }; export const getLastLoginTime = async (userId, options) => { const db = await useDb(options); const personalAccessTokensTable = options.tables.personalAccessTokens; const result = await db.sql` SELECT created_at FROM {${personalAccessTokensTable}} WHERE tokenable_id = ${userId} AND tokenable_type = 'user' ORDER BY created_at DESC LIMIT 1 `; if (result.rows.length === 0) { return null; } const lastLogin = result.rows[0].created_at; return lastLogin instanceof Date ? lastLogin.toISOString() : lastLogin; };