n8n
Version:
n8n Workflow Automation Tool
316 lines • 15 kB
JavaScript
"use strict";
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 bad_request_error_1 = require("../errors/response-errors/bad-request.error");
const internal_server_error_1 = require("../errors/response-errors/internal-server.error");
const not_found_error_1 = require("../errors/response-errors/not-found.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 jwt_service_1 = require("./jwt.service");
const ownership_service_1 = require("./ownership.service");
const project_service_ee_1 = require("./project.service.ee");
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, ownershipService, publicApiKeyService, roleService, globalConfig, jwtService, projectService) {
this.logger = logger;
this.userRepository = userRepository;
this.projectRepository = projectRepository;
this.mailer = mailer;
this.urlService = urlService;
this.eventService = eventService;
this.ownershipService = ownershipService;
this.publicApiKeyService = publicApiKeyService;
this.roleService = roleService;
this.globalConfig = globalConfig;
this.jwtService = jwtService;
this.projectService = projectService;
}
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 assertGetUsersAccess(user, projectId) {
if (projectId) {
const project = await this.projectService.getProjectWithScope(user, projectId, [
'project:list',
]);
if (!project) {
throw new not_found_error_1.NotFoundError('Project not found');
}
return;
}
}
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 findUserWithAuthIdentities(userId) {
return await this.userRepository.findOneOrFail({
where: { id: userId },
relations: ['role', 'authIdentities'],
});
}
async findSsoIdentity(userId) {
const user = await this.userRepository.findOne({
where: { id: userId },
relations: ['authIdentities'],
});
const ssoIdentity = user?.authIdentities?.find((identity) => identity.providerType !== 'email');
return ssoIdentity;
}
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?.posthog) {
publicUser = await this.addFeatureFlags(publicUser, options.posthog);
}
if (options?.withScopes) {
publicUser.globalScopes = (0, permissions_1.getGlobalScopes)(user);
}
publicUser.mfaAuthenticated = options?.mfaAuthenticated ?? false;
const { instanceSettingsLoader } = this.globalConfig;
if (instanceSettingsLoader.ownerManagedByEnv &&
!!user.email &&
user.email.toLowerCase() === instanceSettingsLoader.ownerEmail.toLowerCase()) {
publicUser.isManagedByEnv = true;
}
return publicUser;
}
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 token = this.jwtService.sign({
inviterId: owner.id,
inviteeId: id,
}, {
expiresIn: '90d',
});
const inviteAcceptUrl = `${domain}/signup?token=${token}`;
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,
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');
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 } });
}
});
await this.ownershipService.invalidateProjectOwnerCacheByUserId(user.id);
}
async processTokenBasedInvite(token) {
try {
const decoded = this.jwtService.verify(token);
if (!decoded.inviterId || !decoded.inviteeId) {
this.logger.debug('Invalid JWT token payload - missing inviterId or inviteeId');
throw new bad_request_error_1.BadRequestError('Invalid invite URL');
}
return { inviterId: decoded.inviterId, inviteeId: decoded.inviteeId };
}
catch (error) {
if (error instanceof bad_request_error_1.BadRequestError) {
throw error;
}
this.logger.debug('Failed to verify JWT token', { error });
throw new bad_request_error_1.BadRequestError('Invalid invite URL');
}
}
async getInvitationIdsFromPayload(token) {
const instanceOwner = await this.userRepository.findOne({
where: { role: { slug: db_1.GLOBAL_OWNER_ROLE.slug } },
});
if (!instanceOwner) {
throw new bad_request_error_1.BadRequestError('Instance owner not found');
}
return await this.processTokenBasedInvite(token);
}
};
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,
ownership_service_1.OwnershipService,
public_api_key_service_1.PublicApiKeyService,
role_service_1.RoleService,
config_1.GlobalConfig,
jwt_service_1.JwtService,
project_service_ee_1.ProjectService])
], UserService);
//# sourceMappingURL=user.service.js.map