import { sdk } from '../sdk' import { configFile } from '../file-models/config.json' const { InputSpec, Value } = sdk // Lifetime cap on how many distinct anonymous trial cookies one IP // address can mint, FOR THE LIFETIME OF THE INSTALL — not a rolling // daily window. Was previously per-day; switched in 0.2.84 so a user // who clears cookies can't simply wait 24h and replay the trial. // // Anti-abuse model: // - Each minted cookie carries trial_credits_per_visitor credits // - Each IP can mint at most `trials_per_ip_lifetime` cookies, ever // - Combined effect: an IP's total free credits = // trial_credits_per_visitor × trials_per_ip_lifetime // - Once spent, the visitor must sign up (which grants // tenant_default_credits) or pay for more // // IP rotation via VPN / proxy pool defeats this, same as before. The // goal isn't to be unbypassable — it's to raise the floor for casual // scripted abuse and give the operator forensic data (IP + UA logged // on every trial) to manually ban any sophisticated abuser. // // Defaults to 5 — generous enough that a family on one NAT all get // trials, tight enough that 50 trials/IP from one address looks like // scripted abuse in the admin dashboard. // // Legacy field name `trials_per_ip_per_day` is preserved on the // config schema as a read-only alias so installs upgrading from // 0.2.77–0.2.83 don't lose their existing setting. const inputSpec = InputSpec.of({ limit: Value.number({ name: 'Max trial cookies per IP (lifetime)', description: 'How many anonymous trial cookies can be issued from a single IP, FOR THE LIFE OF THIS INSTALL. Not a rolling daily window — once the IP hits this cap, no more trial cookies from that address ever. Higher = friendlier to shared networks (offices, families). Lower = tighter against scripted abuse + cookie-clearing replay.', required: true, default: 5, integer: true, min: 1, max: 50, }), }) export const setTrialsPerIpPerDay = sdk.Action.withInput( 'set-trials-per-ip-per-day', async ({ effects }) => ({ name: 'Set Trial Cookies per IP (Lifetime)', description: 'Anti-abuse cap on how many trial cookies a single IP can mint over the life of this install. Default: 5. Was per-day in 0.2.77–0.2.83 and is now lifetime — see release notes for 0.2.84.', warning: null, allowedStatuses: 'any', group: 'Multi-Tenant', visibility: 'enabled', }), inputSpec, async ({ effects }) => { const config = await configFile.read().once() // Prefer the new field; fall back to the legacy per_day key for // operators whose StartOS-managed config still has the old name. const current = config?.trials_per_ip_lifetime ?? config?.trials_per_ip_per_day ?? 5 return { limit: current } }, async ({ effects, input }) => { // Write to BOTH keys so any code path still reading the legacy name // gets a sane value too. anon-trial.js prefers the new key. await configFile.merge(effects, { trials_per_ip_lifetime: input.limit, trials_per_ip_per_day: input.limit, }) return null }, )