@rnaga/wp-node
Version:
👉 **[View Full Documentation at rnaga.github.io/wp-node →](https://rnaga.github.io/wp-node/)**
627 lines (626 loc) • 27.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UserCrud = void 0;
const zod_1 = require("zod");
const common_1 = require("../common");
const diff_1 = require("../common/diff");
const config_1 = require("../config");
const components_1 = require("../core/components");
const current_1 = require("../core/current");
const roles_1 = require("../core/roles");
const query_util_1 = require("../core/utils/query.util");
const roles_util_1 = require("../core/utils/roles.util");
const user_util_1 = require("../core/utils/user.util");
const component_1 = require("../decorators/component");
const transactions_1 = require("../transactions");
const val = __importStar(require("../validators"));
const crud_1 = require("./crud");
const error_1 = require("./error");
let UserCrud = class UserCrud extends crud_1.Crud {
config;
constructor(components, config) {
super(components);
this.config = config;
}
async getAsUpsert(userId) {
const user = await this.get(userId, { context: "edit" });
const userUpsertMeta = val.trx.userUpsertMeta.parse(user.data.metas);
const userUpsert = val.trx.userUpsert
.merge(zod_1.z.object({
user_login: val.database.wpUsers.shape.user_login,
user_pass: zod_1.z.string().transform(() => ""),
}))
.parse({
...user.data,
role: user.data.roles,
meta_input: user.data.metas,
});
return this.returnValue({
...userUpsert,
...userUpsertMeta,
});
}
// check_role_update
async checkRoleUpdatePermission(userId, role) {
const { user: currentUser } = await this.getUser();
if (!currentUser.props?.ID) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Invalid User");
}
const currentUserId = currentUser.props.ID;
const currentRole = await currentUser.role();
// Changing role
if (role.length > 0 &&
(0, diff_1.diffStringArray)(Array.from(currentRole.names), role).length > 0 &&
!(await currentUser.can("promote_user", userId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit roles of this user");
}
// Removing role
if (0 >= role.length && !(await currentUser.can("remove_users"))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit roles of this user");
}
/*
* Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
* Multisite super admins can freely edit their blog roles -- they possess all caps.
*/
if ((!this.config.isMultiSite() ||
!(await currentUser.can("manage_sites"))) &&
currentUserId === userId &&
!(await currentUser.can("edit_users"))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to give users that role");
}
}
async get(userId = 0, options) {
const userUtil = this.components.get(user_util_1.UserUtil);
const { user: currentUser } = await this.getUser();
let targetUser = currentUser;
if (userId > 0 && currentUser.props?.ID !== userId) {
if (!(await currentUser.can("list_users"))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to list users");
}
targetUser = await userUtil.get(userId);
}
if (!targetUser.props) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, "User not found");
}
let data = {};
const targetRole = await targetUser.role();
if (options?.context == "edit" &&
(currentUser.props?.ID === userId ||
(await currentUser.can("edit_users")))) {
data = {
...targetUser.props,
metas: await targetUser.meta.props(),
role: Array.from(targetRole.names),
capabilities: targetRole.capabilities,
};
}
else {
data = {
ID: targetUser.props.ID,
display_name: targetUser.props.display_name,
user_nicename: targetUser.props.user_nicename,
user_url: targetUser.props.user_url,
};
}
return this.returnValue(data);
}
async getBlogs(userId) {
const { user: currentUser, userProps: currentUserProps } = await this.getUser();
if (!currentUserProps || !(await currentUser.can("manage_network_users"))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Not permitted");
}
const userUtil = this.components.get(user_util_1.UserUtil);
const blogs = await userUtil.getBlogs(userId);
if (0 >= blogs.length) {
return this.returnValue([]);
}
const blogIds = blogs.map((blog) => blog.blog_id);
if (!(await currentUser.can("list_blog_users", blogIds))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Not permitted");
}
return this.returnValue(blogs.map((blog) => ({
site_id: blog.site_id,
blog_id: blog.blog_id,
blogname: blog.blogname,
rolenames: blog.rolenames,
})));
}
async getAvailableSites() {
const { user: currentUser, userProps: currentUserProps } = await this.getUser();
if (!currentUserProps) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Not permitted");
}
const userUtil = this.components.get(user_util_1.UserUtil);
const currentUserId = currentUserProps.ID;
const currentSites = await userUtil.getSites(currentUserId);
if (!currentSites.primary_blog) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Not permitted (no primary blog)");
}
const getCapabilities = async (user, blogId, siteId, capabilities) => {
const set = new Set(capabilities);
if (await user.can("edit_user_roles", [blogId])) {
set.add("edit_user_roles");
}
if (await user.can("manage_roles", blogId)) {
set.add("manage_roles");
}
if (await user.can("list_blog_users", [blogId])) {
set.add("list_blog_users");
}
if (!(await user.can("manage_blog_users", [blogId]))) {
set.delete("edit_users");
}
(await user.can("create_users", [siteId]))
? set.add("create_users")
: set.delete("create_users");
return Array.from(set);
};
const getBlog = async (blog) => {
const capabilities = await getCapabilities(currentUser, blog.blog_id, blog.site_id, blog.capabilities);
const returnValue = {
site_id: blog.site_id,
blog_id: blog.blog_id,
blogname: blog.blogname,
rolenames: blog.rolenames,
blog_roles: undefined,
capabilities,
};
if (await currentUser.can("manage_blog_users", [blog.blog_id])) {
return {
...returnValue,
blog_roles: blog.blog_roles,
};
}
return returnValue;
};
const sites = [];
for (const site of currentSites.sites ?? []) {
const blogs = [];
for (const blog of site.blogs) {
blogs.push(await getBlog(blog));
}
if (blogs.length > 0) {
sites.push({
site_id: site.site_id,
sitename: site.sitename,
siteurl: site.siteurl,
is_superadmin: site.is_superadmin,
blogs,
});
}
}
return this.returnValue({
user: currentSites.user,
primary_blog: await getBlog(currentSites.primary_blog),
is_multisite: this.config.isMultiSite(),
sites: 0 >= sites.length ? undefined : sites,
});
}
async create(input) {
const { user: currentUser } = await this.getUser();
if (!(await currentUser.can("create_users"))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to create new users");
}
const data = val.trx.userUpsert.parse(input);
// For not to attach default role (subscriber)
data.role = !input.role ? undefined : data.role;
if (data.role) {
const roleNames = Array.isArray(data.role)
? data.role
.map((roleName) => roleName.trim())
.filter((roleName) => roleName.length > 0)
: [data.role];
if (0 >= roleNames.length) {
data.role = undefined;
}
else {
const roles = this.components.get(roles_1.Roles);
for (const roleName of roleNames) {
if (!roles.get(roleName)) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, `The role does not exist. - ${data.role}`);
}
}
}
}
data.ID = undefined;
const userTrx = this.components.get(transactions_1.UserTrx);
const newUserId = await userTrx.upsert(data, {
attachRole: data.role ? true : false,
});
const userUtil = this.components.get(user_util_1.UserUtil);
const newUser = await userUtil.get(newUserId);
if (!newUser.props) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, "User not found");
}
const newUserLogin = newUser.props.user_login;
if (this.config.isMultiSite()) {
const signupTrx = this.components.get(transactions_1.SignupTrx);
await signupTrx.remove(newUserLogin);
if (data.role) {
const blogTrx = this.components.get(transactions_1.BlogTrx);
const current = this.components.get(current_1.Current);
await blogTrx.addUser(current.blogId, newUserId, data.role);
}
}
return this.returnValue(newUser.props);
}
async updateSuperAdmin(userId, options) {
if (!this.config.isMultiSite()) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Not supported");
}
try {
const { blogId, siteId, remove = false } = options ?? {};
await this.switchBlog({
siteId,
blogId,
});
const userUtil = this.components.get(user_util_1.UserUtil);
const user = await userUtil.get(userId);
if (!user.props?.user_login) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, "User not found");
}
const { user: currentUser } = await this.getUser();
// Not allowing to remove own.
if ((userId === currentUser.props?.ID && remove) ||
!(await currentUser.can("manage_network_users"))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Not permitted");
}
const userTrx = this.components.get(transactions_1.UserTrx);
await userTrx.syncSuperAdmin(userId, {
siteId,
blogId,
remove,
});
return this.returnValue(true);
}
finally {
await this.restoreBlog();
}
}
async updateRole(userId, role, options) {
try {
const { blogId, siteId } = options ?? {};
await this.switchBlog({
siteId,
blogId,
});
const parsedRole = zod_1.z.array(zod_1.z.string()).parse(role);
await this.checkRoleUpdatePermission(userId, parsedRole);
const userUtil = this.components.get(user_util_1.UserUtil);
const user = await userUtil.get(userId);
if (!user.props?.user_login) {
throw new error_1.CrudError(error_1.StatusMessage.NOT_FOUND, "User not found");
}
const userTrx = this.components.get(transactions_1.UserTrx);
if (parsedRole.length > 0) {
await userTrx.upsertRole(userId, parsedRole);
}
else {
await userTrx.removeRole(userId, {
removeSuperAdmin: false,
});
}
return this.returnValue(true);
}
finally {
await this.restoreBlog();
}
}
async update(userId, data, options) {
try {
const { blogId, siteId, attachRole = false, removeRole = false, } = options ?? {};
await this.switchBlog({
siteId,
blogId,
});
data.ID = userId;
const { user: currentUser } = await this.getUser();
const queryUtil = this.components.get(query_util_1.QueryUtil);
const currentUserData = (await this.getAsUpsert(userId)).data;
const diffData = (0, diff_1.diffObject)(data, currentUserData);
if (!(await currentUser.can("edit_user", userId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to edit this user");
}
if (diffData.user_login ||
currentUserData.user_login !== data.user_login) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "User Login is not editable");
}
if (typeof data.user_nicename == "string" &&
data.user_nicename.length > 0 &&
data.user_nicename !== currentUserData.user_nicename &&
(await queryUtil.users((query) => {
query.where("user_nicename", data.user_nicename);
}))) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "Invalid slug");
}
const userTrx = this.components.get(transactions_1.UserTrx);
if (data.role) {
const roleNames = Array.isArray(data.role)
? data.role
.map((roleName) => roleName.trim())
.filter((roleName) => roleName.length > 0)
: [data.role];
await this.checkRoleUpdatePermission(userId, roleNames);
if (0 >= roleNames.length) {
data.role = undefined;
}
else {
const roles = this.components.get(roles_1.Roles);
for (const roleName of roleNames) {
if (!roles.get(roleName)) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, `The role does not exist - ${data.role}`);
}
}
}
}
return this.returnValue(await userTrx.upsert(data, {
attachRole,
removeRole,
}));
}
finally {
await this.restoreBlog();
}
}
async updatePassword(userId, newPassword) {
const userUtil = this.components.get(user_util_1.UserUtil);
const user = await userUtil.get(userId);
if (!user.props || !user.props.user_login) {
throw new error_1.CrudError(error_1.StatusMessage.BAD_REQUEST, "User not found");
}
const userLogin = user.props.user_login;
const hashedPassword = (0, common_1.hashPassword)(newPassword);
return await this.update(userId, {
user_login: userLogin,
user_pass: hashedPassword,
});
}
async delete(userId, options) {
const { reassignList = undefined } = options ?? {};
let { reassign = undefined } = options ?? {};
const { user: currentUser } = await this.getUser();
const userTrx = this.components.get(transactions_1.UserTrx);
if (!(await currentUser.can("delete_user", userId))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to delete this user");
}
if (!this.config.isMultiSite()) {
if (reassignList) {
reassign = Object.values(reassignList)[0];
}
return this.returnValue(await userTrx.remove(userId, reassign));
}
return this.returnValue(await userTrx.removeFromAllBlogs(userId, reassignList));
}
async list(args, options) {
try {
const { context = "view" } = options ?? {};
const queryUtil = this.components.get(query_util_1.QueryUtil);
const userUtil = this.components.get(user_util_1.UserUtil);
const rolesUtil = this.components.get(roles_util_1.RolesUtil);
const parsedArgs = val.crud.userListParams.parse(args ?? {});
await this.switchBlog({
siteId: parsedArgs.site_id,
blogId: parsedArgs.blog_id,
});
const { user: currentUser } = await this.getUser();
const currentRole = await currentUser.role();
if ((parsedArgs.roles ||
parsedArgs.superadmins ||
parsedArgs.include_unregistered_users ||
context == "edit" ||
parsedArgs.orderby == "user_email" ||
parsedArgs.orderby == "user_registered") &&
(!currentUser.props?.ID || !(await currentUser.can("list_users")))) {
throw new error_1.CrudError(error_1.StatusMessage.UNAUTHORIZED, "Sorry, you are not allowed to filter users by role");
}
let blogIds = [];
let superAdmins = [];
if (this.config.isMultiSite()) {
const current = this.components.get(current_1.Current);
superAdmins =
(await rolesUtil.getSuperAdmins({
siteId: current.siteId,
})) ?? [];
if (parsedArgs.site_id) {
const siteId = parsedArgs.site_id;
const blogs = (await queryUtil.blogs((query) => {
query.where("site_id", siteId);
})) ?? [];
// List all users in site
blogs.map((blog) => blogIds?.push(blog.blog_id));
}
else {
const current = this.components.get(current_1.Current);
blogIds = [parsedArgs.blog_id ?? current.blogId];
}
}
const includeAnonymous = Array.isArray(parsedArgs.roles) &&
parsedArgs.roles.filter((role) => role === "anonymous").length > 0;
const roleNames = !Array.isArray(parsedArgs.roles)
? []
: parsedArgs.roles.filter((role) => role !== "anonymous");
const buildSearchQuery = (query) => {
query.andWhere((query) => {
const searchColumns = [
"user_login",
"user_nicename",
"display_name",
"user_email",
];
for (const searchColumn of searchColumns) {
parsedArgs.search &&
query.or.whereLike(searchColumn, parsedArgs.search);
}
});
};
const buildQuery = (query) => {
const { column } = query.alias;
const offset = parsedArgs.offset ?? (parsedArgs.page - 1) * parsedArgs.per_page;
const limit = parsedArgs.per_page;
if (includeAnonymous && !parsedArgs.exclude_anonymous) {
query.hasNoRole();
}
else if (blogIds.length > 0) {
query.andWhere((query) => {
query.withBlogIds(blogIds);
});
}
else if (!this.config.isMultiSite()) {
// For single site
query.hasRole();
}
query.builder
.offset(offset)
.limit(limit)
.groupBy(column("users", "ID"));
if (parsedArgs.orderby) {
query.builder.orderBy(column("users", parsedArgs.orderby), parsedArgs.order);
}
if (Array.isArray(parsedArgs.include)) {
query.whereIn("ID", parsedArgs.include);
}
if (Array.isArray(parsedArgs.exclude)) {
query.andWhereNot((query) => query.whereIn("ID", parsedArgs.exclude));
}
if (parsedArgs.search) {
buildSearchQuery(query);
}
if (Array.isArray(parsedArgs.slug) && parsedArgs.slug.length > 0) {
query.where("user_nicename", parsedArgs.slug);
}
if (roleNames.length > 0) {
query.withRoles(roleNames);
}
// SuperAdmins
if (superAdmins.length > 0) {
if (parsedArgs.site_id && currentRole.isSuperAdmin()) {
query[parsedArgs.superadmins ? "andWhere" : "orWhere"]((query) => {
query.whereIn("user_login", superAdmins);
});
}
}
// Include anonymous users if no role is specified
if (!includeAnonymous &&
!parsedArgs.exclude_anonymous &&
0 >= roleNames.length &&
!parsedArgs.superadmins) {
query.orWhere((query) => {
query.hasNoRole();
if (parsedArgs.search) {
buildSearchQuery(query);
}
});
}
if (parsedArgs.has_published_posts) {
query.withPublishedPosts();
}
};
const users = (await queryUtil.users((query) => {
buildQuery(query);
})) ?? [];
const counts = await queryUtil.users((query) => {
buildQuery(query);
query.count("users", "ID");
}, val.query.resultCount);
const countPosts = context !== "edit"
? []
: await userUtil.countPosts(users.map((user) => user.ID));
const data = [];
for (const user of await userUtil.toUsers(users)) {
if (!user.props)
continue;
// const props = Object.entries(user.props)
// .filter(([k]) => k !== "user_pass")
// .reduce((a, b) => ({ ...a, [b[0]]: b[1] }), {}) as Exclude<
// types.WpUsers,
// "user_pass"
// >;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { user_pass, ...props } = user.props;
const role = await user.role();
const posts = countPosts?.filter((v) => v.post_author == props.ID)[0]?.count ?? 0;
if (this.config.isMultiSite() && !currentRole.isSuperAdmin()) {
role.names.delete("superadmin");
}
if (context === "edit") {
data.push({
...props,
metas: await user.meta.props(),
roles: Array.from(role.names),
capabilities: role.capabilities,
posts,
});
}
else {
data.push({
ID: props.ID,
display_name: props.display_name,
user_url: props.user_url,
user_nicename: props.user_nicename,
user_login: props.user_login,
posts,
});
}
}
const pagination = this.pagination({
page: parsedArgs.page,
limit: parsedArgs.per_page,
count: counts?.count ?? 0,
});
return this.returnValue(data, {
pagination,
context,
});
}
finally {
await this.restoreBlog();
}
}
};
exports.UserCrud = UserCrud;
exports.UserCrud = UserCrud = __decorate([
(0, component_1.component)(),
__metadata("design:paramtypes", [components_1.Components, config_1.Config])
], UserCrud);