mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
@@ -0,0 +1,323 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/ucan"
|
||||
)
|
||||
|
||||
// UCANDelegator handles UCAN token generation for OAuth flows
|
||||
type UCANDelegator struct {
|
||||
scopeMapper *ScopeMapper
|
||||
signer UCANSigner
|
||||
}
|
||||
|
||||
// UCANSigner interface for signing UCAN tokens
|
||||
type UCANSigner interface {
|
||||
Sign(token *ucan.Token) (string, error)
|
||||
GetIssuerDID() string
|
||||
}
|
||||
|
||||
// NewUCANDelegator creates a new UCAN delegator
|
||||
func NewUCANDelegator(signer UCANSigner) *UCANDelegator {
|
||||
if signer == nil {
|
||||
// Use blockchain signer by default
|
||||
signer, _ = NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
|
||||
}
|
||||
return &UCANDelegator{
|
||||
scopeMapper: NewScopeMapper(),
|
||||
signer: signer,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDelegation creates a UCAN token for user-to-client delegation
|
||||
func (d *UCANDelegator) CreateDelegation(
|
||||
userDID string,
|
||||
clientID string,
|
||||
scopes []string,
|
||||
expiresAt time.Time,
|
||||
) (*ucan.Token, error) {
|
||||
// Build resource context for the user
|
||||
resourceContext := d.buildResourceContext(userDID)
|
||||
|
||||
// Map OAuth scopes to UCAN attenuations
|
||||
attenuations := d.scopeMapper.MapToUCAN(scopes, userDID, clientID, resourceContext)
|
||||
if len(attenuations) == 0 {
|
||||
return nil, fmt.Errorf("no valid attenuations for scopes: %v", scopes)
|
||||
}
|
||||
|
||||
// Create UCAN token
|
||||
token := &ucan.Token{
|
||||
Issuer: userDID,
|
||||
Audience: clientID,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
NotBefore: time.Now().Unix(),
|
||||
Attenuations: attenuations,
|
||||
Facts: []ucan.Fact{
|
||||
{
|
||||
Data: d.createOAuthFact(scopes, "user_delegation"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Sign the token
|
||||
signedToken, err := d.signToken(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign UCAN token: %w", err)
|
||||
}
|
||||
|
||||
token.Raw = signedToken
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// CreateServiceDelegation creates a UCAN token for service-to-service delegation
|
||||
func (d *UCANDelegator) CreateServiceDelegation(
|
||||
clientID string,
|
||||
scopes []string,
|
||||
expiresAt time.Time,
|
||||
) (*ucan.Token, error) {
|
||||
// Service delegations use the OAuth provider as issuer
|
||||
issuerDID := d.signer.GetIssuerDID()
|
||||
|
||||
// Build resource context for service
|
||||
resourceContext := map[string]string{
|
||||
"service_id": clientID,
|
||||
"type": "service",
|
||||
}
|
||||
|
||||
// Map OAuth scopes to UCAN attenuations
|
||||
attenuations := d.scopeMapper.MapToUCAN(scopes, issuerDID, clientID, resourceContext)
|
||||
if len(attenuations) == 0 {
|
||||
return nil, fmt.Errorf("no valid attenuations for scopes: %v", scopes)
|
||||
}
|
||||
|
||||
// Create UCAN token
|
||||
token := &ucan.Token{
|
||||
Issuer: issuerDID,
|
||||
Audience: clientID,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
NotBefore: time.Now().Unix(),
|
||||
Attenuations: attenuations,
|
||||
Facts: []ucan.Fact{
|
||||
{
|
||||
Data: d.createOAuthFact(scopes, "service_delegation"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Sign the token
|
||||
signedToken, err := d.signToken(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign UCAN token: %w", err)
|
||||
}
|
||||
|
||||
token.Raw = signedToken
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// CreateDelegationChain creates a chain of UCAN tokens for complex delegations
|
||||
func (d *UCANDelegator) CreateDelegationChain(
|
||||
userDID string,
|
||||
intermediaries []string,
|
||||
finalAudience string,
|
||||
scopes []string,
|
||||
expiresAt time.Time,
|
||||
) ([]*ucan.Token, error) {
|
||||
chain := make([]*ucan.Token, 0, len(intermediaries)+1)
|
||||
|
||||
currentIssuer := userDID
|
||||
proofs := []ucan.Proof{}
|
||||
|
||||
// Create delegation for each intermediary
|
||||
for _, intermediary := range intermediaries {
|
||||
token, err := d.createIntermediateDelegation(
|
||||
currentIssuer,
|
||||
intermediary,
|
||||
scopes,
|
||||
expiresAt,
|
||||
proofs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create intermediate delegation: %w", err)
|
||||
}
|
||||
|
||||
chain = append(chain, token)
|
||||
proofs = append(proofs, ucan.Proof(token.Raw))
|
||||
currentIssuer = intermediary
|
||||
}
|
||||
|
||||
// Create final delegation
|
||||
finalToken, err := d.createFinalDelegation(
|
||||
currentIssuer,
|
||||
finalAudience,
|
||||
scopes,
|
||||
expiresAt,
|
||||
proofs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create final delegation: %w", err)
|
||||
}
|
||||
|
||||
chain = append(chain, finalToken)
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// RevokeDelegation revokes a UCAN delegation
|
||||
func (d *UCANDelegator) RevokeDelegation(tokenID string) error {
|
||||
// TODO: Implement delegation revocation
|
||||
// This would typically involve:
|
||||
// 1. Adding the token to a revocation list
|
||||
// 2. Publishing revocation to a public ledger or database
|
||||
// 3. Notifying relevant parties
|
||||
return fmt.Errorf("delegation revocation not yet implemented")
|
||||
}
|
||||
|
||||
// ValidateDelegation validates a UCAN token delegation
|
||||
func (d *UCANDelegator) ValidateDelegation(token *ucan.Token, requiredScopes []string) error {
|
||||
// Check expiration
|
||||
if time.Now().Unix() > token.ExpiresAt {
|
||||
return fmt.Errorf("token expired")
|
||||
}
|
||||
|
||||
// Check not before
|
||||
if token.NotBefore > 0 && time.Now().Unix() < token.NotBefore {
|
||||
return fmt.Errorf("token not yet valid")
|
||||
}
|
||||
|
||||
// Verify the token has required capabilities
|
||||
for _, scope := range requiredScopes {
|
||||
if !d.tokenGrantsScope(token, scope) {
|
||||
return fmt.Errorf("token does not grant required scope: %s", scope)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Verify signature
|
||||
// TODO: Verify delegation chain if proofs exist
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
|
||||
func (d *UCANDelegator) buildResourceContext(userDID string) map[string]string {
|
||||
// TODO: Fetch actual resource information for the user
|
||||
return map[string]string{
|
||||
"vault_address": fmt.Sprintf("vault:%s", userDID),
|
||||
"enclave_cid": fmt.Sprintf("cid:%s", userDID),
|
||||
"dwn_id": fmt.Sprintf("dwn:%s", userDID),
|
||||
"service_id": fmt.Sprintf("service:%s", userDID),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *UCANDelegator) createOAuthFact(scopes []string, delegationType string) json.RawMessage {
|
||||
fact := map[string]any{
|
||||
"oauth_scopes": scopes,
|
||||
"delegation_type": delegationType,
|
||||
"issued_at": time.Now().Unix(),
|
||||
"oauth_version": "2.0",
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(fact)
|
||||
return json.RawMessage(data)
|
||||
}
|
||||
|
||||
func (d *UCANDelegator) signToken(token *ucan.Token) (string, error) {
|
||||
if d.signer == nil {
|
||||
return "", fmt.Errorf("no signer configured")
|
||||
}
|
||||
|
||||
return d.signer.Sign(token)
|
||||
}
|
||||
|
||||
func (d *UCANDelegator) createIntermediateDelegation(
|
||||
issuer, audience string,
|
||||
scopes []string,
|
||||
expiresAt time.Time,
|
||||
proofs []ucan.Proof,
|
||||
) (*ucan.Token, error) {
|
||||
return d.createDelegationWithType(
|
||||
issuer,
|
||||
audience,
|
||||
scopes,
|
||||
expiresAt,
|
||||
proofs,
|
||||
"intermediate_delegation",
|
||||
)
|
||||
}
|
||||
|
||||
func (d *UCANDelegator) createFinalDelegation(
|
||||
issuer, audience string,
|
||||
scopes []string,
|
||||
expiresAt time.Time,
|
||||
proofs []ucan.Proof,
|
||||
) (*ucan.Token, error) {
|
||||
return d.createDelegationWithType(
|
||||
issuer,
|
||||
audience,
|
||||
scopes,
|
||||
expiresAt,
|
||||
proofs,
|
||||
"final_delegation",
|
||||
)
|
||||
}
|
||||
|
||||
// createDelegationWithType creates a delegation token with a specific type
|
||||
func (d *UCANDelegator) createDelegationWithType(
|
||||
issuer, audience string,
|
||||
scopes []string,
|
||||
expiresAt time.Time,
|
||||
proofs []ucan.Proof,
|
||||
delegationType string,
|
||||
) (*ucan.Token, error) {
|
||||
resourceContext := d.buildResourceContext(issuer)
|
||||
attenuations := d.scopeMapper.MapToUCAN(scopes, issuer, audience, resourceContext)
|
||||
|
||||
token := &ucan.Token{
|
||||
Issuer: issuer,
|
||||
Audience: audience,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
NotBefore: time.Now().Unix(),
|
||||
Attenuations: attenuations,
|
||||
Proofs: proofs,
|
||||
Facts: []ucan.Fact{
|
||||
{
|
||||
Data: d.createOAuthFact(scopes, delegationType),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
signedToken, err := d.signToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token.Raw = signedToken
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (d *UCANDelegator) tokenGrantsScope(token *ucan.Token, scope string) bool {
|
||||
// Get the scope definition
|
||||
scopeDef, exists := d.scopeMapper.GetScope(scope)
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if any attenuation grants the required capabilities
|
||||
for _, attenuation := range token.Attenuations {
|
||||
if attenuation.Capability.Grants(scopeDef.UCANActions) {
|
||||
// Also check resource type matches
|
||||
if d.resourceMatches(attenuation.Resource, scopeDef.ResourceType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *UCANDelegator) resourceMatches(resource ucan.Resource, resourceType string) bool {
|
||||
// Simple scheme matching for now
|
||||
return resource.GetScheme() == resourceType || resource.GetScheme() == "*"
|
||||
}
|
||||
Reference in New Issue
Block a user