This document describes the backend user management and authentication systems in Memos. It covers the user data model, store operations with caching, authentication mechanisms (password-based and OAuth2/SSO), personal access tokens, and role-based access control.
For frontend authentication flows and session management, see Authentication and User Management. For instance-level settings that control user behavior, see Settings and Integration Services. For data storage implementation details, see Data Storage Layer.
The User struct serves as the core entity for all user-related operations in Memos.
| Field | Type | Description |
|---|---|---|
ID | int32 | System-generated unique identifier |
RowStatus | RowStatus | State: NORMAL or ARCHIVED (soft delete) |
CreatedTs | int64 | Unix timestamp of creation |
UpdatedTs | int64 | Unix timestamp of last update |
Username | string | Unique username for login |
Role | Role | Either ADMIN or USER |
Email | string | User email address |
Nickname | string | Display name |
PasswordHash | string | bcrypt hash of password |
AvatarURL | string | URL or base64-encoded avatar image |
Description | string | User bio/description |
The system defines two roles:
RoleAdmin ("ADMIN"): Full system access, can manage users and instance settingsRoleUser ("USER"): Standard user with access to their own memos and public contentA special SystemBot user (ID 0) is predefined for system-generated actions. This bot has admin role and is used for automated operations.
SystemBot = &User{
ID: 0,
Username: "system_bot",
Role: RoleAdmin,
Nickname: "Bot",
}
Sources: store/user.go1-158
The Store provides standard CRUD operations for users:
| Operation | Method | Description |
|---|---|---|
| Create | CreateUser(ctx, *User) | Creates new user and caches it |
| Read | GetUser(ctx, *FindUser) | Retrieves user, checking cache first |
| Read List | ListUsers(ctx, *FindUser) | Lists users matching criteria, caches results |
| Update | UpdateUser(ctx, *UpdateUser) | Updates user and refreshes cache |
| Delete | DeleteUser(ctx, *DeleteUser) | Hard deletes user and removes from cache |
The store maintains an in-memory cache (s.userCache) to optimize user lookups:
Cache behavior:
Sources: store/user.go92-157
Memos supports two authentication methods: password-based and OAuth2/SSO. Both methods result in JWT access tokens that are stored in localStorage and used for subsequent API requests.
Password authentication uses bcrypt hashing and is implemented through the authServiceClient.signIn method.
Sources: web/src/components/PasswordSignInForm.tsx40-69 web/src/pages/SignIn.tsx1-131
The UpdateUser operation supports password changes:
When updating a password, callers can set either:
Password (string) - will be hashed by the service using bcryptPasswordHash (string) - pre-hashed value (for internal use)Sources: store/user.go58-71
OAuth2 and SSO are handled through the Identity Provider system. The frontend implements PKCE (Proof Key for Code Exchange, RFC 7636) for enhanced security when HTTPS is available.
PKCE Security Features:
| Feature | Implementation | Purpose |
|---|---|---|
| State Parameter | 32-byte cryptographically random hex | CSRF protection |
| State Expiry | 10 minutes | Prevent replay attacks |
| Code Verifier | 32-byte random base64url | PKCE flow |
| Code Challenge | SHA-256 hash of verifier | Prevent authorization code interception |
| sessionStorage | Isolated per browser tab | State isolation |
Fallback Behavior: If crypto.subtle is unavailable (HTTP context), PKCE is skipped and standard OAuth2 flow is used. web/src/utils/oauth.ts54-68
Sources: web/src/utils/oauth.ts1-146 web/src/pages/SignIn.tsx41-78 web/src/pages/AuthCallback.tsx27-114
Sources: web/src/pages/SignIn.tsx33-78 web/src/pages/AuthCallback.tsx1-127
| Field | Type | Description |
|---|---|---|
clientId | string | OAuth2 client identifier |
clientSecret | string | OAuth2 client secret (backend only) |
authUrl | string | Authorization endpoint URL |
tokenUrl | string | Token exchange endpoint URL |
userInfoUrl | string | User information endpoint URL |
scopes | []string | OAuth2 scopes to request (e.g., ["user:email"]) |
fieldMapping | FieldMapping | Maps OAuth2 user fields to Memos fields |
The FieldMapping structure maps OAuth2 provider user data to Memos user fields. This enables custom mapping for any OAuth2 provider:
| OAuth2 Field | Memos Field | Description |
|---|---|---|
identifier | Username | Unique identifier from provider (e.g., GitHub login) |
displayName | Nickname | Display name shown in UI |
email | Email | User email address |
avatarUrl | AvatarURL | Profile picture URL (optional) |
Each Identity Provider can optionally specify an identifierFilter - a filter expression that restricts which OAuth2 identifiers are allowed to authenticate. This enables fine-grained access control such as:
email.endsWith("@company.com")identifier in ["user1", "user2"]Sources: web/src/utils/oauth.ts1-146 web/src/pages/SignIn.tsx41-78
After successful authentication, JWT access tokens are stored in localStorage for persistence across page reloads:
web/src/components/PasswordSignInForm.tsx57-60
localStorage.setItem("access-token", token)
localStorage.setItem("access-token-expires-at", expiryTimestamp)
To prevent 401 errors when users return to the app after extended periods, the system proactively refreshes access tokens when the browser window regains focus:
The useTokenRefreshOnFocus hook only runs when a user is authenticated, preventing unnecessary API calls.
Sources: web/src/main.tsx44-47
The AuthProvider context manages the current user state and provides initialization:
Both instance and auth initialization run in parallel for improved performance.
Sources: web/src/main.tsx28-54
Personal Access Tokens (PATs) provide API authentication without requiring interactive login. They are useful for CLI tools, scripts, and third-party integrations.
CreatePersonalAccessTokenResponse.token - shown only onceAuthorization: Bearer <token> header in API requestsexpiresAt timestamp on each request| Option | Days | Use Case |
|---|---|---|
| 30 Days | 30 | Short-term integrations, testing |
| 90 Days | 90 | Medium-term automation |
| Never | 0 | Long-lived service accounts, production scripts |
Sources: web/src/main.tsx1-79
Access control is enforced at multiple layers: authentication guards in the frontend router, and authorization checks in backend API handlers.
web/src/layouts/RootLayout.tsx20-24
The RootLayout component acts as an authentication guard for all protected routes. If no current user is found, it redirects to /auth.
Sources: web/src/layouts/RootLayout.tsx20-24 web/src/router/index.tsx28-66
Sources: web/src/layouts/RootLayout.tsx1-54 store/user.go7-24
The following operations require RoleAdmin:
| Operation | Scope | Code Reference |
|---|---|---|
| Create/Update/Archive/Delete Users | User management | Backend: UserService.CreateUser(), UpdateUser(), DeleteUser() |
| Configure Instance Settings | System-wide settings | Backend: WorkspaceSettingService |
| Manage Identity Providers | SSO configuration | Backend: IdentityProviderService.CreateIdentityProvider() |
| View All Users | Member list | Backend: UserService.ListUsers() (no filter) |
Users can be in one of two states defined by the RowStatus field:
NORMAL: Active user with full access to the systemARCHIVED: Soft-deleted user, cannot authenticate or perform actions| Operation | Effect | Reversible | Use Case |
|---|---|---|---|
| Archive | Sets RowStatus to ARCHIVED | Yes | Temporary deactivation, preserve data |
| Delete | Removes user record from database | No | Permanent removal (requires prior archival) |
The two-step process prevents accidental permanent deletion.
While user roles control administrative access, memo visibility controls content access. The visibility is stored in the Visibility field of the Memo struct:
| Visibility | Access Rules | Query Filter Example |
|---|---|---|
PUBLIC | Anyone can view (even unauthenticated) | VisibilityList: [Public] |
PROTECTED | Authenticated users can view | VisibilityList: [Public, Protected] |
PRIVATE | Only creator and admins can view | VisibilityList: [Public, Protected, Private] + CreatorID filter |
The FindMemo.VisibilityList parameter in store/memo.go69 allows filtering memos by visibility. Backend services construct this list based on the current user's authentication state and role.
Sources: store/user.go7-24 store/memo.go12-82
The UserService exposes gRPC operations for user management, accessible via the gRPC Gateway at /api/v1/users/*.
Sources: store/user.go92-157
User updates use Protocol Buffer FieldMask for partial updates, specifying which fields should be modified:
Example: Archiving a user (updating only state)
updateMask: { paths: ["state"] }
user: { state: ARCHIVED }
Example: Updating profile information
updateMask: { paths: ["username", "display_name", "email", "avatar_url"] }
user: { username: "new_username", displayName: "New Name", ... }
This pattern allows clients to update specific fields without overwriting others, reducing race conditions and improving API clarity. Only the fields specified in the mask are updated; all other fields are ignored.
User webhooks enable personal automation by notifying external URLs when memo events occur. Each user can configure their own webhooks that fire for their memo events.
Webhook Structure:
name: Resource identifier (e.g., users/123/webhooks/456)displayName: User-friendly name for identificationurl: Endpoint to receive webhook POST requestsTrigger Events:
Webhooks are user-scoped, meaning each user manages their own webhooks and only receives notifications for their own memo events.
Sources: store/user.go92-157
Users can manage their own account information through the settings UI. The frontend enforces instance-level restrictions on what fields can be changed.
Users can update their profile through the account settings dialog:
Editable Fields:
User.AvatarURLdisallowChangeUsernamedisallowChangeNicknameThe update mask is computed dynamically by comparing changed fields with current values, minimizing unnecessary database writes and reducing potential race conditions.
Password changes follow this workflow:
User.PasswordHash via UpdateUserAuthorization: Admins can change passwords for any user, while regular users can only change their own.
Sources: store/user.go58-71
The User and Authentication Services provide a comprehensive system for user identity and access control:
RowStatusThe architecture separates concerns cleanly: the Store layer handles persistence and caching, the Service layer enforces business logic and permissions, and the frontend provides user-friendly management interfaces.
Refresh this wiki