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
JavaScript
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;
};