Skip to content

Commit ab05967

Browse files
authored
feat: enhance access token management with options for resource and organization (#167)
1 parent 732f6a0 commit ab05967

File tree

2 files changed

+86
-34
lines changed

2 files changed

+86
-34
lines changed

client/client.go

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ type AccessToken struct {
1515
ExpiresAt int64 `json:"expiresAt"`
1616
}
1717

18+
// GetAccessTokenOptions contains parameters for retrieving an access token.
19+
// Use Resource to specify the API resource, and OrganizationId to specify the organization context.
20+
// Both fields are optional; leave them empty if not needed.
21+
type GetAccessTokenOptions struct {
22+
Resource string
23+
OrganizationId string
24+
}
25+
26+
// GetOrganizationTokenClaimsOptions contains parameters for retrieving organization access token claims.
27+
// Use Resource to specify the API resource, and OrganizationId to specify the organization whose claims you want to retrieve.
28+
// OrganizationId is required.
29+
type GetOrganizationTokenClaimsOptions struct {
30+
Resource string
31+
OrganizationId string
32+
}
33+
1834
type LogtoClient struct {
1935
httpClient *http.Client
2036
logtoConfig *LogtoConfig
@@ -63,46 +79,32 @@ func (logtoClient *LogtoClient) GetIdTokenClaims() (core.IdTokenClaims, error) {
6379
return core.DecodeIdToken(logtoClient.GetIdToken())
6480
}
6581

66-
func (logtoClient *LogtoClient) GetOrganizationTokenClaims(organizationId string) (core.OrganizationAccessTokenClaims, error) {
67-
token, getTokenErr := logtoClient.GetOrganizationToken(organizationId)
68-
69-
if getTokenErr != nil {
70-
return core.OrganizationAccessTokenClaims{}, getTokenErr
71-
}
72-
73-
jwtObject, parseTokenErr := core.ParseSignedJwt(token.Token)
74-
75-
if parseTokenErr != nil {
76-
return core.OrganizationAccessTokenClaims{}, parseTokenErr
77-
}
78-
79-
var claims core.OrganizationAccessTokenClaims
80-
claimsErr := jwtObject.UnsafeClaimsWithoutVerification(&claims)
81-
82-
if claimsErr != nil {
83-
return core.OrganizationAccessTokenClaims{}, claimsErr
84-
}
85-
86-
return claims, claimsErr
87-
}
88-
8982
func (logtoClient *LogtoClient) SaveAccessToken(key string, accessToken AccessToken) {
9083
logtoClient.accessTokenMap[key] = accessToken
9184
logtoClient.persistAccessTokenMap()
9285
}
9386

94-
func (logtoClient *LogtoClient) getAccessToken(resource string, organizationId string) (AccessToken, error) {
87+
// GetAccessTokenWithOptions retrieves an access token for the specified resource and/or organization.
88+
// Use GetAccessToken for resource-only tokens, or GetOrganizationToken for organization-only tokens.
89+
// This method provides the most flexibility and is recommended for advanced scenarios.
90+
func (logtoClient *LogtoClient) GetAccessTokenWithOptions(options GetAccessTokenOptions) (AccessToken, error) {
9591
if !logtoClient.IsAuthenticated() {
9692
return AccessToken{}, ErrNotAuthenticated
9793
}
9894

99-
if resource != "" {
100-
if !slices.Contains(logtoClient.logtoConfig.Resources, resource) {
95+
if options.Resource != "" {
96+
if !slices.Contains(logtoClient.logtoConfig.Resources, options.Resource) {
10197
return AccessToken{}, ErrUnacknowledgedResourceFound
10298
}
10399
}
104100

105-
accessTokenKey := buildAccessTokenKey([]string{}, resource, organizationId)
101+
if options.OrganizationId != "" {
102+
if !slices.Contains(logtoClient.logtoConfig.Scopes, core.UserScopeOrganizations) {
103+
return AccessToken{}, ErrMissingScopeOrganizations
104+
}
105+
}
106+
107+
accessTokenKey := buildAccessTokenKey([]string{}, options.Resource, options.OrganizationId)
106108
if accessToken, ok := logtoClient.accessTokenMap[accessTokenKey]; ok {
107109
if accessToken.ExpiresAt > time.Now().Unix() {
108110
return accessToken, nil
@@ -126,9 +128,9 @@ func (logtoClient *LogtoClient) getAccessToken(resource string, organizationId s
126128
ClientId: logtoClient.logtoConfig.AppId,
127129
ClientSecret: logtoClient.logtoConfig.AppSecret,
128130
RefreshToken: refreshToken,
129-
Resource: resource,
131+
Resource: options.Resource,
130132
Scopes: []string{},
131-
OrganizationId: organizationId,
133+
OrganizationId: options.OrganizationId,
132134
})
133135

134136
if refreshTokenErr != nil {
@@ -156,16 +158,65 @@ func (logtoClient *LogtoClient) getAccessToken(resource string, organizationId s
156158
return refreshedAccessToken, nil
157159
}
158160

161+
// GetOrganizationTokenClaimsWithOptions retrieves the claims from an organization access token
162+
// for the specified resource and organization. OrganizationId is required.
163+
// Use GetOrganizationTokenClaims for organization-only claims.
164+
// This method is recommended for advanced scenarios where both resource and organization context are needed.
165+
func (logtoClient *LogtoClient) GetOrganizationTokenClaimsWithOptions(options GetOrganizationTokenClaimsOptions) (core.OrganizationAccessTokenClaims, error) {
166+
if options.OrganizationId == "" {
167+
return core.OrganizationAccessTokenClaims{}, ErrMissingOrganizationId
168+
}
169+
170+
token, getTokenErr := logtoClient.GetAccessTokenWithOptions(GetAccessTokenOptions{
171+
Resource: options.Resource,
172+
OrganizationId: options.OrganizationId,
173+
})
174+
175+
if getTokenErr != nil {
176+
return core.OrganizationAccessTokenClaims{}, getTokenErr
177+
}
178+
179+
jwtObject, parseTokenErr := core.ParseSignedJwt(token.Token)
180+
181+
if parseTokenErr != nil {
182+
return core.OrganizationAccessTokenClaims{}, parseTokenErr
183+
}
184+
185+
var claims core.OrganizationAccessTokenClaims
186+
claimsErr := jwtObject.UnsafeClaimsWithoutVerification(&claims)
187+
188+
if claimsErr != nil {
189+
return core.OrganizationAccessTokenClaims{}, claimsErr
190+
}
191+
192+
return claims, claimsErr
193+
}
194+
195+
// GetAccessToken retrieves an access token for the specified resource only.
196+
// This method does not support organization-based access.
197+
// If you need to specify an organization, use GetAccessTokenWithOptions instead.
159198
func (logtoClient *LogtoClient) GetAccessToken(resource string) (AccessToken, error) {
160-
return logtoClient.getAccessToken(resource, "")
199+
return logtoClient.GetAccessTokenWithOptions(GetAccessTokenOptions{
200+
Resource: resource,
201+
})
161202
}
162203

204+
// GetOrganizationToken retrieves an access token for the specified organization only.
205+
// This method does not support resource-based access.
206+
// If you need to specify a resource, use GetAccessTokenWithOptions instead.
163207
func (logtoClient *LogtoClient) GetOrganizationToken(organizationId string) (AccessToken, error) {
164-
if !slices.Contains(logtoClient.logtoConfig.Scopes, core.UserScopeOrganizations) {
165-
return AccessToken{}, ErrMissingScopeOrganizations
166-
}
208+
return logtoClient.GetAccessTokenWithOptions(GetAccessTokenOptions{
209+
OrganizationId: organizationId,
210+
})
211+
}
167212

168-
return logtoClient.getAccessToken("", organizationId)
213+
// GetOrganizationTokenClaims retrieves the claims from an organization access token for the specified organization only.
214+
// This method does not support resource-based claims retrieval.
215+
// If you need to specify a resource, use GetOrganizationTokenClaimsWithOptions instead.
216+
func (logtoClient *LogtoClient) GetOrganizationTokenClaims(organizationId string) (core.OrganizationAccessTokenClaims, error) {
217+
return logtoClient.GetOrganizationTokenClaimsWithOptions(GetOrganizationTokenClaimsOptions{
218+
OrganizationId: organizationId,
219+
})
169220
}
170221

171222
func (logtoClient *LogtoClient) FetchUserInfo() (core.UserInfoResponse, error) {

client/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ var (
66
ErrNotAuthenticated = errors.New("not authenticated")
77
ErrUnacknowledgedResourceFound = errors.New("unacknowledged resource found")
88
ErrMissingScopeOrganizations = errors.New("missing 'urn:logto:scope:organizations' scope")
9+
ErrMissingOrganizationId = errors.New("missing organization id")
910
)

0 commit comments

Comments
 (0)