"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 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 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) {
        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;
    }
    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 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])
], UserService);
//# sourceMappingURL=user.service.js.map