Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
01be01f4
Unverified
Commit
01be01f4
authored
Apr 19, 2026
by
boojack
Committed by
GitHub
Apr 19, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: mixed-case user resource names (#5853)
parent
1fd6a2a3
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
81 additions
and
12 deletions
+81
-12
user_resource_name_test.go
server/router/api/v1/test/user_resource_name_test.go
+56
-0
user_resource_name.go
server/router/api/v1/user_resource_name.go
+21
-7
user_service.go
server/router/api/v1/user_service.go
+4
-5
No files found.
server/router/api/v1/test/user_resource_name_test.go
View file @
01be01f4
...
@@ -44,6 +44,62 @@ func TestUserResourceName(t *testing.T) {
...
@@ -44,6 +44,62 @@ func TestUserResourceName(t *testing.T) {
require
.
Equal
(
t
,
"users/newuser"
,
created
.
Name
)
require
.
Equal
(
t
,
"users/newuser"
,
created
.
Name
)
})
})
t
.
Run
(
"Mixed-case username remains usable after auth"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"Gnammi"
)
require
.
NoError
(
t
,
err
)
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
ID
)
currentUser
,
err
:=
ts
.
Service
.
GetCurrentUser
(
userCtx
,
&
apiv1
.
GetCurrentUserRequest
{})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
currentUser
.
GetUser
())
require
.
Equal
(
t
,
"users/Gnammi"
,
currentUser
.
GetUser
()
.
Name
)
settings
,
err
:=
ts
.
Service
.
ListUserSettings
(
userCtx
,
&
apiv1
.
ListUserSettingsRequest
{
Parent
:
currentUser
.
GetUser
()
.
Name
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
settings
)
shortcuts
,
err
:=
ts
.
Service
.
ListShortcuts
(
userCtx
,
&
apiv1
.
ListShortcutsRequest
{
Parent
:
currentUser
.
GetUser
()
.
Name
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
shortcuts
)
})
t
.
Run
(
"BatchGetUsers preserves mixed-case usernames"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"Gnammi"
)
require
.
NoError
(
t
,
err
)
resp
,
err
:=
ts
.
Service
.
BatchGetUsers
(
ctx
,
&
apiv1
.
BatchGetUsersRequest
{
Usernames
:
[]
string
{
user
.
Username
},
})
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
resp
.
Users
,
1
)
require
.
Equal
(
t
,
"users/Gnammi"
,
resp
.
Users
[
0
]
.
Name
)
})
t
.
Run
(
"CreateUser rejects all-numeric usernames"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
_
,
err
:=
ts
.
Service
.
CreateUser
(
ctx
,
&
apiv1
.
CreateUserRequest
{
User
:
&
apiv1
.
User
{
Username
:
"123"
,
Email
:
"123@example.com"
,
Password
:
"password123"
,
},
})
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"invalid username"
)
})
t
.
Run
(
"GetUser rejects numeric user resource names"
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
"GetUser rejects numeric user resource names"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
defer
ts
.
Cleanup
()
...
...
server/router/api/v1/user_resource_name.go
View file @
01be01f4
...
@@ -2,8 +2,6 @@ package v1
...
@@ -2,8 +2,6 @@ package v1
import
(
import
(
"context"
"context"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/pkg/errors"
...
@@ -26,15 +24,31 @@ func ExtractUsernameFromName(name string) (string, error) {
...
@@ -26,15 +24,31 @@ func ExtractUsernameFromName(name string) (string, error) {
if
username
==
""
{
if
username
==
""
{
return
""
,
errors
.
Errorf
(
"invalid user name %q"
,
name
)
return
""
,
errors
.
Errorf
(
"invalid user name %q"
,
name
)
}
}
if
_
,
err
:=
strconv
.
ParseInt
(
username
,
10
,
32
);
err
==
nil
{
if
err
:=
validateUsername
(
username
);
err
!=
nil
{
return
""
,
errors
.
Errorf
(
"invalid username %q"
,
username
)
return
""
,
err
}
if
username
!=
strings
.
ToLower
(
username
)
||
!
base
.
UIDMatcher
.
MatchString
(
username
)
{
return
""
,
errors
.
Errorf
(
"invalid username %q"
,
username
)
}
}
return
username
,
nil
return
username
,
nil
}
}
func
validateUsername
(
username
string
)
error
{
if
username
==
""
||
isNumericUsername
(
username
)
||
!
base
.
UIDMatcher
.
MatchString
(
username
)
{
return
errors
.
Errorf
(
"invalid username %q"
,
username
)
}
return
nil
}
func
isNumericUsername
(
username
string
)
bool
{
if
username
==
""
{
return
false
}
for
_
,
char
:=
range
username
{
if
char
<
'0'
||
char
>
'9'
{
return
false
}
}
return
true
}
// ResolveUserByName resolves a username-based user resource name to a store user.
// ResolveUserByName resolves a username-based user resource name to a store user.
func
ResolveUserByName
(
ctx
context
.
Context
,
stores
*
store
.
Store
,
name
string
)
(
*
store
.
User
,
error
)
{
func
ResolveUserByName
(
ctx
context
.
Context
,
stores
*
store
.
Store
,
name
string
)
(
*
store
.
User
,
error
)
{
username
,
err
:=
ExtractUsernameFromName
(
name
)
username
,
err
:=
ExtractUsernameFromName
(
name
)
...
...
server/router/api/v1/user_service.go
View file @
01be01f4
...
@@ -20,7 +20,6 @@ import (
...
@@ -20,7 +20,6 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/usememos/memos/internal/base"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/internal/webhook"
"github.com/usememos/memos/internal/webhook"
v1pb
"github.com/usememos/memos/proto/gen/api/v1"
v1pb
"github.com/usememos/memos/proto/gen/api/v1"
...
@@ -76,8 +75,8 @@ func normalizeBatchUsernames(usernames []string) []string {
...
@@ -76,8 +75,8 @@ func normalizeBatchUsernames(usernames []string) []string {
uniqueUsernames
:=
make
([]
string
,
0
,
len
(
usernames
))
uniqueUsernames
:=
make
([]
string
,
0
,
len
(
usernames
))
seen
:=
make
(
map
[
string
]
struct
{},
len
(
usernames
))
seen
:=
make
(
map
[
string
]
struct
{},
len
(
usernames
))
for
_
,
username
:=
range
usernames
{
for
_
,
username
:=
range
usernames
{
username
=
strings
.
TrimSpace
(
strings
.
ToLower
(
username
)
)
username
=
strings
.
TrimSpace
(
username
)
if
username
==
""
||
!
base
.
UIDMatcher
.
MatchString
(
username
)
{
if
validateUsername
(
username
)
!=
nil
{
continue
continue
}
}
if
_
,
ok
:=
seen
[
username
];
ok
{
if
_
,
ok
:=
seen
[
username
];
ok
{
...
@@ -177,7 +176,7 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR
...
@@ -177,7 +176,7 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR
roleToAssign
=
store
.
RoleUser
roleToAssign
=
store
.
RoleUser
}
}
if
!
base
.
UIDMatcher
.
MatchString
(
strings
.
ToLower
(
request
.
User
.
Username
))
{
if
err
:=
validateUsername
(
request
.
User
.
Username
);
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid username: %s"
,
request
.
User
.
Username
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid username: %s"
,
request
.
User
.
Username
)
}
}
...
@@ -254,7 +253,7 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR
...
@@ -254,7 +253,7 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR
if
instanceGeneralSetting
.
DisallowChangeUsername
{
if
instanceGeneralSetting
.
DisallowChangeUsername
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"permission denied: disallow change username"
)
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"permission denied: disallow change username"
)
}
}
if
!
base
.
UIDMatcher
.
MatchString
(
strings
.
ToLower
(
request
.
User
.
Username
))
{
if
err
:=
validateUsername
(
request
.
User
.
Username
);
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid username: %s"
,
request
.
User
.
Username
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid username: %s"
,
request
.
User
.
Username
)
}
}
update
.
Username
=
&
request
.
User
.
Username
update
.
Username
=
&
request
.
User
.
Username
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment