mirror of https://github.com/usememos/memos.git
fix(api): correct user registration logic and first-user detection
- Changed first-user detection to check for any users instead of only HOST users - Moved registration setting check before role assignment to properly block unauthorized registrations - Fixed role assignment logic to ensure unauthenticated users always get USER role - Updated test cases to create host user first when not testing first-user scenario This ensures the first user is always created as HOST and registration settings are properly enforced for subsequent user creation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
21d31e3609
commit
8f136ffa75
|
|
@ -34,8 +34,12 @@ func TestCreateUserRegistration(t *testing.T) {
|
||||||
ts := NewTestService(t)
|
ts := NewTestService(t)
|
||||||
defer ts.Cleanup()
|
defer ts.Cleanup()
|
||||||
|
|
||||||
|
// Create a host user first so we're not in first-user setup mode
|
||||||
|
_, err := ts.CreateHostUser(ctx, "admin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Disable user registration
|
// Disable user registration
|
||||||
_, err := ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
_, err = ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
||||||
Key: storepb.InstanceSettingKey_GENERAL,
|
Key: storepb.InstanceSettingKey_GENERAL,
|
||||||
Value: &storepb.InstanceSetting_GeneralSetting{
|
Value: &storepb.InstanceSetting_GeneralSetting{
|
||||||
GeneralSetting: &storepb.InstanceGeneralSetting{
|
GeneralSetting: &storepb.InstanceGeneralSetting{
|
||||||
|
|
@ -147,6 +151,10 @@ func TestCreateUserRegistration(t *testing.T) {
|
||||||
ts := NewTestService(t)
|
ts := NewTestService(t)
|
||||||
defer ts.Cleanup()
|
defer ts.Cleanup()
|
||||||
|
|
||||||
|
// Create a host user first so we're not in first-user setup mode
|
||||||
|
_, err := ts.CreateHostUser(ctx, "admin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// User registration is enabled by default
|
// User registration is enabled by default
|
||||||
|
|
||||||
// Unauthenticated user tries to create admin user - role should be ignored
|
// Unauthenticated user tries to create admin user - role should be ignored
|
||||||
|
|
|
||||||
|
|
@ -140,36 +140,19 @@ func (s *APIV1Service) GetUserAvatar(ctx context.Context, request *v1pb.GetUserA
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) {
|
func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) {
|
||||||
// Check if there are any existing host users (for first-time setup detection)
|
// Get current user (might be nil for unauthenticated requests)
|
||||||
hostUserType := store.RoleHost
|
currentUser, _ := s.GetCurrentUser(ctx)
|
||||||
existedHostUsers, err := s.Store.ListUsers(ctx, &store.FindUser{
|
|
||||||
Role: &hostUserType,
|
// Check if there are any existing users (for first-time setup detection)
|
||||||
})
|
limitOne := 1
|
||||||
|
allUsers, err := s.Store.ListUsers(ctx, &store.FindUser{Limit: &limitOne})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to list host users: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
|
||||||
}
|
}
|
||||||
|
isFirstUser := len(allUsers) == 0
|
||||||
|
|
||||||
// Determine the role to assign and check permissions
|
// Check registration settings FIRST (unless it's the very first user)
|
||||||
var roleToAssign store.Role
|
if !isFirstUser {
|
||||||
if len(existedHostUsers) == 0 {
|
|
||||||
// First-time setup: create the first user as HOST (no authentication required)
|
|
||||||
roleToAssign = store.RoleHost
|
|
||||||
} else {
|
|
||||||
// Regular user creation: allow unauthenticated creation of normal users
|
|
||||||
// But if authenticated, check if user has HOST permission for any role
|
|
||||||
currentUser, err := s.GetCurrentUser(ctx)
|
|
||||||
if err == nil && currentUser != nil && currentUser.Role == store.RoleHost {
|
|
||||||
// Authenticated HOST user can create users with any role specified in request
|
|
||||||
if request.User.Role != v1pb.User_ROLE_UNSPECIFIED {
|
|
||||||
roleToAssign = convertUserRoleToStore(request.User.Role)
|
|
||||||
} else {
|
|
||||||
roleToAssign = store.RoleUser
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unauthenticated or non-HOST users can only create normal users
|
|
||||||
roleToAssign = store.RoleUser
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only allow user registration if it is enabled in the settings, or if the user is a superuser
|
// Only allow user registration if it is enabled in the settings, or if the user is a superuser
|
||||||
if currentUser == nil || !isSuperUser(currentUser) {
|
if currentUser == nil || !isSuperUser(currentUser) {
|
||||||
instanceGeneralSetting, err := s.Store.GetInstanceGeneralSetting(ctx)
|
instanceGeneralSetting, err := s.Store.GetInstanceGeneralSetting(ctx)
|
||||||
|
|
@ -182,6 +165,23 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the role to assign
|
||||||
|
var roleToAssign store.Role
|
||||||
|
if isFirstUser {
|
||||||
|
// First-time setup: create the first user as HOST (no authentication required)
|
||||||
|
roleToAssign = store.RoleHost
|
||||||
|
} else if currentUser != nil && currentUser.Role == store.RoleHost {
|
||||||
|
// Authenticated HOST user can create users with any role specified in request
|
||||||
|
if request.User.Role != v1pb.User_ROLE_UNSPECIFIED {
|
||||||
|
roleToAssign = convertUserRoleToStore(request.User.Role)
|
||||||
|
} else {
|
||||||
|
roleToAssign = store.RoleUser
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unauthenticated or non-HOST users can only create normal users
|
||||||
|
roleToAssign = store.RoleUser
|
||||||
|
}
|
||||||
|
|
||||||
if !base.UIDMatcher.MatchString(strings.ToLower(request.User.Username)) {
|
if !base.UIDMatcher.MatchString(strings.ToLower(request.User.Username)) {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
|
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue