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}