code wiki / _hdl_build / nx_lan_signup_routes.nx

nx_lan_signup_routes.nx source

↩ module page · 140 lines · 13161 B

1// nx_lan_signup_routes.nx -- the SHARED router for the family signup + HR admin surface (no main, no sockets), so 2// BOTH the daemon (nx_lan_signup_daemon) and a referee (nx_lan_signup_socket_gate) drive ONE table (rule #15 DRY, 3// mirroring nx_opaque_login_routes / olg_route). lsd_route builds + writes the response on cfd given the request, 4// the real peer IP (from accept()), and the OPAQUE ctx + HR store. Routes + gating documented on the daemon. 5import "nx_opaque_login.nx" // olg_register / olg_whoami + NxAuthContext + NX_MAUTH_OK 6import "nx_lan_signup.nx" // ls_signup_allowed / ls_ip_is_lan 7import "nx_hr_admin.nx" // hra_claim / hra_invite / hra_suspend / hra_is_superadmin 8import "nx_hr_admin_page.nx" // hap_emit 9import "nx_hr_audit.nx" // hau_emit -- the /hr/audit trail 10import "nx_http_form.nx" // nx_http_form_get_field 11const LSD_MAGIC_524288: i64 = 524288 12const LSD_MAGIC_262144: i64 = 262144 13 14const LSD_REALM: *u8 = "nishi_site_admin" as *u8 15const LSD_REALM_N: i64 = 16 16const LSD_FAMILY: *u8 = "andelin" as *u8 17 18func lsd_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 19func lsd_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c<48{return v} if c>57{return v} v=v*10+(c-48); i=i+1 } return v } 20func lsd_starts(buf: *u8, n: i64, pre: *u8) -> i64 { var i: i64=0; while pre[i]!=(0 as u8){ if i>=n {return 0} if buf[i]!=pre[i]{return 0} i=i+1 } return 1 } 21func lsd_find(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 { if nl==0 {return 0} var i: i64=0; while i+nl<=n { var j: i64=0; var ok: i64=1; while j<nl { if buf[i+j]!=needle[j]{ok=0;j=nl} else {j=j+1} } if ok==1 {return i} i=i+1 } return 0-1 } 22func lsd_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){dst[o]=s[i]; o=o+1; i=i+1} return o } 23func lsd_catb(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var o: i64=off; var i: i64=0; while i<n {dst[o]=src[i]; o=o+1; i=i+1} return o } 24func lsd_itoa(dst: *u8, off: i64, v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var o: i64=off; var q: i64=k-1; while q>=0{dst[o]=t[q];o=o+1;q=q-1} return o } 25func lsd_hex(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { let hx: *u8="0123456789abcdef" as *u8; var o: i64=off; var i: i64=0; while i<n { let c: i64=(src[i] as i64)&0xff; dst[o]=hx[(c>>4)&15]; dst[o+1]=hx[c&15]; o=o+2; i=i+1 } return o } 26func lsd_hdr_val(req: *u8, hend: i64, name: *u8, nl: i64, out: *u8, cap: i64) -> i64 { 27 let p: i64 = lsd_find(req, hend, name, nl) 28 if p < 0 { return 0 } 29 var i: i64 = p + nl 30 if i < hend { if req[i]==(32 as u8) { i=i+1 } } 31 var o: i64 = 0 32 while i < hend { let c: u8 = req[i]; if c==(13 as u8){i=hend} else { if c==(10 as u8){i=hend} else { if o<cap-1 {out[o]=c; o=o+1} i=i+1 } } } 33 out[o]=0 as u8; return o 34} 35func lsd_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 36 let buf: *u8 = sys_mmap(LSD_MAGIC_524288); var o: i64 = 0 37 o = lsd_cat(buf, o, "HTTP/1.1 " as *u8); o = lsd_cat(buf, o, status) 38 o = lsd_cat(buf, o, "\r\nContent-Type: " as *u8); o = lsd_cat(buf, o, ctype) 39 o = lsd_cat(buf, o, "\r\nContent-Length: " as *u8); o = lsd_itoa(buf, o, blen) 40 o = lsd_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8) 41 o = lsd_catb(buf, o, body, blen) 42 sys_write(cfd, buf, o); return 0 43} 44func lsd_send303(cfd: i64, loc: *u8) -> i64 { 45 let buf: *u8 = sys_mmap(256); var o: i64 = 0 46 o = lsd_cat(buf, o, "HTTP/1.1 303 See Other\r\nLocation: " as *u8); o = lsd_cat(buf, o, loc) 47 o = lsd_cat(buf, o, "\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" as *u8) 48 sys_write(cfd, buf, o); return 0 49} 50// 1 iff the request's X-Nishi-Session resolves (via olg_whoami) to an ACTIVE superadmin in the HR SSOT. 51func lsd_caller_super(ctx: *NxAuthContext, req: *u8, hend: i64, now: i64, hr_store: *u8) -> i64 { 52 let tb: *u8 = sys_mmap(512) 53 let tl: i64 = lsd_hdr_val(req, hend, "X-Nishi-Session:" as *u8, 16, tb, 512) 54 if tl <= 0 { return 0 } 55 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 56 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) != NX_MAUTH_OK { return 0 } 57 let ux: *u8 = sys_mmap(160); let uxn: i64 = lsd_hex(ux, 0, uh, uhn[0]) 58 return hra_is_superadmin(hr_store, ux, uxn) 59} 60 61const LSD_PAGE: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Family &mdash; sign up</title><style>body{font-family:system-ui,sans-serif;max-width:460px;margin:6vh auto;padding:0 18px;color:#16202e}h1{font-size:1.25rem}input{width:100%;padding:9px;margin:5px 0;box-sizing:border-box;border:1px solid #b9c2d6;border-radius:5px}button{padding:9px 16px;background:#0b2545;color:#fff;border:0;border-radius:5px;cursor:pointer}#m{margin:14px 0;padding:12px;background:#f1f4fa;border-left:3px solid #0b2545;word-break:break-all;min-height:1.2em}</style></head><body><h1>Nishi Family &mdash; sign up</h1><p style=\"color:#667;font-size:.9rem\">Home-network only, by invitation. Use the handle elderwesto gave you (your first name) and choose a passphrase the server never sees (full OPAQUE).</p><div id=m>Enter your invited handle + a passphrase.</div><input id=h placeholder=\"handle (your first name)\"><input id=p type=password placeholder=\"passphrase\"><button onclick=reg()>Sign up</button><details style=\"margin-top:16px\"><summary style=\"cursor:pointer;color:#0b2545\">Forgot your passphrase?</summary><p style=\"color:#667;font-size:.85rem\">Enter your handle, your 24-word recovery phrase, and a new passphrase.</p><input id=rh placeholder=\"handle\"><input id=rm placeholder=\"recovery phrase (24 words)\"><input id=rp2 type=password placeholder=\"new passphrase\"><button onclick=rec()>Recover</button></details><script>function M(t){document.getElementById('m').textContent=t}async function reg(){M('Creating your account\\u2026 (a moment)');try{var r=await fetch('/register',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'handle='+encodeURIComponent(document.getElementById('h').value)+'&pw='+encodeURIComponent(document.getElementById('p').value)});var j={};try{j=await r.json()}catch(e){}if(r.ok){M('Welcome! SAVE THIS RECOVERY PHRASE: '+j.mnemonic)}else{M('Cannot sign up ('+r.status+'): '+(j.error||'denied'))}}catch(e){M('network: '+e)}}async function rec(){M('Recovering\\u2026 (a moment)');try{var r=await fetch('/recover',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'handle='+encodeURIComponent(document.getElementById('rh').value)+'&mn='+encodeURIComponent(document.getElementById('rm').value)+'&pw='+encodeURIComponent(document.getElementById('rp2').value)});var j={};try{j=await r.json()}catch(e){}if(r.ok){M('Recovered! Your NEW recovery phrase (save it): '+j.mnemonic)}else{M('Recovery failed ('+r.status+'): '+(j.error||'denied'))}}catch(e){M('network: '+e)}}</script></body></html>" as *u8 62 63// THE ROUTER (flat, early-return; no deep nesting). Sends the response on cfd. ip4 = the real peer IP from accept(). 64// realm/realm_n are DATA-DRIVEN (rule #17): one daemon binary serves ANY property (nishi_site_admin, andelinwest_admin, 65// ...) -- the realm namespaces cred_ids, so a single sovereign daemon is realm-agnostic and the realms stay isolated. 66func lsd_route(ctx: *NxAuthContext, realm: *u8, realm_n: i64, ip4: *u8, req: *u8, rn: i64, hend: i64, body: *u8, bn: i64, now: i64, hr_store: *u8, cfd: i64) -> i64 { 67 if lsd_starts(req, rn, "POST /register" as *u8) == 1 { 68 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64 69 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64 70 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 71 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 72 if ls_signup_allowed(ip4, hr_store, realm, realm_n, hbuf, hl[0]) == 1 { 73 let mn: *u8 = sys_mmap(512); let mnn: *i64 = sys_mmap(16) as *i64 74 if olg_register(ctx, hbuf, hl[0], pbuf, pl[0], mn, 512, mnn) == NX_MAUTH_OK { 75 hra_claim(hr_store, realm, realm_n, hbuf, hl[0], LSD_FAMILY, now, "lan-signup" as *u8) 76 let jb: *u8 = sys_mmap(640); var o: i64 = lsd_cat(jb, 0, "{\"mnemonic\":\"" as *u8); o = lsd_catb(jb, o, mn, mnn[0]); o = lsd_cat(jb, o, "\"}" as *u8) 77 lsd_send(cfd, "200 OK" as *u8, "application/json" as *u8, jb, o) 78 } else { lsd_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"register failed\"}" as *u8, 27) } 79 } else { lsd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"signup requires the home network and an invite\"}" as *u8, 57) } 80 return 0 81 } 82 if lsd_starts(req, rn, "POST /recover" as *u8) == 1 { 83 // forgot-passphrase: LAN-gated (recover from home). The 24-word mnemonic IS the proof of ownership; a wrong 84 // mnemonic fails closed (olg_recover never resets the passphrase). NOT invite-gated -- the account exists. 85 if ls_ip_is_lan(ip4) == 0 { lsd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"home network only\"}" as *u8, 29); return 0 } 86 let rhb: *u8 = sys_mmap(128); let rhl: *i64 = sys_mmap(16) as *i64 87 let rmb: *u8 = sys_mmap(512); let rml: *i64 = sys_mmap(16) as *i64 88 let rpb: *u8 = sys_mmap(320); let rpl: *i64 = sys_mmap(16) as *i64 89 nx_http_form_get_field(body, bn, "handle" as *u8, 6, rhb, 127, rhl) 90 nx_http_form_get_field(body, bn, "mn" as *u8, 2, rmb, 511, rml) 91 nx_http_form_get_field(body, bn, "pw" as *u8, 2, rpb, 319, rpl) 92 let rmn: *u8 = sys_mmap(512); let rmnn: *i64 = sys_mmap(16) as *i64 93 if olg_recover(ctx, rhb, rhl[0], rmb, rml[0], rpb, rpl[0], rmn, 512, rmnn) == NX_MAUTH_OK { 94 let jb: *u8 = sys_mmap(640); var o: i64 = lsd_cat(jb, 0, "{\"mnemonic\":\"" as *u8); o = lsd_catb(jb, o, rmn, rmnn[0]); o = lsd_cat(jb, o, "\"}" as *u8) 95 lsd_send(cfd, "200 OK" as *u8, "application/json" as *u8, jb, o) 96 } else { 97 let em: *u8 = "{\"error\":\"recovery failed (check your recovery phrase)\"}" as *u8 98 lsd_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, em, lsd_slen(em)) 99 } 100 return 0 101 } 102 if lsd_starts(req, rn, "POST /hr/invite" as *u8) == 1 { 103 if ls_ip_is_lan(ip4) == 0 { lsd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"home network only\"}" as *u8, 29); return 0 } 104 if lsd_caller_super(ctx, req, hend, now, hr_store) == 0 { lsd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"owner only\"}" as *u8, 21); return 0 } 105 let ihb: *u8 = sys_mmap(128); let ihl: *i64 = sys_mmap(16) as *i64 106 let ilb: *u8 = sys_mmap(16); let ill: *i64 = sys_mmap(16) as *i64 107 nx_http_form_get_field(body, bn, "handle" as *u8, 6, ihb, 127, ihl) 108 nx_http_form_get_field(body, bn, "level" as *u8, 5, ilb, 15, ill) 109 var ilv: i64 = lsd_atoi(ilb); if ilv != 3 { ilv = 1 } // only member(1) or owner(3); default member 110 let icid: *u8 = sys_mmap(96) 111 hra_invite(hr_store, realm, realm_n, ihb, ihl[0], ilv, LSD_FAMILY, now, "console-admin" as *u8, icid) 112 lsd_send303(cfd, "/hr" as *u8) 113 return 0 114 } 115 if lsd_starts(req, rn, "POST /hr/suspend" as *u8) == 1 { 116 if ls_ip_is_lan(ip4) == 0 { lsd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"home network only\"}" as *u8, 29); return 0 } 117 if lsd_caller_super(ctx, req, hend, now, hr_store) == 0 { lsd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"owner only\"}" as *u8, 21); return 0 } 118 let shb: *u8 = sys_mmap(128); let shl: *i64 = sys_mmap(16) as *i64 119 nx_http_form_get_field(body, bn, "handle" as *u8, 6, shb, 127, shl) 120 hra_suspend(hr_store, realm, realm_n, shb, shl[0], LSD_FAMILY, now, "console-admin" as *u8) 121 lsd_send303(cfd, "/hr" as *u8) 122 return 0 123 } 124 if lsd_starts(req, rn, "GET /hr/audit" as *u8) == 1 { // checked BEFORE GET /hr (prefix) 125 var asuper: i64 = 0 126 if ls_ip_is_lan(ip4) == 1 { if lsd_caller_super(ctx, req, hend, now, hr_store) == 1 { asuper = 1 } } 127 let ag: *u8 = sys_mmap(LSD_MAGIC_262144); let an: i64 = hau_emit(hr_store, asuper, ag, LSD_MAGIC_262144) 128 lsd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, ag, an) 129 return 0 130 } 131 if lsd_starts(req, rn, "GET /hr" as *u8) == 1 { 132 var vsuper: i64 = 0 133 if ls_ip_is_lan(ip4) == 1 { if lsd_caller_super(ctx, req, hend, now, hr_store) == 1 { vsuper = 1 } } 134 let pg: *u8 = sys_mmap(LSD_MAGIC_262144); let pn: i64 = hap_emit(hr_store, vsuper, pg, LSD_MAGIC_262144) 135 lsd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, pg, pn) 136 return 0 137 } 138 lsd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, LSD_PAGE, lsd_slen(LSD_PAGE)) 139 return 0 140}