UNPKG

nuxt-users

Version:

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

244 lines (243 loc) 8.96 kB
import { useDb } from "./db.js"; import bcrypt from "bcrypt"; import { validatePassword, getPasswordValidationOptions } from "nuxt-users/utils"; 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, active FROM {${usersTable}} WHERE email = ${userData.email}`; if (result.rows.length === 0) { throw new Error("Failed to retrieve created user."); } const user = result.rows[0]; if (!user) { throw new Error("Failed to retrieve created user."); } 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, active: user.active }; }; 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]; if (!user) { return null; } return { id: user.id, email: user.email, name: user.name, password: user.password, role: user.role, created_at: user.created_at, updated_at: user.updated_at, active: user.active }; }; 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]; if (!user) { return null; } 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", "active"]; const fieldsToUpdate = allowedFields.filter((field) => userData[field] !== void 0); if (fieldsToUpdate.length === 0) { const currentUser = await findUserById(id, options); if (!currentUser) { throw new Error("User not found."); } return currentUser; } if (userData.active === false) { await revokeUserTokens(id, options); } for (const field of fieldsToUpdate) { await db.sql`UPDATE {${usersTable}} SET {${field}} = ${userData[field]} WHERE id = ${id}`; } await db.sql`UPDATE {${usersTable}} SET updated_at = CURRENT_TIMESTAMP 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 userExists = await db.sql`SELECT id FROM {${usersTable}} WHERE id = ${id}`; if (userExists.rows.length === 0) { throw new Error("User not found."); } await revokeUserTokens(id, options); if (options.hardDelete) { await db.sql`DELETE FROM {${usersTable}} WHERE id = ${id}`; return; } await db.sql`UPDATE {${usersTable}} SET active = false, updated_at = CURRENT_TIMESTAMP WHERE id = ${id}`; }; 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}}`; const firstRow = users.rows?.[0]; return firstRow ? firstRow.count > 0 : false; } 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 tokenRow = tokenResult.rows[0]; if (!tokenRow) { return null; } const userId = tokenRow.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 (!user) { return null; } if (!user.active) { return null; } 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 resultRow = result.rows[0]; if (!resultRow) { return null; } const lastLogin = resultRow.created_at; return lastLogin instanceof Date ? lastLogin.toISOString() : lastLogin; };