code wiki / _hdl_build / nx_siteedit_gate.nx
nx_siteedit_gate.nx source
↩ module page · 195 lines · 13298 B
1// nx_siteedit_gate.nx -- SOVEREIGN in-process referee for the admin.<d>/site SITE-VISUALS editor.
2// Arms a fresh Modern-Auth realm, seeds a real .site blueprint, then drives the pure router
3// sed_handle with crafted HTTP bytes and asserts response bytes AND filesystem side effects:
4// T1 GET /site -> 200 editor shell
5// T2 NEG /site/config no session -> 401
6// T3 authed GET /site/config -> 200 + the seeded blueprint bytes
7// T4 authed POST /site/save (edited hero marker) -> 200 SAVED + config updated + a .v version kept
8// T5 NEG save with hero| MISSING -> 400 REFUSED + live config UNCHANGED (marker still present)
9// T6 authed POST /site/preview -> preview/index.html EXISTS + carries the edited marker
10// T7 authed POST /site/publish -> docroot index.html + practice page + sitemap exist w/ marker,
11// .prev-index.html backup exists (the pre-publish homepage)
12// T8 NEG publish with NO session -> 401 and the docroot index keeps T7's content (untouched)
13// GREEN iff 8/8. Appends knowledge/status/siteedit_gate.log. license_tier: ORIGINAL expect_exit: 0
14import "_hdl_build/nx_siteedit_daemon.nx"
15import "hub/nx_modern_auth_flow.nx"
16import "nx_syscalls.nx"
17
18func g_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
19func g_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
20func g_lw(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
21func g_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i }
22func g_cat_n(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o + i] = s[i]; i = i + 1 } return o + n }
23func g_starts(buf: *u8, n: i64, s: *u8) -> i64 { let sn: i64 = g_len(s); if n < sn { return 0 } var i: i64 = 0; while i < sn { if (buf[i] as i64) != (s[i] as i64) { return 0 } i = i + 1 } return 1 }
24func g_contains(hay: *u8, n: i64, needle: *u8) -> i64 {
25 let nn: i64 = g_len(needle); var i: i64 = 0
26 while i + nn <= n { var m: i64 = 1; var j: i64 = 0; while j < nn { if (hay[i + j] as i64) != (needle[j] as i64) { m = 0; j = nn } else { j = j + 1 } } if m == 1 { return 1 } i = i + 1 }
27 return 0
28}
29func g_extract_token(resp: *u8, n: i64, out: *u8) -> i64 {
30 let key: *u8 = "{\"token\":\"" as *u8; let kn: i64 = 10
31 var start: i64 = 0 - 1; var i: i64 = 0
32 while i + kn <= n { var m: i64 = 1; var j: i64 = 0; while j < kn { if (resp[i + j] as i64) != (key[j] as i64) { m = 0; j = kn } else { j = j + 1 } } if m == 1 { start = i + kn; i = n } i = i + 1 }
33 if start < 0 { return 0 }
34 var w: i64 = 0; var k: i64 = start; var done: i64 = 0
35 while done == 0 { if k >= n { done = 1 } if done == 0 { if (resp[k] as i64) == 34 { done = 1 } } if done == 0 { out[w] = resp[k]; w = w + 1; k = k + 1 } }
36 return w
37}
38// file contains needle? (0 also when absent)
39func g_file_has(path: *u8, needle: *u8) -> i64 {
40 let szp: *i64 = sys_mmap(16) as *i64
41 szp[0] = 0
42 let b: *u8 = sys_read_file(path, szp)
43 if (b as i64) == 0 { return 0 }
44 return g_contains(b, szp[0], needle)
45}
46func g_file_exists(path: *u8) -> i64 {
47 let fd: i64 = sys_openat_rd(path)
48 if fd < 0 { return 0 }
49 sys_close(fd)
50 return 1
51}
52func g_verdict(label: *u8, ok: i64) -> i64 { g_w(" " as *u8); g_w(label); if ok == 1 { g_w(" PASS\n" as *u8) } else { g_w(" FAIL\n" as *u8) } return ok }
53
54const GCFG_BASE: *u8 = "title|Andelin West -- Utah Attorneys\nheader|Andelin West|Free consultation|#consult\nhero|Straightforward legal help.|Talk to a Utah attorney.|Start now|#consult\nsearch|/search|Search...\ncards|How we help\ncard|Family Law|Divorce and custody handled with care.\ncard|Estate Planning|Wills and trusts done right.\nstep|Tell us what happened.\nsig|Licensed by the Utah State Bar\nfooter|Andelin West, PLLC -- Utah\n"
55const GCFG_EDIT: *u8 = "title|Andelin West -- Utah Attorneys\nheader|Andelin West|Free consultation|#consult\nhero|glacierheadline legal help for Utah.|Talk to a Utah attorney.|Start now|#consult\nsearch|/search|Search...\ncards|How we help\ncard|Family Law|Divorce and custody handled with care.\ncard|Estate Planning|Wills and trusts done right.\nstep|Tell us what happened.\nsig|Licensed by the Utah State Bar\nfooter|Andelin West, PLLC -- Utah\n"
56const GCFG_BAD: *u8 = "title|Broken config\nheader|X|Y|#z\nsearch|/search|s\ncard|Only|Card\nfooter|F\n"
57
58func main() -> i64 {
59 g_w("=== SITEEDIT GATE (admin.<d>/site visuals editor; sovereign in-process) ===\n" as *u8)
60 let store_path: *u8 = "/tmp/nx_sed_gate_store.log" as *u8
61 let keys_path: *u8 = "/tmp/nx_sed_gate_keys.log" as *u8
62 let g_trunc1: i64 = sys_openat_wr(store_path, 0x180)
63 if g_trunc1 >= 0 { sys_close(g_trunc1) }
64 let g_trunc2: i64 = sys_openat_wr(keys_path, 0x180)
65 if g_trunc2 >= 0 { sys_close(g_trunc2) }
66 sys_mkdir("/tmp/nx_sed_gate_root" as *u8, 511)
67 let docroot: *u8 = "/tmp/nx_sed_gate_root" as *u8
68 let cfgpath: *u8 = "/tmp/nx_sed_gate.site" as *u8
69 let domain: *u8 = "andelinwest.com" as *u8
70 // seed the blueprint
71 let cf: i64 = sys_openat_wr(cfgpath, 0x1a4)
72 if cf >= 0 { sys_write(cf, GCFG_BASE, g_len(GCFG_BASE)); sys_close(cf) }
73 // seed a fake "old homepage" so the publish backup has something to keep
74 let oldidx: i64 = sys_openat_wr("/tmp/nx_sed_gate_root/index.html" as *u8, 0x1a4)
75 if oldidx >= 0 { sys_write(oldidx, "OLD-HOMEPAGE-SENTINEL" as *u8, 21); sys_close(oldidx) }
76
77 let oprf_seed: *u8 = sys_mmap(32); let akp: *u8 = sys_mmap(32); let akb: *u8 = sys_mmap(33)
78 let edp: *u8 = sys_mmap(32); let edb: *u8 = sys_mmap(32)
79 if nx_uas_server_keys_load_or_init(keys_path, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK { g_w("KEYS FAIL\n" as *u8); sys_exit(1) }
80 let realm: *u8 = "andelinwest_docportal" as *u8
81 let realm_n: i64 = g_len(realm)
82 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext
83 if nx_auth_context_init(ctx, realm, realm_n, realm, realm_n, store_path as i64, oprf_seed, edp, edb, 900, 8192, 1, 1, 5, 1) != NX_MAUTH_OK { g_w("CTX FAIL\n" as *u8); sys_exit(1) }
84 let mn: *u8 = sys_mmap(512); let mn_n: *i64 = sys_mmap(16) as *i64
85 if nx_modern_auth_register(ctx, "editor" as *u8, 6, "site editor passphrase" as *u8, 22, mn, 512, mn_n) != NX_MAUTH_OK { g_w("REGISTER FAIL\n" as *u8); sys_exit(1) }
86 let tok: *u8 = sys_mmap(NX_MAUTH_SESSION_TOKEN_BYTES)
87 let tokn: *i64 = sys_mmap(16) as *i64
88 tokn[0] = 0
89 if nx_modern_auth_login(ctx, "editor" as *u8, 6, "site editor passphrase" as *u8, 22, tok, NX_MAUTH_SESSION_TOKEN_BYTES, tokn) != NX_MAUTH_OK { g_w("LOGIN FAIL\n" as *u8); sys_exit(1) }
90 let tok64: *u8 = sys_mmap(256)
91 let tok64n: i64 = b64_encode(tok, NX_MAUTH_SESSION_TOKEN_BYTES, tok64)
92
93 let req: *u8 = sys_mmap(65536)
94 let out: *u8 = sys_mmap(SED_OUTCAP)
95 var pass: i64 = 0
96
97 // T1 shell
98 let n1: i64 = g_cat(req, 0, "GET /site HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
99 let o1: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n1, out)
100 var t1: i64 = 0
101 if g_starts(out, o1, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o1, "Site editor" as *u8) == 1 { t1 = 1 } }
102 pass = pass + g_verdict("T1 GET /site -> 200 editor shell:" as *u8, t1)
103
104 // T2 NEG no session
105 let n2: i64 = g_cat(req, 0, "GET /site/config HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
106 let o2: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n2, out)
107 var t2: i64 = 0
108 if g_starts(out, o2, "HTTP/1.1 401" as *u8) == 1 { t2 = 1 }
109 pass = pass + g_verdict("T2 NEG /site/config no session -> 401:" as *u8, t2)
110
111 // T3 authed config read
112 var n3: i64 = g_cat(req, 0, "GET /site/config HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8)
113 n3 = g_cat_n(req, n3, tok64, tok64n)
114 n3 = g_cat(req, n3, "\r\n\r\n" as *u8)
115 let o3: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n3, out)
116 var t3: i64 = 0
117 if g_starts(out, o3, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o3, "Straightforward legal help." as *u8) == 1 { t3 = 1 } }
118 pass = pass + g_verdict("T3 authed /site/config -> the blueprint:" as *u8, t3)
119
120 // T4 authed save (edited hero)
121 var n4: i64 = g_cat(req, 0, "POST /site/save HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8)
122 n4 = g_cat_n(req, n4, tok64, tok64n)
123 n4 = g_cat(req, n4, "\r\n\r\n" as *u8)
124 n4 = g_cat(req, n4, GCFG_EDIT)
125 let o4: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n4, out)
126 var t4: i64 = 0
127 if g_starts(out, o4, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o4, "SAVED" as *u8) == 1 { if g_file_has(cfgpath, "glacierheadline" as *u8) == 1 { t4 = 1 } } }
128 pass = pass + g_verdict("T4 authed save -> SAVED + config carries the edit:" as *u8, t4)
129
130 // T5 NEG bad config refused, live config unchanged
131 var n5: i64 = g_cat(req, 0, "POST /site/save HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8)
132 n5 = g_cat_n(req, n5, tok64, tok64n)
133 n5 = g_cat(req, n5, "\r\n\r\n" as *u8)
134 n5 = g_cat(req, n5, GCFG_BAD)
135 let o5: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n5, out)
136 var t5: i64 = 0
137 if g_starts(out, o5, "HTTP/1.1 400" as *u8) == 1 { if g_file_has(cfgpath, "glacierheadline" as *u8) == 1 { t5 = 1 } }
138 pass = pass + g_verdict("T5 NEG hero-less config -> 400 + config unchanged:" as *u8, t5)
139
140 // T6 preview
141 var n6: i64 = g_cat(req, 0, "POST /site/preview HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8)
142 n6 = g_cat_n(req, n6, tok64, tok64n)
143 n6 = g_cat(req, n6, "\r\n\r\n" as *u8)
144 let o6: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n6, out)
145 var t6: i64 = 0
146 if g_starts(out, o6, "HTTP/1.1 200" as *u8) == 1 { if g_file_has("/tmp/nx_sed_gate_root/preview/index.html" as *u8, "glacierheadline" as *u8) == 1 { if g_file_has("/tmp/nx_sed_gate_root/index.html" as *u8, "OLD-HOMEPAGE-SENTINEL" as *u8) == 1 { t6 = 1 } } }
147 pass = pass + g_verdict("T6 preview builds under /preview, live untouched:" as *u8, t6)
148
149 // T7 publish
150 var n7: i64 = g_cat(req, 0, "POST /site/publish HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8)
151 n7 = g_cat_n(req, n7, tok64, tok64n)
152 n7 = g_cat(req, n7, "\r\n\r\n" as *u8)
153 let o7: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n7, out)
154 var t7: i64 = 0
155 if g_starts(out, o7, "HTTP/1.1 200" as *u8) == 1 {
156 if g_file_has("/tmp/nx_sed_gate_root/index.html" as *u8, "glacierheadline" as *u8) == 1 {
157 if g_file_exists("/tmp/nx_sed_gate_root/practice/family-law.html" as *u8) == 1 {
158 if g_file_has("/tmp/nx_sed_gate_root/sitemap.xml" as *u8, "family-law" as *u8) == 1 {
159 if g_file_has("/tmp/nx_sed_gate_root/.prev-index.html" as *u8, "OLD-HOMEPAGE-SENTINEL" as *u8) == 1 { t7 = 1 }
160 }
161 }
162 }
163 }
164 pass = pass + g_verdict("T7 publish -> live pages + sitemap + .prev backup:" as *u8, t7)
165
166 // T8 NEG publish without session leaves T7's live content
167 let n8: i64 = g_cat(req, 0, "POST /site/publish HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
168 let o8: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, n8, out)
169 var t8: i64 = 0
170 if g_starts(out, o8, "HTTP/1.1 401" as *u8) == 1 { if g_file_has("/tmp/nx_sed_gate_root/index.html" as *u8, "glacierheadline" as *u8) == 1 { t8 = 1 } }
171 pass = pass + g_verdict("T8 NEG no-session publish -> 401, live intact:" as *u8, t8)
172
173 g_w("----\nSITEEDIT rows=8 pass=" as *u8)
174 let db: *u8 = sys_mmap(8); var v: i64 = pass; var k: i64 = 0
175 if v == 0 { db[0] = 48 as u8; k = 1 } else { while v > 0 { db[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 } }
176 let rv: *u8 = sys_mmap(8); var x: i64 = 0
177 while x < k { rv[x] = db[k - 1 - x]; x = x + 1 }
178 sys_write(1, rv, k); g_w("\n" as *u8)
179 let lg: i64 = sys_openat_append("knowledge/status/siteedit_gate.log" as *u8, 0x1a4)
180 if lg >= 0 {
181 g_lw(lg, "SITEEDIT auth=modern-opaque shell=" as *u8); if t1 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
182 g_lw(lg, " deny_no_session=" as *u8); if t2 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
183 g_lw(lg, " config_read=" as *u8); if t3 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
184 g_lw(lg, " save_versioned=" as *u8); if t4 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
185 g_lw(lg, " refuse_bad_config=" as *u8); if t5 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
186 g_lw(lg, " preview_isolated=" as *u8); if t6 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
187 g_lw(lg, " publish_backup=" as *u8); if t7 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
188 g_lw(lg, " deny_publish_no_session=" as *u8); if t8 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) }
189 g_lw(lg, " rows=8 pass=" as *u8); sys_write(lg, rv, k)
190 if pass == 8 { g_lw(lg, " verdict=GREEN\n" as *u8) } else { g_lw(lg, " verdict=RED\n" as *u8) }
191 sys_close(lg)
192 }
193 if pass == 8 { g_w("SITEEDIT GREEN (auth-gated save/preview/publish; refuse-bad-config; .prev revert kept)\n" as *u8); sys_exit(0); return 0 }
194 g_w("SITEEDIT RED\n" as *u8); sys_exit(1); return 1
195}