upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt
diff options
context:
space:
mode:
Diffstat (limited to 'docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt')
-rw-r--r--docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt299
1 files changed, 0 insertions, 299 deletions
diff --git a/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt b/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt
deleted file mode 100644
index 10fb529..0000000
--- a/docs/archive/2025-11-04-evening/2025-11-04-authorization-flow-diagram.txt
+++ /dev/null
@@ -1,299 +0,0 @@
1╔═══════════════════════════════════════════════════════════════════════════════╗
2║ GIT PUSH AUTHORIZATION FLOW (INLINE) ║
3╚═══════════════════════════════════════════════════════════════════════════════╝
4
5┌─────────────────────────────────────────────────────────────────────────────┐
6│ CLIENT: git push │
7└────────────────────────────────┬────────────────────────────────────────────┘
8
9 │ HTTP POST
10 │ /npub/repo.git/git-receive-pack
11
12┌─────────────────────────────────────────────────────────────────────────────┐
13│ ACTIX-WEB ROUTER (git-http-backend) │
14│ │
15│ Route: /{namespace}/{repo}/git-receive-pack │
16│ Handler: git_receive_pack() │
17└────────────────────────────────┬────────────────────────────────────────────┘
18
19
20┌─────────────────────────────────────────────────────────────────────────────┐
21│ STEP 1: RESOLVE REPOSITORY PATH │
22│ │
23│ GitConfig::rewrite("/npub/repo") → /data/git/npub/repo.git │
24└────────────────────────────────┬────────────────────────────────────────────┘
25
26
27┌─────────────────────────────────────────────────────────────────────────────┐
28│ STEP 2: VALIDATE REPOSITORY EXISTS │
29│ │
30│ ✓ Check HEAD exists │
31│ ✓ Check config exists │
32│ ✓ Check bare = true │
33│ │
34│ ❌ If not: Return 400 "Repository not found" │
35└────────────────────────────────┬────────────────────────────────────────────┘
36
37
38┌─────────────────────────────────────────────────────────────────────────────┐
39│ STEP 3: READ REQUEST BODY (MODIFIED) │
40│ │
41│ • Read full request body into memory │
42│ • Decode gzip if Content-Encoding: gzip │
43│ • Store in body_data: Vec<u8> │
44└────────────────────────────────┬────────────────────────────────────────────┘
45
46
47┌─────────────────────────────────────────────────────────────────────────────┐
48│ STEP 4: PARSE REF UPDATES (NEW!) │
49│ │
50│ parse_receive_pack_request(&body_data) → Vec<RefUpdate> │
51│ │
52│ Git Pack Protocol: │
53│ ┌──────────────────────────────────────────────────────────────┐ │
54│ │ 0000000000000000000000000000000000000000 a1b2c3d4e5f6... │ │
55│ │ refs/heads/main\0 report-status\n │ │
56│ │ │ │
57│ │ old_oid: 0000... (new branch) │ │
58│ │ new_oid: a1b2c3d4e5f6... │ │
59│ │ ref_name: refs/heads/main │ │
60│ └──────────────────────────────────────────────────────────────┘ │
61│ │
62│ Result: RefUpdate { │
63│ old_oid: "0000...", │
64│ new_oid: "a1b2c3d4e5f6...", │
65│ ref_name: "refs/heads/main" │
66│ } │
67└────────────────────────────────┬────────────────────────────────────────────┘
68
69
70┌─────────────────────────────────────────────────────────────────────────────┐
71│ STEP 5: VALIDATE AUTHORIZATION (NEW!) │
72│ │
73│ validator.validate_push(npub, identifier, &ref_updates).await │
74│ │
75│ ┌────────────────────────────────────────────────────────────┐ │
76│ │ PushValidator::validate_push() │ │
77│ │ │ │
78│ │ 1. Get latest state event from Nostr relay │ │
79│ │ • Query: kind=30618, d=identifier, author=npub │ │
80│ │ • Extract refs from state event │ │
81│ │ │ │
82│ │ 2. For each ref update: │ │
83│ │ • If refs/heads/* or refs/tags/*: │ │
84│ │ - Check state event has matching ref │ │
85│ │ - Check new_oid matches state event oid │ │
86│ │ - ❌ Reject if mismatch │ │
87│ │ • If refs/nostr/*: │ │
88│ │ - ✅ Always allow (PRs) │ │
89│ │ │ │
90│ │ 3. Get maintainers (recursive) │ │
91│ │ • Extract maintainers from announcement │ │
92│ │ • Recursively resolve maintainer sets │ │
93│ │ • Check if pusher is in maintainer list │ │
94│ │ • ❌ Reject if not maintainer │ │
95│ │ │ │
96│ │ 4. Return Ok(()) or Err(message) │ │
97│ └────────────────────────────────────────────────────────────┘ │
98│ │
99│ ❌ If validation fails: │
100│ Return 403 Forbidden │
101│ { │
102│ "error": "unauthorized", │
103│ "message": "Push rejected: refs/heads/main points to ..., state has..."│
104│ } │
105└────────────────────────────────┬────────────────────────────────────────────┘
106
107 │ ✅ AUTHORIZED
108
109┌─────────────────────────────────────────────────────────────────────────────┐
110│ STEP 6: SPAWN GIT RECEIVE-PACK (EXISTING) │
111│ │
112│ Command::new("git") │
113│ .arg("receive-pack") │
114│ .arg("--stateless-rpc") │
115│ .arg(".") │
116│ .current_dir(&repo_path) │
117│ .spawn() │
118│ │
119│ • Write body_data to git stdin │
120│ • Stream git stdout back to client │
121└────────────────────────────────┬────────────────────────────────────────────┘
122
123 │ Stream response
124
125┌─────────────────────────────────────────────────────────────────────────────┐
126│ CLIENT: git push success │
127└─────────────────────────────────────────────────────────────────────────────┘
128
129
130╔═══════════════════════════════════════════════════════════════════════════════╗
131║ COMPARISON: BEFORE vs AFTER ║
132╚═══════════════════════════════════════════════════════════════════════════════╝
133
134┌─────────────────────────────────┬─────────────────────────────────────────┐
135│ BEFORE (git-http-backend) │ AFTER (our fork) │
136├─────────────────────────────────┼─────────────────────────────────────────┤
137│ 1. Resolve path │ 1. Resolve path │
138│ 2. Check bare repo │ 2. Check bare repo │
139│ 3. Read request body │ 3. Read request body │
140│ 4. Spawn git immediately ❌ │ 4. Parse ref updates ← NEW │
141│ 5. Stream response │ 5. Validate authorization ← NEW │
142│ │ 6. Spawn git (if authorized) │
143│ │ 7. Stream response │
144└─────────────────────────────────┴─────────────────────────────────────────┘
145
146┌─────────────────────────────────┬─────────────────────────────────────────┐
147│ AUTHORIZATION │ METHOD │
148├─────────────────────────────────┼─────────────────────────────────────────┤
149│ ❌ None │ No validation │
150│ ⚠️ Git hooks (pre-receive) │ After git accepts push │
151│ ✅ Inline (our approach) │ Before git touches repository │
152└─────────────────────────────────┴─────────────────────────────────────────┘
153
154
155╔═══════════════════════════════════════════════════════════════════════════════╗
156║ KEY MODIFICATIONS NEEDED ║
157╚═══════════════════════════════════════════════════════════════════════════════╝
158
159┌─────────────────────────────────────────────────────────────────────────────┐
160│ FILE: src/actix/git_receive_pack.rs │
161├─────────────────────────────────────────────────────────────────────────────┤
162│ │
163│ CHANGE 1: Add validator parameter │
164│ ──────────────────────────────────────────────────────────────────────── │
165│ pub async fn git_receive_pack( │
166│ request: HttpRequest, │
167│ mut payload: Payload, │
168│ service: web::Data<impl GitConfig>, │
169│ + validator: web::Data<PushValidator>, // ← ADD THIS │
170│ ) -> impl Responder { │
171│ │
172│ CHANGE 2: Parse ref updates after reading body │
173│ ──────────────────────────────────────────────────────────────────────── │
174│ // Read and decode body (existing) │
175│ let body_data = read_and_decode_body(&mut payload, &request).await?; │
176│ │
177│ + // Parse ref updates (NEW) │
178│ + let ref_updates = parse_receive_pack_request(&body_data)?; │
179│ │
180│ CHANGE 3: Validate before spawning git │
181│ ──────────────────────────────────────────────────────────────────────── │
182│ + // Extract repo info from path │
183│ + let (npub, identifier) = extract_repo_info(&request.uri().path())?; │
184│ + │
185│ + // Validate authorization │
186│ + if let Err(e) = validator.validate_push(&npub, &identifier, │
187│ + &ref_updates).await { │
188│ + return HttpResponse::Forbidden() │
189│ + .json(json!({ │
190│ + "error": "unauthorized", │
191│ + "message": e.to_string(), │
192│ + })); │
193│ + } │
194│ │
195│ // Spawn git (existing, unchanged) │
196│ let mut cmd = Command::new("git"); │
197│ // ... rest of existing code ... │
198└─────────────────────────────────────────────────────────────────────────────┘
199
200┌─────────────────────────────────────────────────────────────────────────────┐
201│ NEW FILE: src/git/protocol.rs │
202├─────────────────────────────────────────────────────────────────────────────┤
203│ │
204│ pub struct RefUpdate { │
205│ pub old_oid: String, │
206│ pub new_oid: String, │
207│ pub ref_name: String, │
208│ } │
209│ │
210│ pub fn parse_receive_pack_request(body: &[u8]) -> Result<Vec<RefUpdate>> { │
211│ // Parse git pack protocol │
212│ // Extract ref updates from pkt-line format │
213│ } │
214└─────────────────────────────────────────────────────────────────────────────┘
215
216┌─────────────────────────────────────────────────────────────────────────────┐
217│ NEW FILE: src/git/authorization.rs │
218├─────────────────────────────────────────────────────────────────────────────┤
219│ │
220│ pub struct PushValidator { │
221│ storage: Storage, │
222│ } │
223│ │
224│ impl PushValidator { │
225│ pub async fn validate_push( │
226│ &self, │
227│ npub: &str, │
228│ identifier: &str, │
229│ updates: &[RefUpdate], │
230│ ) -> Result<()> { │
231│ // Query Nostr relay for state event │
232│ // Validate each ref update │
233│ // Check maintainer permissions │
234│ } │
235│ } │
236└─────────────────────────────────────────────────────────────────────────────┘
237
238
239╔═══════════════════════════════════════════════════════════════════════════════╗
240║ BENEFITS OF INLINE AUTH ║
241╚═══════════════════════════════════════════════════════════════════════════════╝
242
243✅ BETTER ERROR MESSAGES
244 • Return 403 with JSON error details
245 • Show exactly which ref failed validation
246 • Show expected vs. actual commit
247 • Better developer experience
248
249✅ SIMPLER DEPLOYMENT
250 • No git hooks to manage
251 • No symlinks or hook installation
252 • Single binary handles everything
253 • Easier to test
254
255✅ TIGHTER INTEGRATION
256 • Direct access to Nostr relay state
257 • Shared storage layer
258 • No IPC between components
259 • Atomic validation
260
261✅ EASIER TESTING
262 • Pure Rust unit tests
263 • Mock validator for testing
264 • No subprocess coordination
265 • Deterministic behavior
266
267✅ SECURITY
268 • Validation before git touches repo
269 • Can't bypass by manipulating hooks
270 • Centralized authorization logic
271 • Audit trail in application logs
272
273
274╔═══════════════════════════════════════════════════════════════════════════════╗
275║ TIMELINE ║
276╚═══════════════════════════════════════════════════════════════════════════════╝
277
278Week 1: Foundation
279├─ Day 1-2: Fork git-http-backend, set up integration
280├─ Day 3-4: Add git2, implement GitRepository
281└─ Day 5: Add protocol parsing module
282
283Week 2: Authorization
284├─ Day 1-2: Implement PushValidator
285├─ Day 3-4: Modify git_receive_pack handler
286└─ Day 5: Integration tests
287
288Week 3: Polish
289├─ Day 1-2: Add CORS support
290├─ Day 3-4: Error handling improvements
291└─ Day 5: E2E tests with real git
292
293Week 4: Compliance
294├─ Day 1-3: GRASP-01 compliance testing
295├─ Day 4: Performance testing
296└─ Day 5: Documentation
297
298
299Status: ✅ Analysis complete, ready to implement