diff options
Diffstat (limited to 'internal/auth/interceptor.go')
| -rw-r--r-- | internal/auth/interceptor.go | 67 |
1 files changed, 55 insertions, 12 deletions
diff --git a/internal/auth/interceptor.go b/internal/auth/interceptor.go index 7d785bf..66880a7 100644 --- a/internal/auth/interceptor.go +++ b/internal/auth/interceptor.go | |||
| @@ -3,6 +3,7 @@ package auth | |||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | "fmt" | 5 | "fmt" |
| 6 | "strings" | ||
| 6 | 7 | ||
| 7 | "google.golang.org/grpc" | 8 | "google.golang.org/grpc" |
| 8 | "google.golang.org/grpc/codes" | 9 | "google.golang.org/grpc/codes" |
| @@ -35,11 +36,17 @@ type InterceptorOptions struct { | |||
| 35 | // Default: false | 36 | // Default: false |
| 36 | ValidatePayload bool | 37 | ValidatePayload bool |
| 37 | 38 | ||
| 38 | // AllowedNpubs is an optional whitelist of allowed pubkeys (hex format). | 39 | // AllowedNpubsRead is an optional whitelist of allowed pubkeys for read operations (hex format). |
| 39 | // Config accepts npub format only, normalized to hex at load time. | 40 | // Config accepts npub format only, normalized to hex at load time. |
| 40 | // If nil or empty, all valid signatures are accepted. | 41 | // If nil or empty, all valid signatures are accepted for reads. |
| 41 | // Default: nil (allow all) | 42 | // Default: nil (allow all) |
| 42 | AllowedNpubs []string | 43 | AllowedNpubsRead []string |
| 44 | |||
| 45 | // AllowedNpubsWrite is an optional whitelist of allowed pubkeys for write operations (hex format). | ||
| 46 | // Config accepts npub format only, normalized to hex at load time. | ||
| 47 | // If nil or empty, all valid signatures are accepted for writes. | ||
| 48 | // Default: nil (allow all) | ||
| 49 | AllowedNpubsWrite []string | ||
| 43 | 50 | ||
| 44 | // SkipMethods is a list of gRPC methods that bypass authentication. | 51 | // SkipMethods is a list of gRPC methods that bypass authentication. |
| 45 | // Useful for public endpoints like health checks or relay info. | 52 | // Useful for public endpoints like health checks or relay info. |
| @@ -51,11 +58,12 @@ type InterceptorOptions struct { | |||
| 51 | // DefaultInterceptorOptions returns the default configuration. | 58 | // DefaultInterceptorOptions returns the default configuration. |
| 52 | func DefaultInterceptorOptions() *InterceptorOptions { | 59 | func DefaultInterceptorOptions() *InterceptorOptions { |
| 53 | return &InterceptorOptions{ | 60 | return &InterceptorOptions{ |
| 54 | TimestampWindow: 60, | 61 | TimestampWindow: 60, |
| 55 | Required: false, | 62 | Required: false, |
| 56 | ValidatePayload: false, | 63 | ValidatePayload: false, |
| 57 | AllowedNpubs: nil, | 64 | AllowedNpubsRead: nil, |
| 58 | SkipMethods: nil, | 65 | AllowedNpubsWrite: nil, |
| 66 | SkipMethods: nil, | ||
| 59 | } | 67 | } |
| 60 | } | 68 | } |
| 61 | 69 | ||
| @@ -169,16 +177,51 @@ func validateAuthFromContext(ctx context.Context, method string, opts *Intercept | |||
| 169 | // Extract pubkey | 177 | // Extract pubkey |
| 170 | pubkey := ExtractPubkey(event) | 178 | pubkey := ExtractPubkey(event) |
| 171 | 179 | ||
| 172 | // Check whitelist if configured (all values are already normalized to hex) | 180 | // Check whitelist based on operation type (all values are already normalized to hex) |
| 173 | if len(opts.AllowedNpubs) > 0 { | 181 | if isWriteMethod(method) { |
| 174 | if !contains(opts.AllowedNpubs, pubkey) { | 182 | // Write operation - check write allowlist |
| 175 | return "", fmt.Errorf("pubkey not in whitelist") | 183 | if len(opts.AllowedNpubsWrite) > 0 { |
| 184 | if !contains(opts.AllowedNpubsWrite, pubkey) { | ||
| 185 | return "", fmt.Errorf("pubkey not authorized for write operations") | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } else { | ||
| 189 | // Read operation - check read allowlist | ||
| 190 | if len(opts.AllowedNpubsRead) > 0 { | ||
| 191 | if !contains(opts.AllowedNpubsRead, pubkey) { | ||
| 192 | return "", fmt.Errorf("pubkey not authorized for read operations") | ||
| 193 | } | ||
| 176 | } | 194 | } |
| 177 | } | 195 | } |
| 178 | 196 | ||
| 179 | return pubkey, nil | 197 | return pubkey, nil |
| 180 | } | 198 | } |
| 181 | 199 | ||
| 200 | // isWriteMethod determines if a gRPC method is a write operation. | ||
| 201 | // Write operations modify state (Publish, Delete, Create, Update, etc.) | ||
| 202 | // Read operations query state (Query, Get, List, Subscribe, etc.) | ||
| 203 | func isWriteMethod(method string) bool { | ||
| 204 | // Common write operation patterns | ||
| 205 | writePatterns := []string{ | ||
| 206 | "Publish", | ||
| 207 | "Delete", | ||
| 208 | "Create", | ||
| 209 | "Update", | ||
| 210 | "Insert", | ||
| 211 | "Remove", | ||
| 212 | "Set", | ||
| 213 | "Put", | ||
| 214 | } | ||
| 215 | |||
| 216 | for _, pattern := range writePatterns { | ||
| 217 | if strings.Contains(method, pattern) { | ||
| 218 | return true | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | return false | ||
| 223 | } | ||
| 224 | |||
| 182 | // shouldSkipAuth checks if a method should bypass authentication. | 225 | // shouldSkipAuth checks if a method should bypass authentication. |
| 183 | func shouldSkipAuth(method string, skipMethods []string) bool { | 226 | func shouldSkipAuth(method string, skipMethods []string) bool { |
| 184 | for _, skip := range skipMethods { | 227 | for _, skip := range skipMethods { |
