fixed authorization (now checks the role inside of the project)
This commit is contained in:
+80
-10
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user