Skip to content

Commit 0898f4e

Browse files
authored
feat: configure jwt user claims (#409)
- user email can now be added as part of the jwt claim, it's added by default - group users can be listed using the list group api by passing an additional flag, `with_members` as true - project groups can be returned with roles via `with_roles` Signed-off-by: Kush Sharma <[email protected]>
1 parent d292964 commit 0898f4e

37 files changed

+5528
-4291
lines changed

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
44
VERSION := $(shell git describe --tags ${TAG})
55
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto ui
66
.DEFAULT_GOAL := build
7-
PROTON_COMMIT := "a643dfe2bec324941bab05cec4061324a0f6b643"
7+
PROTON_COMMIT := "eb24a09e48f7f746b07006daae47f9434ef70ec4"
88

99
ui:
1010
@echo " > generating ui build"
@@ -32,17 +32,17 @@ lint-fix:
3232
test: ## Run tests
3333
@go test -race $(shell go list ./... | grep -v /ui | grep -v /vendor/ | grep -v /test/) -coverprofile=coverage.out -count 2 -timeout 150s
3434

35-
test-all: lint test e2e-smoke-test e2e-regression-test ## Run all tests
35+
test-all: lint test e2e-test ## Run all tests
3636

3737
e2e-test: ## Run all e2e tests
3838
## run `docker network prune` if docker fails to find non-overlapping ipv4 address pool
39-
go test -v -race ./test/e2e/...
39+
go test -v ./test/e2e/...
4040

4141
e2e-smoke-test: ## Run smoke tests
42-
go test -v -race ./test/e2e/smoke
42+
go test -v ./test/e2e/smoke
4343

4444
e2e-regression-test: ## Run regression tests
45-
go test -v -race ./test/e2e/regression
45+
go test -v ./test/e2e/regression
4646

4747
benchmark: ## Run benchmarks
4848
go test -run=XX -bench=Benchmark. -count 3 -benchtime=1s github.com/raystack/frontier/test/integration

config/sample.config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ app:
7777
iss: "http://localhost.frontier"
7878
# validity of the token
7979
validity: "1h"
80+
# custom claims configuration for the jwt
81+
claims:
82+
# if set to true, the jwt will contain the org ids of the user in the claim
83+
add_org_ids: true
84+
# if set to true, the jwt will contain the user email in the claim
85+
add_user_email: true
8086
# Public facing host used for oidc redirect uri and mail link redirection
8187
# after user credentials are verified.
8288
# If frontier is exposed behind a proxy, this should set as proxy endpoint

core/authenticate/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ type TokenConfig struct {
3131

3232
// Validity is the duration for which the token is valid
3333
Validity time.Duration `yaml:"validity" mapstructure:"validity" default:"1h"`
34+
35+
Claims TokenClaimConfig `yaml:"claims" mapstructure:"claims"`
36+
}
37+
38+
type TokenClaimConfig struct {
39+
AddOrgIDsClaim bool `yaml:"add_org_ids" mapstructure:"add_org_ids" default:"true"`
40+
AddUserEmailClaim bool `yaml:"add_user_email" mapstructure:"add_user_email" default:"true"`
3441
}
3542

3643
type SessionConfig struct {

core/authenticate/token/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var (
2020
const (
2121
GeneratedClaimKey = "gen"
2222
GeneratedClaimValue = "system"
23+
OrgIDsClaimKey = "org_ids"
2324
)
2425

2526
type Service struct {

core/group/filter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ type Filter struct {
55

66
OrganizationID string
77
State State
8+
9+
GroupIDs []string
810
}

core/policy/service.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package policy
33
import (
44
"context"
55

6+
"github.com/raystack/frontier/pkg/utils"
7+
68
"github.com/raystack/frontier/core/role"
79

810
"github.com/raystack/frontier/core/relation"
@@ -16,6 +18,7 @@ type RelationService interface {
1618

1719
type RoleService interface {
1820
Get(ctx context.Context, id string) (role.Role, error)
21+
List(ctx context.Context, f role.Filter) ([]role.Role, error)
1922
}
2023

2124
type Service struct {
@@ -127,11 +130,11 @@ func (s Service) AssignRole(ctx context.Context, pol Policy) error {
127130
return nil
128131
}
129132

130-
// ListForUser lists roles assigned via policies to a user
131-
func (s Service) ListForUser(ctx context.Context, userID, objectNamespace, objectID string) ([]role.Role, error) {
133+
// ListRoles lists roles assigned via policies to a user
134+
func (s Service) ListRoles(ctx context.Context, principalType, principalID, objectNamespace, objectID string) ([]role.Role, error) {
132135
flt := Filter{
133-
PrincipalType: schema.UserPrincipal,
134-
PrincipalID: userID,
136+
PrincipalType: principalType,
137+
PrincipalID: principalID,
135138
}
136139
switch objectNamespace {
137140
case schema.OrganizationNamespace:
@@ -146,13 +149,10 @@ func (s Service) ListForUser(ctx context.Context, userID, objectNamespace, objec
146149
return nil, err
147150
}
148151

149-
roles := make([]role.Role, 0, len(policies))
150-
for _, pol := range policies {
151-
role, err := s.roleService.Get(ctx, pol.RoleID)
152-
if err != nil {
153-
return nil, err
154-
}
155-
roles = append(roles, role)
156-
}
157-
return roles, nil
152+
roleIDs := utils.Map(policies, func(p Policy) string {
153+
return p.RoleID
154+
})
155+
return s.roleService.List(ctx, role.Filter{
156+
IDs: roleIDs,
157+
})
158158
}

core/role/filter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ package role
33
type Filter struct {
44
OrgID string
55
Scopes []string
6+
IDs []string
67
}

docs/docs/apis/admin-service-list-groups.api.mdx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,61 @@ api:
5050
"example": "2023-06-07T05:39:56.961Z",
5151
"description": "The time the group was last updated.",
5252
},
53+
"users":
54+
{
55+
"type": "array",
56+
"items":
57+
{
58+
"type": "object",
59+
"properties":
60+
{
61+
"id": { "type": "string" },
62+
"name":
63+
{
64+
"type": "string",
65+
"example": "johndoe",
66+
"description": "Unique name of the user.",
67+
"title": "can either be empty or must start with a character",
68+
},
69+
"title":
70+
{
71+
"type": "string",
72+
"example": "John Doe",
73+
"description": "Name of the user.",
74+
},
75+
"email":
76+
{ "type": "string" },
77+
"metadata":
78+
{ "type": "object" },
79+
"createdAt":
80+
{
81+
"type": "string",
82+
"format": "date-time",
83+
"example": "2023-06-07T05:39:56.961Z",
84+
"description": "The time the user was created.",
85+
},
86+
"updatedAt":
87+
{
88+
"type": "string",
89+
"format": "date-time",
90+
"example": "2023-06-07T05:39:56.961Z",
91+
"description": "The time the user was last updated.",
92+
},
93+
"state":
94+
{
95+
"type": "string",
96+
"example": "enabled",
97+
"description": "The state of the user (enabled or disabled).",
98+
},
99+
"avatar":
100+
{
101+
"type": "string",
102+
"description": "The base64 encoded image string of the user avatar. Should be less than 2MB.",
103+
},
104+
},
105+
},
106+
"readOnly": true,
107+
},
53108
},
54109
},
55110
},
@@ -359,7 +414,7 @@ Lists all the groups from all the organizations in a Frontier instance. It can b
359414

360415
A successful response.
361416

362-
</div><div><MimeTabs schemaType={"response"}><TabItem label={"application/json"} value={"application/json"}><SchemaTabs><TabItem label={"Schema"} value={"Schema"}><details style={{}} data-collapsed={false} open={true}><summary style={{"textAlign":"left"}}><strong>Schema</strong></summary><div style={{"textAlign":"left","marginLeft":"1rem"}}></div><ul style={{"marginLeft":"1rem"}}><SchemaItem collapsible={true} className={"schemaItem"}><details style={{}}><summary style={{}}><strong>groups</strong><span style={{"opacity":"0.6"}}> object[]</span></summary><div style={{"marginLeft":"1rem"}}><li><div style={{"fontSize":"var(--ifm-code-font-size)","opacity":"0.6","marginLeft":"-.5rem","paddingBottom":".5rem"}}>Array [</div></li><SchemaItem collapsible={false} name={"id"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"name"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"title"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"orgId"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"metadata"} required={false} schemaName={"object"} qualifierMessage={undefined} schema={{"type":"object"}}></SchemaItem><SchemaItem collapsible={false} name={"createdAt"} required={false} schemaName={"date-time"} qualifierMessage={undefined} schema={{"type":"string","format":"date-time","example":"2023-06-07T05:39:56.961Z","description":"The time the group was created."}}></SchemaItem><SchemaItem collapsible={false} name={"updatedAt"} required={false} schemaName={"date-time"} qualifierMessage={undefined} schema={{"type":"string","format":"date-time","example":"2023-06-07T05:39:56.961Z","description":"The time the group was last updated."}}></SchemaItem><li><div style={{"fontSize":"var(--ifm-code-font-size)","opacity":"0.6","marginLeft":"-.5rem"}}>]</div></li></div></details></SchemaItem></ul></details></TabItem><TabItem label={"Example (from schema)"} value={"Example (from schema)"}><ResponseSamples responseExample={"{\n \"groups\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"title\": \"string\",\n \"orgId\": \"string\",\n \"metadata\": {},\n \"createdAt\": \"2023-06-07T05:39:56.961Z\",\n \"updatedAt\": \"2023-06-07T05:39:56.961Z\"\n }\n ]\n}"} language={"json"}></ResponseSamples></TabItem></SchemaTabs></TabItem></MimeTabs></div></TabItem><TabItem label={"400"} value={"400"}><div>
417+
</div><div><MimeTabs schemaType={"response"}><TabItem label={"application/json"} value={"application/json"}><SchemaTabs><TabItem label={"Schema"} value={"Schema"}><details style={{}} data-collapsed={false} open={true}><summary style={{"textAlign":"left"}}><strong>Schema</strong></summary><div style={{"textAlign":"left","marginLeft":"1rem"}}></div><ul style={{"marginLeft":"1rem"}}><SchemaItem collapsible={true} className={"schemaItem"}><details style={{}}><summary style={{}}><strong>groups</strong><span style={{"opacity":"0.6"}}> object[]</span></summary><div style={{"marginLeft":"1rem"}}><li><div style={{"fontSize":"var(--ifm-code-font-size)","opacity":"0.6","marginLeft":"-.5rem","paddingBottom":".5rem"}}>Array [</div></li><SchemaItem collapsible={false} name={"id"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"name"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"title"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"orgId"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"metadata"} required={false} schemaName={"object"} qualifierMessage={undefined} schema={{"type":"object"}}></SchemaItem><SchemaItem collapsible={false} name={"createdAt"} required={false} schemaName={"date-time"} qualifierMessage={undefined} schema={{"type":"string","format":"date-time","example":"2023-06-07T05:39:56.961Z","description":"The time the group was created."}}></SchemaItem><SchemaItem collapsible={false} name={"updatedAt"} required={false} schemaName={"date-time"} qualifierMessage={undefined} schema={{"type":"string","format":"date-time","example":"2023-06-07T05:39:56.961Z","description":"The time the group was last updated."}}></SchemaItem><SchemaItem collapsible={true} className={"schemaItem"}><details style={{}}><summary style={{}}><strong>users</strong><span style={{"opacity":"0.6"}}> object[]</span></summary><div style={{"marginLeft":"1rem"}}><li><div style={{"fontSize":"var(--ifm-code-font-size)","opacity":"0.6","marginLeft":"-.5rem","paddingBottom":".5rem"}}>Array [</div></li><SchemaItem collapsible={false} name={"id"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"name"} required={false} schemaName={"can either be empty or must start with a character"} qualifierMessage={undefined} schema={{"type":"string","example":"johndoe","description":"Unique name of the user.","title":"can either be empty or must start with a character"}}></SchemaItem><SchemaItem collapsible={false} name={"title"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string","example":"John Doe","description":"Name of the user."}}></SchemaItem><SchemaItem collapsible={false} name={"email"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string"}}></SchemaItem><SchemaItem collapsible={false} name={"metadata"} required={false} schemaName={"object"} qualifierMessage={undefined} schema={{"type":"object"}}></SchemaItem><SchemaItem collapsible={false} name={"createdAt"} required={false} schemaName={"date-time"} qualifierMessage={undefined} schema={{"type":"string","format":"date-time","example":"2023-06-07T05:39:56.961Z","description":"The time the user was created."}}></SchemaItem><SchemaItem collapsible={false} name={"updatedAt"} required={false} schemaName={"date-time"} qualifierMessage={undefined} schema={{"type":"string","format":"date-time","example":"2023-06-07T05:39:56.961Z","description":"The time the user was last updated."}}></SchemaItem><SchemaItem collapsible={false} name={"state"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string","example":"enabled","description":"The state of the user (enabled or disabled)."}}></SchemaItem><SchemaItem collapsible={false} name={"avatar"} required={false} schemaName={"string"} qualifierMessage={undefined} schema={{"type":"string","description":"The base64 encoded image string of the user avatar. Should be less than 2MB."}}></SchemaItem><li><div style={{"fontSize":"var(--ifm-code-font-size)","opacity":"0.6","marginLeft":"-.5rem"}}>]</div></li></div></details></SchemaItem><li><div style={{"fontSize":"var(--ifm-code-font-size)","opacity":"0.6","marginLeft":"-.5rem"}}>]</div></li></div></details></SchemaItem></ul></details></TabItem><TabItem label={"Example (from schema)"} value={"Example (from schema)"}><ResponseSamples responseExample={"{\n \"groups\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"title\": \"string\",\n \"orgId\": \"string\",\n \"metadata\": {},\n \"createdAt\": \"2023-06-07T05:39:56.961Z\",\n \"updatedAt\": \"2023-06-07T05:39:56.961Z\",\n \"users\": [\n {\n \"id\": \"string\",\n \"name\": \"johndoe\",\n \"title\": \"John Doe\",\n \"email\": \"string\",\n \"metadata\": {},\n \"createdAt\": \"2023-06-07T05:39:56.961Z\",\n \"updatedAt\": \"2023-06-07T05:39:56.961Z\",\n \"state\": \"enabled\",\n \"avatar\": \"string\"\n }\n ]\n }\n ]\n}"} language={"json"}></ResponseSamples></TabItem></SchemaTabs></TabItem></MimeTabs></div></TabItem><TabItem label={"400"} value={"400"}><div>
363418

364419
Bad Request - The request was malformed or contained invalid parameters.
365420

0 commit comments

Comments
 (0)