code wiki / _hdl_build / nx_ad_banner_gated_daemon.nx

nx_ad_banner_gated_daemon.nx source

↩ module page · 107 lines · 10162 B

1// nx_ad_banner_gated_daemon.nx -- the BANNER BUILDER served BEHIND THE OPAQUE FAMILY GATE. The builder form 2// page loads, but GENERATION (POST /ad/banner) is gated: it requires a valid no-cookie OPAQUE family session 3// (X-Nishi-Session header, validated via olg_whoami against the SAME realm/keys as /login). A first-party 4// inline script attaches the session token (sessionStorage 'nsess', set by the login page) on submit and 5// injects the returned banner -- the banner OUTPUT stays 0-third-party-JS. Unauthenticated -> 401 + a login 6// hint, so a non-family visitor can see the tool but cannot generate. The clean live wiring (a /ad proxy route 7// on sites.elf -> this daemon) is handed to the publisher/hosting. argv: [1]=port [2]=keys [3]=store 8// [4]=argon2_m(opt). Composes nx_opaque_login (session) + nx_ad_banner_builder (abb_render). license_tier: ORIGINAL 9import "nx_opaque_login.nx" // olg_ctx_setup / olg_whoami / NxAuthContext / NX_MAUTH_OK 10import "nx_ad_banner_builder.nx" // abb_render 11import "nx_syscalls.nx" 12const GBB_MAGIC_262144: i64 = 262144 13const GBB_MAGIC_65536: i64 = 65536 14const GBB_MAGIC_65535: i64 = 65535 15const GBB_MAGIC_1024: i64 = 1024 16const GBB_MAGIC_32768: i64 = 32768 17 18// The builder page: public shell + first-party session-aware generate (no third-party scripts). 19const GBB_PAGE: *u8 = "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Nishi Banner Builder</title><style>body{font-family:system-ui,sans-serif;max-width:760px;margin:4vh auto;padding:0 18px;color:#16202e}h1{font-size:1.3rem}form{display:grid;gap:10px}label{display:grid;gap:3px;font-size:.85rem;color:#445}input,select{padding:8px;border:1px solid #b9c2d6;border-radius:6px;font-size:.95rem}button{padding:10px 18px;background:#0b2545;color:#fff;border:0;border-radius:6px;font-size:1rem;cursor:pointer;justify-self:start}.prev{margin:18px 0;padding:20px;background:#eef2f7;border-radius:10px;display:flex;justify-content:center;min-height:60px}textarea{width:100%;min-height:90px;font-family:ui-monospace,monospace;font-size:.8rem;border:1px solid #b9c2d6;border-radius:6px;padding:8px;box-sizing:border-box}.note{color:#667;font-size:.9rem}</style></head><body><h1>Nishi Banner Builder</h1><p class='note'>Generate a static or animated banner for the one-ad-per-page slot. No third-party scripts, no trackers, no external images &mdash; ever. Generating requires a Nishi Family sign-in.</p><form id='f'><label>Business / sponsor<input name='business' required></label><label>Headline<input name='headline'></label><label>Body line<input name='body'></label><label>Call-to-action<input name='cta'></label><label>Click link (https:// or /path)<input name='link'></label><label>Size<select name='size'><option>rectangle 300x250</option><option>leaderboard 728x90</option><option>mobile 320x50</option><option>skyscraper 160x600</option><option>halfpage 300x600</option><option>billboard 970x250</option></select></label><label>Theme<select name='theme'><option>light</option><option>dark</option><option>warm</option></select></label><label>Kind<select name='kind'><option>static</option><option>dynamic (animated)</option></select></label><button type='submit'>Generate banner</button></form><h2 style='font-size:1.05rem'>Preview</h2><div class='prev' id='prev'>Fill the form and Generate.</div><h2 style='font-size:1.05rem'>Banner HTML (copy into the ad slot)</h2><textarea id='out' readonly></textarea><script>var f=document.getElementById('f'),pv=document.getElementById('prev'),ot=document.getElementById('out');f.addEventListener('submit',function(e){e.preventDefault();var b=new URLSearchParams(new FormData(f)).toString();pv.textContent='Generating\\u2026';fetch('/ad/banner',{method:'POST',headers:{'X-Nishi-Session':sessionStorage.getItem('nsess')||'','Content-Type':'application/x-www-form-urlencoded'},body:b}).then(function(r){if(r.status===401){pv.innerHTML='Please <a href=\"/login\">sign in</a> as Nishi Family to generate.';return null}return r.text()}).then(function(t){if(t){pv.innerHTML=t;ot.value=t}}).catch(function(err){pv.textContent='error: '+err})});</script></body></html>" 20 21func gbb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 22func gbb_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 } 23func gbb_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 } 24func gbb_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 } 25func gbb_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 } 26func gbb_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 } 27func gbb_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 } 28// extract a request header value (req[0..hend]) for `name` (incl trailing ':'), trimming a leading space. 29func gbb_hdr_val(req: *u8, hend: i64, name: *u8, nl: i64, out: *u8, cap: i64) -> i64 { 30 let p: i64 = gbb_find(req, hend, name, nl) 31 if p < 0 { out[0] = 0 as u8; return 0 } 32 var i: i64 = p + nl 33 if i < hend { if req[i] == (32 as u8) { i = i + 1 } } 34 var o: i64 = 0 35 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 } } } 36 out[o] = 0 as u8; return o 37} 38func gbb_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 39 let buf: *u8 = sys_mmap(GBB_MAGIC_262144); var o: i64 = 0 40 o = gbb_cat(buf, o, "HTTP/1.1 " as *u8); o = gbb_cat(buf, o, status) 41 o = gbb_cat(buf, o, "\r\nContent-Type: " as *u8); o = gbb_cat(buf, o, ctype) 42 o = gbb_cat(buf, o, "\r\nContent-Length: " as *u8); o = gbb_itoa(buf, o, blen) 43 o = gbb_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\nReferrer-Policy: no-referrer\r\n\r\n" as *u8) 44 o = gbb_catb(buf, o, body, blen) 45 sys_write(cfd, buf, o); return 0 46} 47 48func main(argc: i64, argv: *i64) -> i64 { 49 if argc < 4 { sys_write(1, "usage: nx_ad_banner_gated_daemon <port> <keys> <store> [argon2_m]\n" as *u8, 64); sys_exit(2); return 2 } 50 let port: i64 = gbb_atoi(argv[1] as *u8) 51 let keys: *u8 = argv[2] as *u8 52 let store: *u8 = argv[3] as *u8 53 var m_cost: i64 = GBB_MAGIC_65536 54 if argc > 4 { m_cost = gbb_atoi(argv[4] as *u8) } 55 56 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 57 if olg_ctx_setup(ctx, keys, store, "nishi_site_admin" as *u8, 16, "Nishi site admin" as *u8, 16, m_cost, 3, 4) != 0 { 58 sys_write(1, "CTX-INIT-FAIL\n" as *u8, 14); sys_exit(1); return 1 59 } 60 61 let addr: *u8 = sys_mmap(16) 62 addr[0] = 2 as u8; addr[1] = 0 as u8; addr[2] = ((port >> 8) & 0xff) as u8; addr[3] = (port & 0xff) as u8 63 var zi: i64 = 4; while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 } 64 let lfd: i64 = sys_socket(2, 1, 0) 65 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 66 let optv: *u8 = sys_mmap(4); optv[0] = 1 as u8 67 sys_setsockopt(lfd, 1, 2, optv, 4) 68 if sys_bind(lfd, addr, 16) < 0 { sys_write(1, "BIND-FAIL\n" as *u8, 10); sys_exit(1); return 1 } 69 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 70 sys_write(1, "BANNER-BUILDER-GATED-UP\n" as *u8, 24) 71 72 while 1 == 1 { 73 let cfd: i64 = sys_accept(lfd) 74 if cfd >= 0 { 75 sys_set_socket_timeout(cfd, 5) 76 let req: *u8 = sys_mmap(GBB_MAGIC_65536) 77 let rn: i64 = sys_read(cfd, req, GBB_MAGIC_65535) 78 if rn > 0 { 79 let he: i64 = gbb_find(req, rn, "\r\n\r\n" as *u8, 4) 80 if gbb_starts(req, rn, "POST /ad/banner" as *u8) == 1 { 81 // GATED: require a valid family OPAQUE session. 82 let tb: *u8 = sys_mmap(GBB_MAGIC_1024) 83 let tl: i64 = gbb_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, GBB_MAGIC_1024) 84 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 85 let now: i64 = sys_now_realtime_sec() 86 var authed: i64 = 0 87 if tl > 0 { if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { authed = 1 } } 88 if authed == 1 { 89 var body: *u8 = req; var bn: i64 = 0 90 if he >= 0 { body = ((req as i64) + he + 4) as *u8; bn = rn - he - 4 } 91 let out: *u8 = sys_mmap(GBB_MAGIC_32768) 92 let glen: i64 = abb_render(body, bn, out) 93 if glen > 0 { gbb_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, out, glen) } 94 else { gbb_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"missing business\"}" as *u8, 27) } 95 } else { 96 gbb_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, "{\"error\":\"login required\"}" as *u8, 25) 97 } 98 } else { 99 // the builder page (public shell; generation is gated above) 100 gbb_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, GBB_PAGE, gbb_slen(GBB_PAGE)) 101 } 102 } 103 sys_close(cfd) 104 } 105 } 106 sys_close(lfd); sys_exit(0); return 0 107}