tinyrelayfile
tinyrelay/internal/daemon/private_grasp.go/

internal/daemon/private_grasp.go raw
1package daemon
2
3import (
4 "context"
5 "errors"
6 "net/http"
7
8 "github.com/FelineStateMachine/tinyrelay/internal/event"
9 "github.com/FelineStateMachine/tinyrelay/internal/policy"
10)
11
12type privateGitProofKey struct{}
13type privateGitPayloadCheckedKey struct{}
14
15func privateGitRequest(r *http.Request, proof event.Event) *http.Request {
16 ctx := context.WithValue(r.Context(), privateGitProofKey{}, proof)
17 ctx = context.WithValue(ctx, privateGitPayloadCheckedKey{}, struct{}{})
18 return r.WithContext(ctx)
19}
20
21func privateGitProof(r *http.Request) (event.Event, bool) {
22 proof, ok := r.Context().Value(privateGitProofKey{}).(event.Event)
23 return proof, ok
24}
25
26// PrivateServiceProfile describes a GRASP-08 tenant's configured protocol,
27// authentication methods, read policy and owner identity. Protocol adapters
28// choose the fields appropriate to the request's authenticated audience.
29type PrivateServiceProfile struct {
30 Enabled bool
31 Protocol string
32 Authentication []string
33 Reads string
34 Owner string
35}
36
37// PrivateAccess records tenant-level authority resolved from current community
38// membership. Owner, member and moderator roles grant private-service access.
39type PrivateAccess struct {
40 PubKey string
41 Role string
42 Member bool
43 Owner bool
44}
45
46func (a PrivateAccess) Allowed() bool { return a.Member || a.Owner }
47
48// PrivateServiceEnabled reports whether this tenant has the complete private
49// service contract enabled. Callers should use PrivateAccess before exposing
50// any tenant or repository metadata.
51func (t *Tenant) PrivateServiceEnabled() bool {
52 return t != nil && t.Policy().PrivateServiceEnabled()
53}
54
55func (t *Tenant) PrivateProfile() PrivateServiceProfile {
56 if t == nil {
57 return PrivateServiceProfile{}
58 }
59 p := t.Policy()
60 return PrivateServiceProfile{
61 Enabled: p.PrivateServiceEnabled(),
62 Protocol: "GRASP-08",
63 Authentication: []string{"NIP-42", "NIP-98"},
64 Reads: p.Reads,
65 Owner: p.Owner,
66 }
67}
68
69// PrivateAccess reads current membership and bans for each request, so access
70// changes apply to the next authorization check.
71func (t *Tenant) PrivateAccess(ctx context.Context, pubkey string) (PrivateAccess, error) {
72 access := PrivateAccess{PubKey: pubkey}
73 if t == nil || !t.PrivateServiceEnabled() {
74 return access, nil
75 }
76 if pubkey == "" {
77 return access, errors.New("auth-required: private service requires NIP-42 or NIP-98")
78 }
79 banned, err := t.community.IsBanned(ctx, pubkey)
80 if err != nil {
81 return access, err
82 }
83 if banned {
84 return access, errors.New("blocked: private service identity is banned")
85 }
86 role, err := t.community.Role(ctx, pubkey)
87 if err != nil {
88 return access, err
89 }
90 access.Role = role
91 access.Owner = role == "owner" || pubkey == t.Policy().Owner
92 access.Member = role == "member" || role == "moderator"
93 if !access.Allowed() {
94 return access, errors.New("restricted: private service membership required")
95 }
96 return access, nil
97}
98
99func (t *Tenant) requirePrivateAccess(ctx context.Context, pubkey string) error {
100 _, err := t.PrivateAccess(ctx, pubkey)
101 return err
102}
103
104// AuthorizePrivatePeer is the narrow boundary for outbound Git/event sync.
105// Callers must invoke it immediately before using a private peer credential;
106// a previously successful check must never be reused after membership changes.
107func (t *Tenant) AuthorizePrivatePeer(ctx context.Context, pubkey string) error {
108 if !t.PrivateServiceEnabled() {
109 return errors.New("restricted: private peer access is disabled")
110 }
111 return t.requirePrivateAccess(ctx, pubkey)
112}
113
114// privateNIP11 returns the metadata safe to expose before authentication.
115// The advertised owner key is the service identity needed by NIP-42 clients;
116// tenant-specific descriptive fields remain intentionally omitted.
117func (t *Tenant) privateNIP11() map[string]any {
118 profile := t.PrivateProfile()
119 return map[string]any{
120 "name": "private relay",
121 "description": "authenticated private relay",
122 "pubkey": profile.Owner,
123 "supported_grasps": []string{"GRASP-01", "GRASP-08"},
124 "private_service": true,
125 }
126}
127
128func privatePolicy(p policy.Policy) bool {
129 return p.PrivateServiceEnabled()
130}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs