mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-24 02:29:05 +02:00
feat: add OAuth APIs with scoped permissions (#1542)
Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
co-authored by
Alessandro Segala
parent
0b2706a488
commit
09d196f7c5
@@ -2,6 +2,7 @@ package oidc
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ory/fosite"
|
||||
@@ -10,12 +11,14 @@ import (
|
||||
type tokenHandler struct {
|
||||
provider fosite.OAuth2Provider
|
||||
claimsService *ClaimsService
|
||||
apiAccess APIAccessProvider
|
||||
}
|
||||
|
||||
func newTokenHandler(provider fosite.OAuth2Provider, claimsService *ClaimsService) *tokenHandler {
|
||||
func newTokenHandler(provider fosite.OAuth2Provider, claimsService *ClaimsService, apiAccess APIAccessProvider) *tokenHandler {
|
||||
return &tokenHandler{
|
||||
provider: provider,
|
||||
claimsService: claimsService,
|
||||
apiAccess: apiAccess,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,17 +45,41 @@ func (h *tokenHandler) token(c *gin.Context) {
|
||||
|
||||
if client, ok := accessRequest.GetClient().(Client); ok {
|
||||
// Re-validate the resource owner on every user-bound grant.
|
||||
if err := h.claimsService.ValidateUserAccess(ctx, requestSession.Subject, client); err != nil {
|
||||
err := h.claimsService.ValidateUserAccess(ctx, requestSession.Subject, client)
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "Rejected token request: user no longer allowed to access client", "error", err.Error())
|
||||
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Bind every issued JWT access token to the requesting client so it always carries an aud claim.
|
||||
accessRequest.GrantAudience(client.GetID())
|
||||
// The client credentials grant has no authorize step so the RFC 8707 resource is resolved here to stamp the API audience and limit the granted scope to what the client is allowed for that API
|
||||
// It resolves against the client-subject grants: a permission delegated by users does not let the client act as itself
|
||||
// The other grants had their audience and scope resolved at authorize or device time and restored from storage, so they must be left untouched
|
||||
if accessRequest.GetGrantTypes().Has(string(fosite.GrantTypeClientCredentials)) {
|
||||
resource, err := accessRequest.GetResource()
|
||||
if err != nil {
|
||||
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
|
||||
return
|
||||
}
|
||||
audience, grantedScopes, err := resolveResource(ctx, h.apiAccess, client.GetID(), resource, accessRequest.GetRequestedScopes(), SubjectTypeClient)
|
||||
if err != nil {
|
||||
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
|
||||
return
|
||||
}
|
||||
// A client credentials token has no resource owner, so it must never carry identity scopes such as openid or profile
|
||||
// Dropping them keeps machine tokens out of the userinfo endpoint, which is gated on the openid scope
|
||||
grantedScopes = slices.DeleteFunc(grantedScopes, isStandardScope)
|
||||
accessReq, ok := accessRequest.(*fosite.AccessRequest)
|
||||
if ok {
|
||||
accessReq.GrantedScope = grantedScopes
|
||||
accessReq.GrantedAudience = nil
|
||||
}
|
||||
accessRequest.GrantResourceIndicator(audience, grantedScopes)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.claimsService.applyIDTokenClaims(ctx, requestSession, accessRequest.GetGrantedScopes()); err != nil {
|
||||
err = h.claimsService.applyIDTokenClaims(ctx, requestSession, accessRequest.GetGrantedScopes())
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Failed to apply ID token claims", "error", err)
|
||||
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
|
||||
return
|
||||
@@ -61,7 +88,8 @@ func (h *tokenHandler) token(c *gin.Context) {
|
||||
// The client credentials grant has no resource owner, so no subject is ever set. Assign a
|
||||
// stable synthetic subject so the issued JWT access token still carries a subclaim.
|
||||
if requestSession.Subject == "" {
|
||||
if client, ok := accessRequest.GetClient().(Client); ok && accessRequest.GetGrantTypes().Has(string(fosite.GrantTypeClientCredentials)) {
|
||||
client, ok := accessRequest.GetClient().(Client)
|
||||
if ok && accessRequest.GetGrantTypes().Has(string(fosite.GrantTypeClientCredentials)) {
|
||||
requestSession.Subject = "client-" + client.GetID()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user