code wiki / _hdl_build / nx_cms_email.nx
nx_cms_email.nx source
↩ module page · 63 lines · 3019 B
1// nx_cms_email.nx -- CMS EMAIL / CRM / NEWSLETTER (sovereign double-opt-in subscriber engine). The
2// Mailchimp/HubSpot class, made Nishi-native: the subscriber list lives ON-BOX and NEVER leaves for a
3// cloud ESP (privacy-native, the exceed angle), confirmation is DOUBLE OPT-IN by default (a fresh
4// subscribe is PENDING, never auto-confirmed = no list-bombing / GDPR-correct), and the confirm token
5// is DETERMINISTIC -- derived from (list-salt, email) via the canonical nx_fnv, so it is recomputable
6// and needs no stored-token table. Only CONFIRMED subscribers are ever on the send list. Composes
7// nx_fnv (cardinal: never re-implement a hash inline). license_tier: ORIGINAL
8import "nx_fnv.nx"
9import "nx_syscalls.nx"
10
11// subscriber states
12const NX_SUB_PENDING: i64 = 0
13const NX_SUB_CONFIRMED: i64 = 1
14const NX_SUB_UNSUBSCRIBED: i64 = 2
15
16func em_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
17
18// deterministic confirmation token for (list, email): canonical FNV-1a over list "|" email, sign-folded.
19// Recomputable -> no stored-token table; an attacker cannot forge it without the list salt.
20func email_confirm_token(list: *u8, email: *u8) -> i64 {
21 var h: i64 = fnv1a_init()
22 h = fnv1a_update(h, list, em_slen(list))
23 h = fnv1a_update(h, "|" as *u8, 1)
24 h = fnv1a_update(h, email, em_slen(email))
25 return h & 0x7fffffffffffffff
26}
27
28// a fresh subscription is always PENDING (double opt-in -- never auto-confirmed)
29func email_subscribe() -> i64 { return NX_SUB_PENDING }
30
31// confirm a subscription with a provided token. Idempotent on an already-confirmed subscriber; only a
32// PENDING subscriber with the CORRECT token becomes CONFIRMED; a wrong token leaves it PENDING (refused);
33// an UNSUBSCRIBED subscriber stays unsubscribed (must re-subscribe out of band).
34func email_confirm(state: i64, provided: i64, list: *u8, email: *u8) -> i64 {
35 if state == NX_SUB_CONFIRMED { return NX_SUB_CONFIRMED }
36 if state != NX_SUB_PENDING { return state }
37 if provided == email_confirm_token(list, email) { return NX_SUB_CONFIRMED }
38 return NX_SUB_PENDING
39}
40
41// one-click unsubscribe (GDPR): from any state -> UNSUBSCRIBED
42func email_unsubscribe(state: i64) -> i64 { return NX_SUB_UNSUBSCRIBED }
43
44// is this subscriber on the send list? ONLY confirmed -- pending and unsubscribed are suppressed.
45func email_on_send_list(state: i64) -> i64 { if state == NX_SUB_CONFIRMED { return 1 } return 0 }
46
47// minimal address sanity: exactly one '@' (not first), at least one '.' after it, no spaces.
48func email_addr_valid(s: *u8) -> i64 {
49 var at: i64 = 0 - 1
50 var dot_after: i64 = 0
51 var i: i64 = 0
52 while s[i] != (0 as u8) {
53 let c: i64 = s[i] as i64
54 if c == 64 { if at < 0 { at = i } else { return 0 } }
55 if c == 32 { return 0 }
56 if at >= 0 { if i > at { if c == 46 { dot_after = 1 } } }
57 i = i + 1
58 }
59 if at <= 0 { return 0 }
60 if at >= i - 1 { return 0 }
61 if dot_after == 0 { return 0 }
62 return 1
63}