How `user.service.ts` Manages User Accounts, Roles, and Permissions in Immich
The UserService enforces access control through an isAdmin flag and authentication context, restricting user enumeration to administrators unless public mode is enabled, while strictly limiting profile updates to self-service operations only.
Immich's server-side UserService serves as the central authority for user-related business logic and permission enforcement. Located at server/src/services/user.service.ts, this service coordinates database access, authentication checks, and role-based access control (RBAC) to ensure users can only interact with their own data or perform administrative actions when explicitly authorized.
Authentication-Aware Data Access
Every public method in UserService receives an AuthDto object containing the authenticated user's context. This pattern ensures that all data retrieval operations respect the caller's permissions before returning results.
The Admin Flag Check
The service relies on the isAdmin boolean property stored on the User entity to gate access to sensitive operations. In the search method (lines 23-34), UserService checks auth.user.isAdmin to determine whether the caller may enumerate all user accounts:
// server/src/services/user.service.ts
async search(auth: AuthDto) {
// Only admins can list all users unless public mode is enabled
if (!auth.user.isAdmin && !this.config.server.publicUsers) {
return [auth.user]; // Return only self
}
return this.userRepository.getAll();
}
This flag is populated during user creation in AuthService from the JWT claim roleClaim (typically "immich_role") and persists in the database via server/src/repositories/user.repository.ts.
Public User Mode Configuration
When config.server.publicUsers is enabled (configured in server/src/dtos/system-config.dto.ts), the service relaxes administrative restrictions. This allows any authenticated user to retrieve the full user list, which supports public gallery instances where user discovery is expected behavior. The check occurs alongside the admin validation in the search method, creating a logical OR condition for access.
Self-Service Profile Management
Immich strictly enforces that users can only modify their own profiles through the updateMe method, preventing privilege escalation attacks where one user might attempt to alter another's credentials or metadata.
Enforcing Ownership in updateMe
The updateMe method (lines 46-69) uses the authentication context to bind every update operation to the requesting user's ID:
@Patch('me')
async updateMe(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto) {
// Guarantees users can only edit their own record
return this.userService.updateMe(auth, dto);
}
Internally, the service fetches the user record using auth.user.id, ensuring that even if a malicious client sends a different user ID in the payload, the operation targets only the authenticated account. This design prevents role changes through profile updates, as the method only accepts fields like email, name, and password—not the isAdmin flag.
Password Hashing and Security Flags
When users update their passwords through updateMe, the service delegates hashing to cryptoRepository.hashBcrypt before persisting changes. The method also automatically clears the shouldChangePassword flag upon successful password modification, which is used to force password resets after administrative actions or security events.
Privileged Operations and Role Enforcement
Methods exposing sensitive data or administrative capabilities implicitly rely on the admin flag baked into the User entity rather than implementing separate permission checks.
License Management and Admin Restrictions
Operations like getMe, getLicense, and setLicense respect the admin flag when determining what data to return. While the service itself never mutates roles—role assignment occurs exclusively during authentication flows in AuthService—it strictly honors the isAdmin flag when deciding whether to expose license information or allow license modifications.
Background Jobs for User Cleanup
Privileged deletion logic runs through scheduled jobs handleUserDeleteCheck and handleUserDelete. These methods execute only for users marked with a deletion flag and respect the deleteDelay configuration from SystemConfig. By isolating permanent deletion to background jobs, the service ensures that destructive operations occur under controlled conditions separate from immediate API request handling.
Separation of Concerns: Server vs. Web Layer
Immich maintains two distinct user.service.ts implementations to separate business logic from UI concerns. The server-side implementation (server/src/services/user.service.ts) contains the actual permission checks, database interactions, and cryptographic operations described above.
In contrast, the web layer (web/src/lib/services/user.service.ts) provides thin wrappers around the autogenerated SDK (@immich/sdk) for UI-specific actions:
// web/src/lib/services/user.service.ts
import { lockAuthSession } from '@immich/sdk';
import { eventManager } from '$lib/managers/event-manager.svelte';
export const lockSession = async () => {
await lockAuthSession(); // Calls backend API
eventManager.emit('SessionLocked'); // Notifies UI components
};
This architecture ensures that all security-critical logic resides on the server, while the web client handles only presentation-layer events like session locking, PIN resets, and password change workflows.
Summary
UserServiceenforces RBAC through theisAdminflag checked in methods likesearch, with optional relaxation viapublicUsersconfiguration.- Self-service updates are strictly bounded by
auth.user.idinupdateMe, preventing users from modifying other accounts or elevating privileges. - Password security uses bcrypt hashing via
cryptoRepository.hashBcryptand manages theshouldChangePasswordflag automatically. - Privileged cleanup operations run as background jobs (
handleUserDeleteCheck) with configurable delays, separate from interactive API flows. - Role assignment occurs in
AuthServiceduring OAuth/JWT processing, not inUserService, which only reads and respects the admin flag.
Frequently Asked Questions
What is the difference between the server and web UserService in Immich?
The server-side UserService (server/src/services/user.service.ts) contains the core business logic, database access, and permission enforcement for user accounts. The web UserService (web/src/lib/services/user.service.ts) acts as a thin client layer that calls the autogenerated SDK for UI-specific actions like locking sessions or resetting PINs, containing no actual permission logic.
How does Immich determine if a user is an administrator?
Admin status is determined by the isAdmin boolean on the User entity. During authentication in AuthService, the system extracts the roleClaim from the OAuth provider or JWT token (typically "immich_role") and sets this flag when creating or updating the user record. UserService reads this flag but never modifies it.
Can regular users view other user accounts in Immich?
By default, only administrators can enumerate all users through the search method. However, if the server administrator enables config.server.publicUsers in the system configuration, any authenticated user can view the full user list. Without this setting, non-admin users receive only their own user record from the API.
Where is the admin role actually assigned to new users?
The admin role is assigned during the authentication flow in server/src/services/auth.service.ts, not in UserService. When a user logs in via OAuth or JWT, AuthService maps the identity provider's role claim to the isAdmin flag and persists it through UserRepository. UserService subsequently uses this flag for all permission checks but does not have authority to grant or revoke admin privileges.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →