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 — 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 — 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}