n8n
Version:
n8n Workflow Automation Tool
260 lines • 12.6 kB
JavaScript
;
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 __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.UserService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const permissions_1 = require("@n8n/permissions");
const n8n_workflow_1 = require("n8n-workflow");
const internal_server_error_1 = require("../errors/response-errors/internal-server.error");
const event_service_1 = require("../events/event.service");
const url_service_1 = require("../services/url.service");
const email_1 = require("../user-management/email");
const public_api_key_service_1 = require("./public-api-key.service");
const role_service_1 = require("./role.service");
let UserService = class UserService {
constructor(logger, userRepository, projectRepository, mailer, urlService, eventService, publicApiKeyService, roleService, globalConfig) {
this.logger = logger;
this.userRepository = userRepository;
this.projectRepository = projectRepository;
this.mailer = mailer;
this.urlService = urlService;
this.eventService = eventService;
this.publicApiKeyService = publicApiKeyService;
this.roleService = roleService;
this.globalConfig = globalConfig;
}
async update(userId, data) {
const user = await this.userRepository.findOneBy({ id: userId });
if (user) {
await this.userRepository.save({ ...user, ...data }, { transaction: true });
}
return;
}
getManager() {
return this.userRepository.manager;
}
async updateSettings(userId, newSettings) {
const user = await this.userRepository.findOneOrFail({ where: { id: userId } });
if (user.settings) {
Object.assign(user.settings, newSettings);
}
else {
user.settings = newSettings;
}
await this.userRepository.save(user);
}
async toPublic(user, options) {
const { password, updatedAt, authIdentities, mfaRecoveryCodes, mfaSecret, role, ...rest } = user;
const providerType = authIdentities?.[0]?.providerType;
let publicUser = {
...rest,
role: role?.slug,
signInType: providerType ?? 'email',
isOwner: user.role.slug === 'global:owner',
};
if (options?.withInviteUrl && !options?.inviterId) {
throw new n8n_workflow_1.UnexpectedError('Inviter ID is required to generate invite URL');
}
const inviteLinksEmailOnly = this.globalConfig.userManagement.inviteLinksEmailOnly;
if (!inviteLinksEmailOnly &&
options?.withInviteUrl &&
options?.inviterId &&
publicUser.isPending) {
publicUser = this.addInviteUrl(options.inviterId, publicUser);
}
if (options?.posthog) {
publicUser = await this.addFeatureFlags(publicUser, options.posthog);
}
if (options?.withScopes) {
publicUser.globalScopes = (0, permissions_1.getGlobalScopes)(user);
}
publicUser.mfaAuthenticated = options?.mfaAuthenticated ?? false;
return publicUser;
}
addInviteUrl(inviterId, invitee) {
const url = new URL(this.urlService.getInstanceBaseUrl());
url.pathname = '/signup';
url.searchParams.set('inviterId', inviterId);
url.searchParams.set('inviteeId', invitee.id);
invitee.inviteAcceptUrl = url.toString();
return invitee;
}
async addFeatureFlags(publicUser, posthog) {
const timeoutPromise = new Promise((resolve) => {
setTimeout(() => {
resolve(publicUser);
}, 1500);
});
const fetchPromise = new Promise(async (resolve) => {
publicUser.featureFlags = await posthog.getFeatureFlags(publicUser);
resolve(publicUser);
});
return await Promise.race([fetchPromise, timeoutPromise]);
}
async sendEmails(owner, toInviteUsers, role) {
const domain = this.urlService.getInstanceBaseUrl();
const inviteLinksEmailOnly = this.globalConfig.userManagement.inviteLinksEmailOnly;
return await Promise.all(Object.entries(toInviteUsers).map(async ([email, id]) => {
const inviteAcceptUrl = `${domain}/signup?inviterId=${owner.id}&inviteeId=${id}`;
const invitedUser = {
user: {
id,
email,
emailSent: false,
role,
},
error: '',
};
try {
const result = await this.mailer.invite({
email,
inviteAcceptUrl,
});
if (result.emailSent) {
invitedUser.user.emailSent = true;
this.eventService.emit('user-transactional-email-sent', {
userId: id,
messageType: 'New user invite',
publicApi: false,
});
}
if (!inviteLinksEmailOnly && !result.emailSent) {
invitedUser.user.inviteAcceptUrl = inviteAcceptUrl;
}
this.eventService.emit('user-invited', {
user: owner,
targetUserId: Object.values(toInviteUsers),
publicApi: false,
emailSent: result.emailSent,
inviteeRole: role,
});
}
catch (e) {
if (e instanceof Error) {
this.eventService.emit('email-failed', {
user: owner,
messageType: 'New user invite',
publicApi: false,
});
this.logger.error('Failed to send email', {
userId: owner.id,
inviteAcceptUrl,
email,
});
invitedUser.error = e.message;
}
}
return invitedUser;
}));
}
async inviteUsers(owner, invitations) {
const emails = invitations.map(({ email }) => email);
const existingUsers = await this.userRepository.findManyByEmail(emails);
const existUsersEmails = existingUsers.map((user) => user.email);
const toCreateUsers = invitations.filter(({ email }) => !existUsersEmails.includes(email));
const pendingUsersToInvite = existingUsers.filter((email) => email.isPending);
const createdUsers = new Map();
this.logger.debug(toCreateUsers.length > 1
? `Creating ${toCreateUsers.length} user shells...`
: 'Creating 1 user shell...');
await this.roleService.checkRolesExist(invitations.map(({ role }) => role), 'global');
try {
await this.getManager().transaction(async (transactionManager) => await Promise.all(toCreateUsers.map(async ({ email, role }) => {
const { user: savedUser } = await this.userRepository.createUserWithProject({
email,
role: {
slug: role,
},
}, transactionManager);
createdUsers.set(email, savedUser.id);
return savedUser;
})));
}
catch (error) {
this.logger.error('Failed to create user shells', { userShells: createdUsers });
throw new internal_server_error_1.InternalServerError('An error occurred during user creation', error);
}
pendingUsersToInvite.forEach(({ email, id }) => createdUsers.set(email, id));
const usersInvited = await this.sendEmails(owner, Object.fromEntries(createdUsers), invitations[0].role);
return { usersInvited, usersCreated: toCreateUsers.map(({ email }) => email) };
}
async changeUserRole(user, newRole) {
await this.roleService.checkRolesExist([newRole.newRoleName], 'global');
return await this.userRepository.manager.transaction(async (trx) => {
await trx.update(db_1.User, { id: user.id }, { role: { slug: newRole.newRoleName } });
const isAdminRole = (roleName) => {
return roleName === 'global:admin' || roleName === 'global:owner';
};
const isDowngradedToChatUser = user.role.slug !== 'global:chatUser' && newRole.newRoleName === 'global:chatUser';
const isUpgradedChatUser = user.role.slug === 'global:chatUser' && newRole.newRoleName !== 'global:chatUser';
const isDowngradedAdmin = isAdminRole(user.role.slug) && !isAdminRole(newRole.newRoleName);
if (isDowngradedToChatUser) {
const projectRelations = await trx.find(db_1.ProjectRelation, {
where: { userId: user.id, role: { slug: (0, db_1.Not)(permissions_1.PROJECT_OWNER_ROLE_SLUG) } },
relations: ['role'],
});
for (const relation of projectRelations) {
if (relation.role.slug === permissions_1.PROJECT_ADMIN_ROLE_SLUG) {
const adminCount = await trx.count(db_1.ProjectRelation, {
where: {
projectId: relation.projectId,
role: { slug: (0, db_1.In)([permissions_1.PROJECT_ADMIN_ROLE_SLUG, permissions_1.PROJECT_OWNER_ROLE_SLUG]) },
userId: (0, db_1.Not)(user.id),
},
});
if (adminCount === 0) {
throw new n8n_workflow_1.UserError(`Cannot downgrade user as they are the only project admin in project "${relation.projectId}".`);
}
}
await trx.delete(db_1.ProjectRelation, {
userId: user.id,
projectId: relation.projectId,
});
}
const personalProject = await this.projectRepository.getPersonalProjectForUserOrFail(user.id, trx);
await trx.update(db_1.ProjectRelation, {
userId: user.id,
role: { slug: permissions_1.PROJECT_OWNER_ROLE_SLUG },
projectId: personalProject.id,
}, { role: { slug: permissions_1.PROJECT_VIEWER_ROLE_SLUG } });
await this.publicApiKeyService.deleteAllApiKeysForUser(user, trx);
}
else if (isDowngradedAdmin) {
await this.publicApiKeyService.removeOwnerOnlyScopesFromApiKeys(user, trx);
}
else if (isUpgradedChatUser) {
const personalProject = await this.projectRepository.getPersonalProjectForUserOrFail(user.id, trx);
await trx.update(db_1.ProjectRelation, {
userId: user.id,
role: { slug: permissions_1.PROJECT_VIEWER_ROLE_SLUG },
projectId: personalProject.id,
}, { role: { slug: permissions_1.PROJECT_OWNER_ROLE_SLUG } });
}
});
}
};
exports.UserService = UserService;
exports.UserService = UserService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger,
db_1.UserRepository,
db_1.ProjectRepository,
email_1.UserManagementMailer,
url_service_1.UrlService,
event_service_1.EventService,
public_api_key_service_1.PublicApiKeyService,
role_service_1.RoleService,
config_1.GlobalConfig])
], UserService);
//# sourceMappingURL=user.service.js.map