n8n-io/n8n · error · ForbiddenError
Admin cannot reset password of global owner
Error message
Admin cannot reset password of global owner
What it means
Returned by GET /users/:id/password-reset-link when the requesting user holds the global admin role (GLOBAL_ADMIN_ROLE.slug) but the target user is the instance/global owner (GLOBAL_OWNER_ROLE.slug). This is a deliberate authorization guard: admins may reset passwords for anyone except the highest-privilege owner. HTTP 403.
Source
Thrown at packages/cli/src/controllers/users.controller.ts:166
});
}
@Get('/:id/password-reset-link')
@GlobalScope('user:resetPassword')
async getUserPasswordResetLink(req: UserRequest.PasswordResetLink) {
const user = await this.userRepository.findOneOrFail({
where: { id: req.params.id },
relations: ['role'],
});
if (!user) {
throw new NotFoundError('User not found');
}
if (
req.user.role.slug === GLOBAL_ADMIN_ROLE.slug &&
user.role.slug === GLOBAL_OWNER_ROLE.slug
) {
throw new ForbiddenError('Admin cannot reset password of global owner');
}
const link = this.authService.generatePasswordResetUrl(user);
return { link };
}
@Post('/:id/invite-link')
@GlobalScope('user:generateInviteLink')
async generateInviteLink(req: AuthenticatedRequest<{ id: string }, {}, {}, {}>, _res: Response) {
const inviterId = req.user.id;
const inviteeId = req.params.id;
const targetUser = await this.userRepository.findOne({ where: { id: inviteeId } });
if (!targetUser) {
throw new NotFoundError('User to generate invite link for not found');
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Have the global owner reset their own password through the self-service flow instead.
- Filter the owner out of the target list before issuing bulk reset calls.
- If a true owner password reset is required, follow the documented owner-recovery/runbook rather than the admin endpoint.
Defensive patterns
Strategy: validation
Validate before calling
function canResetPassword(requesterRoleSlug: string, targetRoleSlug: string) {
return !(requesterRoleSlug === 'global:admin' && targetRoleSlug === 'global:owner');
}
if (!canResetPassword(reqUser.role.slug, target.role.slug)) {
throw new Error('Owner password must be reset through the owner-recovery flow');
} Type guard
const isOwner = (slug: string) => slug === 'global:owner'; const isAdmin = (slug: string) => slug === 'global:admin';
Try / catch
try { await fetch(`/rest/users/${id}/password-reset-link`); }
catch (e) { if (e.statusCode === 403) { /* escalate to owner-recovery */ } else throw e; } Prevention
- Filter global:owner out of admin-driven reset lists.
- Surface role badges in the UI so admins see owner rows distinctly.
- Document the owner-password recovery runbook separately.
When it happens
Trigger: A global admin (not the owner) calls GET /users/<owner-id>/password-reset-link. The role-slug comparison matches GLOBAL_ADMIN_ROLE for the requester and GLOBAL_OWNER_ROLE for the target.
Common situations: Admin console listing where the owner is visually indistinguishable from other admins; scripted bulk reset that iterates all user ids including the owner; RBAC misconfiguration promoting someone to admin who then tries owner operations.
Related errors
- Admin cannot change role on global owner
- Instance owner cannot be deleted.
- Owner cannot change role on global owner
- Instance roles are managed automatically and cannot be chang
- Cannot change your own global role
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/c4a25b84a0d56a4a.
Report an issue: GitHub.