107 lines
4.1 KiB
JavaScript
107 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// Local patch: optional email auto-verification for self-hosted servers.
|
|
//
|
|
// Adventure Land normally emails a verification link after signup. A
|
|
// self-hosted server without SES/SMTP cannot deliver it, so every new account
|
|
// stays "Not Verified" (luck/gold -25%) until an admin verifies it manually.
|
|
//
|
|
// With options.auto_verify_email enabled, this patch:
|
|
// - marks new accounts verified before they are saved,
|
|
// - verifies existing accounts on their next login,
|
|
// - keeps accounts verified when they change their email address,
|
|
// - skips the verification emails that cannot be delivered.
|
|
//
|
|
// Upstream behaviour is unchanged when the option is absent or false. Every
|
|
// edit is idempotent and fails loudly if the anchor is gone.
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const app = path.resolve(process.argv[2] || "/app");
|
|
const file = path.join(app, "api.js");
|
|
let source = fs.readFileSync(file, "utf8");
|
|
|
|
// `options` is always present in production, but some upstream tests load
|
|
// api.js without it. Keep the patched code loadable there too.
|
|
const autoVerify = 'typeof options !== "undefined" && options.auto_verify_email';
|
|
|
|
function fail(message) {
|
|
console.error("patch-auto-verify-email: " + message);
|
|
process.exit(1);
|
|
}
|
|
|
|
function edit(label, from, to) {
|
|
if (source.includes(to)) {
|
|
console.log("patch-auto-verify-email: " + label + " already patched");
|
|
return;
|
|
}
|
|
const occurrences = source.split(from).length - 1;
|
|
if (occurrences !== 1) {
|
|
fail(label + ": expected 1 occurrence of the anchor, found " + occurrences);
|
|
}
|
|
source = source.replace(from, to);
|
|
console.log("patch-auto-verify-email: patched " + label);
|
|
}
|
|
|
|
// 1. Existing accounts: verify them when they log in.
|
|
edit(
|
|
"login",
|
|
`\t\t\t\t\tR.auth = get_new_auth(R.user);
|
|
\t\t\t\t\tawait tx_save(R.user);`,
|
|
`\t\t\t\t\t// Local: keep accounts verified without an email service.
|
|
\t\t\t\t\tif (${autoVerify}) R.user.info.verified = true;
|
|
\t\t\t\t\tR.auth = get_new_auth(R.user);
|
|
\t\t\t\t\tawait tx_save(R.user);`,
|
|
);
|
|
|
|
// 2. New accounts: set the flag before the user document is written.
|
|
edit(
|
|
"signup",
|
|
`\t\t\t\tblobs: ["info"],
|
|
\t\t\t};
|
|
\t\t\tR.auth = get_new_auth(R.user);`,
|
|
`\t\t\t\tblobs: ["info"],
|
|
\t\t\t};
|
|
\t\t\t// Local: new accounts are verified immediately without an email service.
|
|
\t\t\tif (${autoVerify}) R.user.info.verified = true;
|
|
\t\t\tR.auth = get_new_auth(R.user);`,
|
|
);
|
|
|
|
// 3. Signup: do not try to send a verification email when auto-verify is on.
|
|
edit(
|
|
"signup email",
|
|
`\tset_cookie(args.res, options.cookie_key, get_id(R.user) + "-" + R.auth, domain.domain);
|
|
\tsend_verification_email(domain, R.user);
|
|
\targs.res.infs.push({ type: "success", message: phrase_html("server.api.signup_complete") });`,
|
|
`\tset_cookie(args.res, options.cookie_key, get_id(R.user) + "-" + R.auth, domain.domain);
|
|
\tif (!(${autoVerify})) send_verification_email(domain, R.user);
|
|
\targs.res.infs.push({ type: "success", message: phrase_html("server.api.signup_complete") });`,
|
|
);
|
|
|
|
// 4. Email changes: keep the account verified.
|
|
edit(
|
|
"email change verification",
|
|
`\t\t\tif (gf(R.user, "verified", 0)) delete R.user.info.verified;`,
|
|
`\t\t\t// Local: keep the account verified after an email change.
|
|
\t\t\tif (${autoVerify}) R.user.info.verified = true;
|
|
\t\t\telse if (gf(R.user, "verified", 0)) delete R.user.info.verified;`,
|
|
);
|
|
|
|
// 5. Email changes: skip the verification email and its "re-sent" message.
|
|
edit(
|
|
"email change email",
|
|
`\tif (R.failed) return { failed: true, reason: R.reason || "operation_failed" };
|
|
\tsend_verification_email(domain, R.user);
|
|
\targs.res.infs.push({ type: "success", message: phrase_html("server.api.email_changed_verification_email_re_sent_refresh_the_page") });`,
|
|
`\tif (R.failed) return { failed: true, reason: R.reason || "operation_failed" };
|
|
\tif (${autoVerify}) {
|
|
\t\targs.res.infs.push({ type: "success", message: phrase_html("server.api.setting_changed") });
|
|
\t} else {
|
|
\t\tsend_verification_email(domain, R.user);
|
|
\t\targs.res.infs.push({ type: "success", message: phrase_html("server.api.email_changed_verification_email_re_sent_refresh_the_page") });
|
|
\t}`,
|
|
);
|
|
|
|
fs.writeFileSync(file, source);
|
|
console.log("patch-auto-verify-email: wrote " + file);
|