Files
omniai-server/src/betaInviteCodes.js
T
2026-06-02 13:14:10 +08:00

139 lines
4.0 KiB
JavaScript

const fs = require("node:fs");
const path = require("node:path");
const {
ENTERPRISE_BETA_ACCOUNTS,
findEnterpriseBetaAccountByInviteCode,
isEnterpriseBetaInviteCode,
} = require("./enterpriseBetaAccounts");
const DEFAULT_BETA_INVITE_CODES_FILE = path.resolve(__dirname, "../config/internal-beta-codes.md");
function normalizeBetaInviteCode(value) {
return String(value || "")
.trim()
.replace(/[\s-]/g, "")
.toUpperCase();
}
function loadBetaInviteCodesFromText(text) {
const codes = new Set();
const content = String(text || "");
const pattern = /\b(?:[0-9A-Fa-f]{16}|[0-7]{16,24})\b/g;
for (const match of content.matchAll(pattern)) {
const code = normalizeBetaInviteCode(match[0]);
if (code) codes.add(code);
}
return codes;
}
function loadBetaInviteCodes(filePath = process.env.BETA_INVITE_CODES_FILE || DEFAULT_BETA_INVITE_CODES_FILE) {
const codes = new Set();
const envCodes = String(process.env.BETA_INVITE_CODES || "")
.split(/[,\s]+/)
.map(normalizeBetaInviteCode)
.filter(Boolean);
envCodes.forEach((code) => codes.add(code));
try {
const markdown = fs.readFileSync(filePath, "utf8");
loadBetaInviteCodesFromText(markdown).forEach((code) => codes.add(code));
} catch (error) {
if (error?.code !== "ENOENT") {
console.warn("[beta-invite] failed to read invite code file", error);
}
}
for (const account of ENTERPRISE_BETA_ACCOUNTS) {
codes.add(normalizeBetaInviteCode(account.inviteCode));
}
return codes;
}
function getBetaInviteCodeFromBody(body) {
return normalizeBetaInviteCode(
body?.betaCode ??
body?.beta_code ??
body?.inviteCode ??
body?.invite_code ??
body?.internalBetaCode ??
body?.internal_beta_code,
);
}
function validateBetaInviteCode(value, allowedCodes = loadBetaInviteCodes()) {
const code = normalizeBetaInviteCode(value);
return Boolean(code && allowedCodes.has(code));
}
function validateBetaInviteCodeFromBody(body) {
return validateBetaInviteCode(getBetaInviteCodeFromBody(body));
}
async function isBetaInviteCodeUsed(value, client) {
const code = normalizeBetaInviteCode(value);
if (!code || !client?.query) return false;
const { rows } = await client.query(
"SELECT 1 FROM beta_invite_code_uses WHERE code = $1 LIMIT 1",
[code],
);
return rows.length > 0;
}
async function checkBetaInviteCodeForRegistration(value, client, allowedCodes = loadBetaInviteCodes()) {
const code = normalizeBetaInviteCode(value);
const enterpriseAccount = findEnterpriseBetaAccountByInviteCode(code);
if (enterpriseAccount) {
return {
ok: true,
code,
enterpriseBeta: true,
account: enterpriseAccount,
};
}
if (!code || !allowedCodes.has(code)) {
return { ok: false, status: 403, error: "内测码无效或缺失", code };
}
if (await isBetaInviteCodeUsed(code, client)) {
return { ok: false, status: 409, error: "内测码已被使用", code };
}
return { ok: true, code };
}
async function consumeBetaInviteCode(value, userId, client, allowedCodes = loadBetaInviteCodes()) {
const check = await checkBetaInviteCodeForRegistration(value, client, allowedCodes);
if (!check.ok) return check;
if (check.enterpriseBeta) return check;
const { rows } = await client.query(
`
INSERT INTO beta_invite_code_uses (code, user_id)
VALUES ($1, $2)
ON CONFLICT (code) DO NOTHING
RETURNING code
`,
[check.code, userId || null],
);
if (rows.length === 0) {
return { ok: false, status: 409, error: "内测码已被使用", code: check.code };
}
return { ok: true, code: check.code };
}
module.exports = {
DEFAULT_BETA_INVITE_CODES_FILE,
normalizeBetaInviteCode,
loadBetaInviteCodesFromText,
loadBetaInviteCodes,
getBetaInviteCodeFromBody,
findEnterpriseBetaAccountByInviteCode,
isEnterpriseBetaInviteCode,
validateBetaInviteCode,
validateBetaInviteCodeFromBody,
isBetaInviteCodeUsed,
checkBetaInviteCodeForRegistration,
consumeBetaInviteCode,
};