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.go116
1 files changed, 116 insertions, 0 deletions
diff --git a/internal/auth/credentials.go b/internal/auth/credentials.go
new file mode 100644
index 0000000..c558653
--- /dev/null
+++ b/internal/auth/credentials.go
@@ -0,0 +1,116 @@
1package auth
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/base64"
7 "encoding/json"
8 "fmt"
9 "time"
10
11 "northwest.io/muxstr/internal/nostr"
12)
13
14// NostrCredentials implements credentials.PerRPCCredentials for NIP-98 authentication.
15// It automatically signs each gRPC request with a Nostr event (kind 27235) and
16// attaches it to the Authorization header.
17type NostrCredentials struct {
18 key *nostr.Key
19 includePayload bool
20}
21
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 {
26 return &NostrCredentials{
27 key: key,
28 includePayload: false,
29 }
30}
31
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 {
35 return &NostrCredentials{
36 key: key,
37 includePayload: true,
38 }
39}
40
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) {
44 if len(uri) == 0 {
45 return nil, fmt.Errorf("no URI provided")
46 }
47
48 // Create kind 27235 event (NIP-98 HTTP Auth)
49 event := &nostr.Event{
50 PubKey: n.key.Public(),
51 CreatedAt: time.Now().Unix(),
52 Kind: 27235, // NIP-98 HTTP Auth
53 Tags: nostr.Tags{},
54 Content: "",
55 }
56
57 // Add URL tag
58 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"})
63
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 {
70 return nil, fmt.Errorf("failed to sign auth event: %w", err)
71 }
72
73 // Encode event as base64 JSON
74 eventJSON, err := json.Marshal(event)
75 if err != nil {
76 return nil, fmt.Errorf("failed to marshal auth event: %w", err)
77 }
78
79 authHeader := "Nostr " + base64.StdEncoding.EncodeToString(eventJSON)
80
81 return map[string]string{
82 "authorization": authHeader,
83 }, nil
84}
85
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 {
90 return false
91}
92
93// SetRequireTLS configures whether TLS is required.
94// When true, the credentials will only work over TLS connections.
95type NostrCredentialsWithTLS struct {
96 *NostrCredentials
97}
98
99// NewNostrCredentialsWithTLS creates credentials that require TLS.
100func NewNostrCredentialsWithTLS(key *nostr.Key) *NostrCredentialsWithTLS {
101 return &NostrCredentialsWithTLS{
102 NostrCredentials: NewNostrCredentials(key),
103 }
104}
105
106// RequireTransportSecurity returns true to enforce TLS.
107func (n *NostrCredentialsWithTLS) RequireTransportSecurity() bool {
108 return true
109}
110
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 {
114 hash := sha256.Sum256(payload)
115 return fmt.Sprintf("%x", hash)
116}