mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
11 KiB
Go
388 lines
11 KiB
Go
package test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
|
|
|
apiv1 "github.com/usememos/memos/proto/gen/api/v1"
|
|
storepb "github.com/usememos/memos/proto/gen/store"
|
|
apiv1server "github.com/usememos/memos/server/router/api/v1"
|
|
"github.com/usememos/memos/store"
|
|
)
|
|
|
|
func TestCreateUserRegistration(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("CreateUser success when registration enabled", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// User registration is enabled by default, no need to set it explicitly
|
|
|
|
// Create user without authentication - should succeed
|
|
_, err := ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newuser",
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("CreateUser blocked when registration disabled", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
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
|
|
_, err = ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{
|
|
GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowUserRegistration: true,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Try to create user without authentication - should fail
|
|
_, err = ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newuser",
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "not allowed")
|
|
})
|
|
|
|
t.Run("CreateUser succeeds for superuser even when registration disabled", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create host user
|
|
hostUser, err := ts.CreateHostUser(ctx, "admin")
|
|
require.NoError(t, err)
|
|
hostCtx := ts.CreateUserContext(ctx, hostUser.ID)
|
|
|
|
// Disable user registration
|
|
_, err = ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{
|
|
GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowUserRegistration: true,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Host user can create users even when registration is disabled - should succeed
|
|
_, err = ts.Service.CreateUser(hostCtx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newuser",
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("CreateUser regular user cannot create users when registration disabled", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create regular user
|
|
regularUser, err := ts.CreateRegularUser(ctx, "regularuser")
|
|
require.NoError(t, err)
|
|
regularUserCtx := ts.CreateUserContext(ctx, regularUser.ID)
|
|
|
|
// Disable user registration
|
|
_, err = ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{
|
|
GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowUserRegistration: true,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Regular user tries to create user when registration is disabled - should fail
|
|
_, err = ts.Service.CreateUser(regularUserCtx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newuser",
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "not allowed")
|
|
})
|
|
|
|
t.Run("CreateUser host can assign roles", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create host user
|
|
hostUser, err := ts.CreateHostUser(ctx, "admin")
|
|
require.NoError(t, err)
|
|
hostCtx := ts.CreateUserContext(ctx, hostUser.ID)
|
|
|
|
// Host user can create user with specific role - should succeed
|
|
createdUser, err := ts.Service.CreateUser(hostCtx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newadmin",
|
|
Email: "newadmin@example.com",
|
|
Password: "password123",
|
|
Role: apiv1.User_ADMIN,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "users/newadmin", createdUser.Name)
|
|
require.NotNil(t, createdUser)
|
|
require.Equal(t, apiv1.User_ADMIN, createdUser.Role)
|
|
})
|
|
|
|
t.Run("CreateUser unauthenticated user can only create regular user", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
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
|
|
|
|
// Unauthenticated user tries to create admin user - role should be ignored
|
|
createdUser, err := ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "wannabeadmin",
|
|
Email: "wannabeadmin@example.com",
|
|
Password: "password123",
|
|
Role: apiv1.User_ADMIN, // This should be ignored
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, createdUser)
|
|
require.Equal(t, "users/wannabeadmin", createdUser.Name)
|
|
require.Equal(t, apiv1.User_USER, createdUser.Role, "Unauthenticated users can only create USER role")
|
|
})
|
|
|
|
t.Run("CreateUser blocked when password auth disabled for self signup", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
_, err := ts.CreateHostUser(ctx, "admin")
|
|
require.NoError(t, err)
|
|
|
|
_, err = ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_GENERAL,
|
|
Value: &storepb.InstanceSetting_GeneralSetting{
|
|
GeneralSetting: &storepb.InstanceGeneralSetting{
|
|
DisallowPasswordAuth: true,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newuser",
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "password signup is not allowed")
|
|
})
|
|
|
|
t.Run("CreateUser rejects empty password", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
_, err := ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "newuser",
|
|
Email: "newuser@example.com",
|
|
Password: "",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "password must not be empty")
|
|
})
|
|
|
|
t.Run("CreateUser rejects invalid writable usernames", func(t *testing.T) {
|
|
for _, username := range []string{"alice@example.com", "legacy_user", "123"} {
|
|
t.Run(username, func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
_, err := ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: username,
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid username")
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("CreateUser validate only rejects invalid writable username", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
_, err := ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: "alice@example.com",
|
|
Email: "newuser@example.com",
|
|
Password: "password123",
|
|
},
|
|
ValidateOnly: true,
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid username")
|
|
})
|
|
|
|
t.Run("UpdateUser rejects empty password", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
user, err := ts.CreateRegularUser(ctx, "alice")
|
|
require.NoError(t, err)
|
|
|
|
authCtx := ts.CreateUserContext(ctx, user.ID)
|
|
_, err = ts.Service.UpdateUser(authCtx, &apiv1.UpdateUserRequest{
|
|
User: &apiv1.User{
|
|
Name: apiv1server.BuildUserName(user.Username),
|
|
Password: "",
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"password"}},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "password must not be empty")
|
|
})
|
|
|
|
t.Run("UpdateUser rejects missing user message", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
user, err := ts.CreateRegularUser(ctx, "missing-message")
|
|
require.NoError(t, err)
|
|
|
|
authCtx := ts.CreateUserContext(ctx, user.ID)
|
|
_, err = ts.Service.UpdateUser(authCtx, &apiv1.UpdateUserRequest{
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"display_name"}},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "user is required")
|
|
})
|
|
|
|
t.Run("CreateUser concurrent first setup creates one admin", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
const workers = 12
|
|
var wg sync.WaitGroup
|
|
for i := range workers {
|
|
wg.Go(func() {
|
|
_, _ = ts.Service.CreateUser(ctx, &apiv1.CreateUserRequest{
|
|
User: &apiv1.User{
|
|
Username: fmt.Sprintf("setup-user-%d", i),
|
|
Email: "setup-user@example.com",
|
|
Password: "password123",
|
|
},
|
|
})
|
|
})
|
|
}
|
|
wg.Wait()
|
|
|
|
users, err := ts.Store.ListUsers(ctx, &store.FindUser{})
|
|
require.NoError(t, err)
|
|
adminCount := 0
|
|
for _, user := range users {
|
|
if user.Role == store.RoleAdmin {
|
|
adminCount++
|
|
}
|
|
}
|
|
require.Equal(t, 1, adminCount)
|
|
})
|
|
|
|
t.Run("UpdateUser state requires admin", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
user, err := ts.CreateRegularUser(ctx, "state-user")
|
|
require.NoError(t, err)
|
|
userCtx := ts.CreateUserContext(ctx, user.ID)
|
|
_, err = ts.Service.UpdateUser(userCtx, &apiv1.UpdateUserRequest{
|
|
User: &apiv1.User{
|
|
Name: apiv1server.BuildUserName(user.Username),
|
|
State: apiv1.State_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}},
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "permission denied")
|
|
|
|
admin, err := ts.CreateHostUser(ctx, "state-admin")
|
|
require.NoError(t, err)
|
|
adminCtx := ts.CreateUserContext(ctx, admin.ID)
|
|
updated, err := ts.Service.UpdateUser(adminCtx, &apiv1.UpdateUserRequest{
|
|
User: &apiv1.User{
|
|
Name: apiv1server.BuildUserName(user.Username),
|
|
State: apiv1.State_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, apiv1.State_ARCHIVED, updated.State)
|
|
})
|
|
|
|
t.Run("archived user context is rejected", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
user, err := ts.CreateRegularUser(ctx, "archived-access-user")
|
|
require.NoError(t, err)
|
|
userCtx := ts.CreateUserContext(ctx, user.ID)
|
|
archived := store.Archived
|
|
_, err = ts.Store.UpdateUser(ctx, &store.UpdateUser{
|
|
ID: user.ID,
|
|
RowStatus: &archived,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = ts.Service.GetCurrentUser(userCtx, &apiv1.GetCurrentUserRequest{})
|
|
require.Error(t, err)
|
|
|
|
_, err = ts.Service.CreateMemo(userCtx, &apiv1.CreateMemoRequest{
|
|
Memo: &apiv1.Memo{
|
|
Content: "should not be created",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
|
|
_, err = ts.Service.UpdateUser(userCtx, &apiv1.UpdateUserRequest{
|
|
User: &apiv1.User{
|
|
Name: apiv1server.BuildUserName(user.Username),
|
|
State: apiv1.State_NORMAL,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}},
|
|
})
|
|
require.Error(t, err)
|
|
})
|
|
}
|