You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
2.9 KiB
101 lines
2.9 KiB
// Package bunker implements NIP-46 remote signing with Cashu token authentication. |
|
package bunker |
|
|
|
import ( |
|
"context" |
|
|
|
"next.orly.dev/pkg/acl" |
|
acliface "next.orly.dev/pkg/interfaces/acl" |
|
cashuiface "next.orly.dev/pkg/interfaces/cashu" |
|
"next.orly.dev/pkg/cashu/token" |
|
) |
|
|
|
// ACLAuthzChecker adapts ORLY's ACL system to cashu.AuthzChecker. |
|
// This allows the Cashu token system to use the existing ACL for authorization. |
|
type ACLAuthzChecker struct { |
|
// ScopeRequirements maps scopes to required access levels. |
|
// If not set, defaults are used. |
|
ScopeRequirements map[string]string |
|
} |
|
|
|
// NewACLAuthzChecker creates a new ACL-based authorization checker. |
|
func NewACLAuthzChecker() *ACLAuthzChecker { |
|
return &ACLAuthzChecker{ |
|
ScopeRequirements: map[string]string{ |
|
token.ScopeRelay: acliface.Write, // Relay access requires write |
|
token.ScopeNIP46: acliface.Write, // Bunker access requires write |
|
token.ScopeBlossom: acliface.Write, // Blossom access requires write |
|
token.ScopeAPI: acliface.Admin, // API access requires admin |
|
token.ScopeNRC: acliface.Write, // NRC tunnel access requires write |
|
}, |
|
} |
|
} |
|
|
|
// CheckAuthorization checks if a pubkey is authorized for a scope. |
|
func (a *ACLAuthzChecker) CheckAuthorization(ctx context.Context, pubkey []byte, scope string, remoteAddr string) error { |
|
// Get access level from ACL registry |
|
level := acl.Registry.GetAccessLevel(pubkey, remoteAddr) |
|
|
|
// Check against required level for scope |
|
requiredLevel, ok := a.ScopeRequirements[scope] |
|
if !ok { |
|
// Default to write access for unknown scopes |
|
requiredLevel = acliface.Write |
|
} |
|
|
|
if !hasAccessLevel(level, requiredLevel) { |
|
return cashuiface.NewAuthzError( |
|
cashuiface.ErrCodeInsufficientAccess, |
|
"insufficient access level for scope "+scope, |
|
) |
|
} |
|
|
|
// Check for banned/blocked status |
|
if level == "banned" { |
|
return cashuiface.ErrBanned |
|
} |
|
if level == "blocked" { |
|
return cashuiface.ErrBlocked |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// ReauthorizationEnabled returns true - we always re-check ACL on each verification. |
|
func (a *ACLAuthzChecker) ReauthorizationEnabled() bool { |
|
return true |
|
} |
|
|
|
// hasAccessLevel checks if the actual level meets or exceeds the required level. |
|
func hasAccessLevel(actual, required string) bool { |
|
levels := map[string]int{ |
|
acliface.None: 0, |
|
"banned": 0, |
|
"blocked": 0, |
|
acliface.Read: 1, |
|
acliface.Write: 2, |
|
acliface.Admin: 3, |
|
acliface.Owner: 4, |
|
} |
|
|
|
actualLevel, aok := levels[actual] |
|
requiredLevel, rok := levels[required] |
|
|
|
if !aok || !rok { |
|
return false |
|
} |
|
|
|
return actualLevel >= requiredLevel |
|
} |
|
|
|
// SetScopeRequirement sets the required access level for a scope. |
|
func (a *ACLAuthzChecker) SetScopeRequirement(scope, level string) { |
|
if a.ScopeRequirements == nil { |
|
a.ScopeRequirements = make(map[string]string) |
|
} |
|
a.ScopeRequirements[scope] = level |
|
} |
|
|
|
// Ensure ACLAuthzChecker implements both interfaces. |
|
var _ cashuiface.AuthzChecker = (*ACLAuthzChecker)(nil) |
|
var _ cashuiface.ReauthorizationChecker = (*ACLAuthzChecker)(nil)
|
|
|