fixed authorization (now checks the role inside of the project)

This commit is contained in:
2026-03-02 13:46:14 +08:00
parent e32a4a2779
commit 8ca995d490
6 changed files with 253 additions and 16 deletions
+80 -10
View File
@@ -28,6 +28,26 @@ type ProjectClaim struct {
OfficeID int `json:"office_id,omitempty"`
}
func (p *ProjectClaim) UnmarshalJSON(data []byte) error {
type Alias ProjectClaim
aux := &struct {
RoleIDs RoleIDs `json:"role_ids"`
*Alias
}{
Alias: (*Alias)(p),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
if len(p.RoleID) == 0 && len(aux.RoleIDs) > 0 {
p.RoleID = aux.RoleIDs
}
return nil
}
// RoleIDs represents one or more role IDs.
// It is defined as a custom type so we can implement flexible JSON unmarshalling
// that accepts a single string ("1"), a single number (1), or an array ([1,2,...]).
@@ -92,28 +112,78 @@ type Claims struct {
// UnmarshalJSON handles both "user_id" and "users_id" field names in JWT claims
func (c *Claims) UnmarshalJSON(data []byte) error {
type Alias Claims
aux := &struct {
UserID string `json:"user_id"`
Email string `json:"email"`
*Alias
}{
Alias: (*Alias)(c),
}
aux := struct {
UsersID string `json:"users_id"`
UserID string `json:"user_id"`
EmailAddress string `json:"email_address"`
Email string `json:"email"`
RoleID RoleIDs `json:"role_id"`
AdditionalRoleID RoleIDs `json:"additional_role_id"`
Projects json.RawMessage `json:"projects"`
ProjectMetadata json.RawMessage `json:"project_metadata"`
ProjectsMetadata json.RawMessage `json:"projects_metadata"`
jwt.RegisteredClaims
}{}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
// If UsersID is empty but UserID is set, copy UserID to UsersID
c.UsersID = aux.UsersID
c.EmailAddress = aux.EmailAddress
c.RoleID = aux.RoleID
c.AdditionalRoleID = aux.AdditionalRoleID
c.RegisteredClaims = aux.RegisteredClaims
if c.UsersID == "" && aux.UserID != "" {
c.UsersID = aux.UserID
}
// If EmailAddress is empty but Email is set, copy Email to EmailAddress
if c.EmailAddress == "" && aux.Email != "" {
c.EmailAddress = aux.Email
}
projects := make([]ProjectClaim, 0)
rawProjectFields := []json.RawMessage{aux.Projects, aux.ProjectMetadata, aux.ProjectsMetadata}
for _, raw := range rawProjectFields {
parsedProjects, err := parseProjectClaims(raw)
if err != nil {
return err
}
if len(parsedProjects) > 0 {
projects = append(projects, parsedProjects...)
}
}
c.Projects = projects
return nil
}
func parseProjectClaims(raw json.RawMessage) ([]ProjectClaim, error) {
trimmed := bytes.TrimSpace(raw)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
return nil, nil
}
switch trimmed[0] {
case '[':
var projects []ProjectClaim
if err := json.Unmarshal(trimmed, &projects); err != nil {
return nil, err
}
return projects, nil
case '{':
var project ProjectClaim
if err := json.Unmarshal(trimmed, &project); err != nil {
return nil, err
}
return []ProjectClaim{project}, nil
default:
return nil, fmt.Errorf("unsupported JSON for projects claim: %s", string(trimmed))
}
}
// ContextKey is a custom type for context keys to avoid collisions
type ContextKey string
+68
View File
@@ -0,0 +1,68 @@
package models
import (
"encoding/json"
"testing"
)
func TestClaimsUnmarshal_ProjectMetadataRoleIDs(t *testing.T) {
payload := `{
"users_id":"U0000000003",
"email_address":"user@example.com",
"role_id":[30],
"project_metadata":[
{"project_id":101,"alias":"proj-a","role_ids":[44,52]}
]
}`
var claims Claims
if err := json.Unmarshal([]byte(payload), &claims); err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(claims.RoleID) != 1 || claims.RoleID[0] != 30 {
t.Fatalf("unexpected base role: %v", claims.RoleID)
}
if len(claims.Projects) != 1 {
t.Fatalf("expected 1 project, got %d", len(claims.Projects))
}
if claims.Projects[0].ProjectID != 101 {
t.Fatalf("expected project_id=101, got %d", claims.Projects[0].ProjectID)
}
if len(claims.Projects[0].RoleID) != 2 || claims.Projects[0].RoleID[0] != 44 || claims.Projects[0].RoleID[1] != 52 {
t.Fatalf("unexpected project role ids: %v", claims.Projects[0].RoleID)
}
}
func TestClaimsUnmarshal_ProjectsMetadataSingleObject(t *testing.T) {
payload := `{
"user_id":"U0000000003",
"email":"user@example.com",
"role_id":"30",
"projects_metadata":{"project_id":202,"alias":"proj-b","role_id":"61"}
}`
var claims Claims
if err := json.Unmarshal([]byte(payload), &claims); err != nil {
t.Fatalf("expected no error, got %v", err)
}
if claims.UsersID != "U0000000003" {
t.Fatalf("expected users_id fallback from user_id, got %s", claims.UsersID)
}
if claims.EmailAddress != "user@example.com" {
t.Fatalf("expected email fallback from email, got %s", claims.EmailAddress)
}
if len(claims.Projects) != 1 {
t.Fatalf("expected 1 project from projects_metadata object, got %d", len(claims.Projects))
}
if len(claims.Projects[0].RoleID) != 1 || claims.Projects[0].RoleID[0] != 61 {
t.Fatalf("unexpected project role ids: %v", claims.Projects[0].RoleID)
}
}