summaryrefslogtreecommitdiffstats
path: root/internal/auth/credentials.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/auth/credentials.go')
-rw-r--r--internal/auth/credentials.go35
1 files changed, 3 insertions, 32 deletions
diff --git a/internal/auth/credentials.go b/internal/auth/credentials.go
index c558653..8e8aae8 100644
--- a/internal/auth/credentials.go
+++ b/internal/auth/credentials.go
@@ -8,20 +8,15 @@ import (
8 "fmt" 8 "fmt"
9 "time" 9 "time"
10 10
11 "northwest.io/muxstr/internal/nostr" 11 "northwest.io/nostr"
12) 12)
13 13
14// NostrCredentials implements credentials.PerRPCCredentials for NIP-98 authentication. 14// NostrCredentials implements credentials.PerRPCCredentials for NIP-98 auth.
15// It automatically signs each gRPC request with a Nostr event (kind 27235) and
16// attaches it to the Authorization header.
17type NostrCredentials struct { 15type NostrCredentials struct {
18 key *nostr.Key 16 key *nostr.Key
19 includePayload bool 17 includePayload bool
20} 18}
21 19
22// NewNostrCredentials creates credentials using the provided key.
23// Each RPC call will be authenticated with a freshly signed NIP-98 event.
24// The key must have a private key (CanSign() must return true).
25func NewNostrCredentials(key *nostr.Key) *NostrCredentials { 20func NewNostrCredentials(key *nostr.Key) *NostrCredentials {
26 return &NostrCredentials{ 21 return &NostrCredentials{
27 key: key, 22 key: key,
@@ -29,8 +24,6 @@ func NewNostrCredentials(key *nostr.Key) *NostrCredentials {
29 } 24 }
30} 25}
31 26
32// NewNostrCredentialsWithPayload creates credentials that include payload hashes.
33// When enabled, a SHA256 hash of the request body is included in the auth event.
34func NewNostrCredentialsWithPayload(key *nostr.Key) *NostrCredentials { 27func NewNostrCredentialsWithPayload(key *nostr.Key) *NostrCredentials {
35 return &NostrCredentials{ 28 return &NostrCredentials{
36 key: key, 29 key: key,
@@ -38,39 +31,26 @@ func NewNostrCredentialsWithPayload(key *nostr.Key) *NostrCredentials {
38 } 31 }
39} 32}
40 33
41// GetRequestMetadata implements credentials.PerRPCCredentials.
42// It creates and signs a NIP-98 auth event for each request.
43func (n *NostrCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 34func (n *NostrCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
44 if len(uri) == 0 { 35 if len(uri) == 0 {
45 return nil, fmt.Errorf("no URI provided") 36 return nil, fmt.Errorf("no URI provided")
46 } 37 }
47 38
48 // Create kind 27235 event (NIP-98 HTTP Auth)
49 event := &nostr.Event{ 39 event := &nostr.Event{
50 PubKey: n.key.Public(), 40 PubKey: n.key.Public(),
51 CreatedAt: time.Now().Unix(), 41 CreatedAt: time.Now().Unix(),
52 Kind: 27235, // NIP-98 HTTP Auth 42 Kind: 27235,
53 Tags: nostr.Tags{}, 43 Tags: nostr.Tags{},
54 Content: "", 44 Content: "",
55 } 45 }
56 46
57 // Add URL tag
58 event.Tags = append(event.Tags, nostr.Tag{"u", uri[0]}) 47 event.Tags = append(event.Tags, nostr.Tag{"u", uri[0]})
59
60 // Add method tag - default to POST for gRPC
61 // The URI contains the method name, e.g., /nostr.v1.NostrRelay/PublishEvent
62 event.Tags = append(event.Tags, nostr.Tag{"method", "POST"}) 48 event.Tags = append(event.Tags, nostr.Tag{"method", "POST"})
63 49
64 // TODO: Add payload hash if includePayload is true
65 // This requires access to the request body, which isn't available in GetRequestMetadata
66 // We could use a context key to pass the payload hash from the application
67
68 // Sign the event
69 if err := n.key.Sign(event); err != nil { 50 if err := n.key.Sign(event); err != nil {
70 return nil, fmt.Errorf("failed to sign auth event: %w", err) 51 return nil, fmt.Errorf("failed to sign auth event: %w", err)
71 } 52 }
72 53
73 // Encode event as base64 JSON
74 eventJSON, err := json.Marshal(event) 54 eventJSON, err := json.Marshal(event)
75 if err != nil { 55 if err != nil {
76 return nil, fmt.Errorf("failed to marshal auth event: %w", err) 56 return nil, fmt.Errorf("failed to marshal auth event: %w", err)
@@ -83,33 +63,24 @@ func (n *NostrCredentials) GetRequestMetadata(ctx context.Context, uri ...string
83 }, nil 63 }, nil
84} 64}
85 65
86// RequireTransportSecurity implements credentials.PerRPCCredentials.
87// Returns false to allow usage over insecure connections (for development).
88// In production, use TLS and set this to true.
89func (n *NostrCredentials) RequireTransportSecurity() bool { 66func (n *NostrCredentials) RequireTransportSecurity() bool {
90 return false 67 return false
91} 68}
92 69
93// SetRequireTLS configures whether TLS is required.
94// When true, the credentials will only work over TLS connections.
95type NostrCredentialsWithTLS struct { 70type NostrCredentialsWithTLS struct {
96 *NostrCredentials 71 *NostrCredentials
97} 72}
98 73
99// NewNostrCredentialsWithTLS creates credentials that require TLS.
100func NewNostrCredentialsWithTLS(key *nostr.Key) *NostrCredentialsWithTLS { 74func NewNostrCredentialsWithTLS(key *nostr.Key) *NostrCredentialsWithTLS {
101 return &NostrCredentialsWithTLS{ 75 return &NostrCredentialsWithTLS{
102 NostrCredentials: NewNostrCredentials(key), 76 NostrCredentials: NewNostrCredentials(key),
103 } 77 }
104} 78}
105 79
106// RequireTransportSecurity returns true to enforce TLS.
107func (n *NostrCredentialsWithTLS) RequireTransportSecurity() bool { 80func (n *NostrCredentialsWithTLS) RequireTransportSecurity() bool {
108 return true 81 return true
109} 82}
110 83
111// HashPayload creates a SHA256 hash of the payload for inclusion in auth events.
112// This can be used to verify request integrity.
113func HashPayload(payload []byte) string { 84func HashPayload(payload []byte) string {
114 hash := sha256.Sum256(payload) 85 hash := sha256.Sum256(payload)
115 return fmt.Sprintf("%x", hash) 86 return fmt.Sprintf("%x", hash)