diff options
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.txt | 299 |
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 | |||
| 278 | Week 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 | |||
| 283 | Week 2: Authorization | ||
| 284 | ├─ Day 1-2: Implement PushValidator | ||
| 285 | ├─ Day 3-4: Modify git_receive_pack handler | ||
| 286 | └─ Day 5: Integration tests | ||
| 287 | |||
| 288 | Week 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 | |||
| 293 | Week 4: Compliance | ||
| 294 | ├─ Day 1-3: GRASP-01 compliance testing | ||
| 295 | ├─ Day 4: Performance testing | ||
| 296 | └─ Day 5: Documentation | ||
| 297 | |||
| 298 | |||
| 299 | Status: ✅ Analysis complete, ready to implement | ||