Fix sandbox MCP path resolution for session policy sync.

Resolve mindspace-sandbox-mcp.mjs from the code module location instead of h5Root, and copy it into the portal runtime artifact so goosed can spawn sandbox-fs on production.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-26 13:36:05 +08:00
parent d51df2fb0a
commit 9ed4fd48d7
4 changed files with 761 additions and 57 deletions
+374 -43
View File
@@ -1,5 +1,6 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import net from 'node:net';
import path from 'node:path';
import { Algorithm as Argon2Algorithm, hashRawSync as argon2HashRawSync } from '@node-rs/argon2';
import { computeDeltaCostCents, loadBillingConfig, normalizeTokenState } from './billing.mjs';
@@ -12,6 +13,7 @@ import {
DEFAULT_USER_CAPABILITIES,
isValidCapabilityKey,
normalizeCapabilityPatch,
resolveSandboxMcpServerPath,
USER_NON_GRANTABLE_CAPABILITIES,
} from './capabilities.mjs';
import {
@@ -25,9 +27,11 @@ import {
import {
ensurePublishSkillInstalled,
ensureUserPublishLayout,
ensureWorkspaceHintsInstalled,
PUBLISH_ROOT_DIR,
PUBLISH_SKILL_NAME,
resolvePublicBaseUrl,
resolveLegacyPublishDir,
} from './user-publish.mjs';
import {
ensureUserSpaceLayout,
@@ -128,6 +132,9 @@ export function createUserAuth(pool, options = {}) {
const loginMaxFailures = Number(options.loginMaxFailures ?? 5);
const loginFailureWindowMs = Number(options.loginFailureWindowMs ?? 5 * 60 * 1000);
const persistSessions = options.persistSessions !== false && Boolean(pool);
let rechargeNotifier =
typeof options.onRechargeNotification === 'function' ? options.onRechargeNotification : null;
const subscriptionService = options.subscriptionService ?? null;
const sessions = new Map();
const loginFailures = new Map();
@@ -170,23 +177,19 @@ export function createUserAuth(pool, options = {}) {
fs.mkdirSync(workspaceRoot, { recursive: true });
};
const resolveAdminWorkspaceRoot = () => {
const configured = env.H5_ADMIN_WORKSPACE_ROOT?.trim();
if (configured) return path.resolve(configured);
return path.dirname(usersRoot);
};
const isAdminRole = (user) => user?.role === 'admin';
const getUserById = async (userId) => {
const [rows] = await pool.query(
`SELECT u.id, u.username, u.slug, u.email, u.display_name, u.role, u.status,
u.plan_type, u.workspace_root,
s.quota_bytes, s.used_bytes, s.reserved_bytes,
w.balance_cents, w.tokens_used,
(SELECT COALESCE(SUM(ABS(amount_cents)), 0)
FROM h5_billing_ledger l
WHERE l.user_id = u.id AND l.type = 'deduct') AS spent_cents
FROM h5_users u
LEFT JOIN h5_user_spaces s ON s.user_id = u.id
LEFT JOIN h5_user_wallets w ON w.user_id = u.id
WHERE u.id = ?`,
[userId],
@@ -210,8 +213,14 @@ export function createUserAuth(pool, options = {}) {
balanceCents,
totalCreditCents: balanceCents + spentCents,
tokensUsed: Number(row.tokens_used ?? 0),
spaceQuotaBytes: Number(row.quota_bytes ?? 0),
spaceUsedBytes: Number(row.used_bytes ?? 0),
spaceReservedBytes: Number(row.reserved_bytes ?? 0),
spaceAvailableBytes: Math.max(
0,
Number(row.quota_bytes ?? 0) - Number(row.used_bytes ?? 0) - Number(row.reserved_bytes ?? 0),
),
};
if (row.role === 'admin') return base;
const publishKey = row.id;
return {
...base,
@@ -238,13 +247,19 @@ export function createUserAuth(pool, options = {}) {
slug: web.slug,
workspaceRoot: web.publishDir,
});
ensurePublishSkillInstalled(web.publishDir, {
const hintsContext = {
slug: web.slug,
username: user.username ?? web.username,
displayName: user.displayName,
publicBaseUrl,
publishDir: web.publishDir,
});
};
ensurePublishSkillInstalled(web.publishDir, hintsContext);
ensureWorkspaceHintsInstalled(web.publishDir, hintsContext);
const legacyPublishDir = resolveLegacyPublishDir(h5Root, user);
if (legacyPublishDir && legacyPublishDir !== web.publishDir && fs.existsSync(legacyPublishDir)) {
ensureWorkspaceHintsInstalled(legacyPublishDir, { ...hintsContext, publishDir: legacyPublishDir });
}
ensureUserMemoryProfile(web.publishDir, {
userId: user.id,
displayName: user.displayName ?? user.display_name,
@@ -293,7 +308,7 @@ export function createUserAuth(pool, options = {}) {
};
const syncUserPublishWorkspace = async (user) => {
if (!user || user.role === 'admin') return null;
if (!user) return null;
const layout = await publishLayoutFor(user);
const current = path.resolve(user.workspace_root);
const target = path.resolve(layout.publishDir);
@@ -386,6 +401,9 @@ export function createUserAuth(pool, options = {}) {
username: normalized,
slug: normalized,
});
if (subscriptionService) {
subscriptionService.grantSubscription(userId, 'free', null, null, '注册赠送免费套餐').catch(() => {});
}
const user = await getUserById(userId);
return { ok: true, user: publicUser(user) };
} catch (err) {
@@ -582,18 +600,13 @@ export function createUserAuth(pool, options = {}) {
const resolveWorkingDir = async (userId) => {
const user = await getUserById(userId);
if (!user) throw new Error('用户不存在');
if (isAdminRole(user)) {
const root = resolveAdminWorkspaceRoot();
ensureWorkspace(root);
return root;
}
const layout = await syncUserPublishWorkspace(user);
return layout.publishDir;
};
const getUserPublishLayout = async (userId) => {
const user = await getUserById(userId);
if (!user || user.role === 'admin') return null;
if (!user) return null;
return syncUserPublishWorkspace(user);
};
@@ -678,6 +691,29 @@ export function createUserAuth(pool, options = {}) {
if (isAdminRole(user)) {
return { ok: true, balanceCents: Number(user.balance_cents ?? 0) };
}
// Subscription check: active subscription with remaining quota bypasses balance requirement.
if (subscriptionService) {
const sub = await subscriptionService.getActiveSubscription(userId);
if (sub) {
const unlimited = sub.periodTokensLimit === 0;
const hasQuota = unlimited || sub.periodTokensUsed < sub.periodTokensLimit;
const balanceCents = Number(user.balance_cents ?? 0);
if (hasQuota) {
return { ok: true, balanceCents, subscription: sub };
}
// Quota exhausted — allow if balance covers overage, otherwise block.
if (balanceCents > 0) {
return { ok: true, balanceCents, subscription: sub, overQuota: true };
}
return {
ok: false,
message: '本月额度已用完,余额不足,请充值或升级套餐',
...buildInsufficientBalancePayload(balanceCents, loadRechargeConfig()),
};
}
}
const balanceCents = Number(user.balance_cents ?? 0);
if (balanceCents <= 0) {
return {
@@ -709,8 +745,10 @@ export function createUserAuth(pool, options = {}) {
const [rows] = await pool.query(
`SELECT u.id, u.username, u.slug, u.email, u.display_name, u.role, u.status,
u.plan_type, u.workspace_root,
s.quota_bytes, s.used_bytes, s.reserved_bytes,
u.created_at, u.updated_at, w.balance_cents, w.tokens_used
FROM h5_users u
LEFT JOIN h5_user_spaces s ON s.user_id = u.id
LEFT JOIN h5_user_wallets w ON w.user_id = u.id
${where}
ORDER BY u.created_at DESC
@@ -725,6 +763,11 @@ export function createUserAuth(pool, options = {}) {
};
};
const getUserPublic = async (userId) => {
const user = await getUserById(userId);
return user ? publicUser(user) : null;
};
const createUser = async ({
username,
password,
@@ -744,9 +787,7 @@ export function createUserAuth(pool, options = {}) {
const isAdmin = role === 'admin';
const userId = crypto.randomUUID();
const root = isAdmin
? path.resolve(workspaceRoot || path.join(usersRoot, normalized))
: (await publishLayoutFor({ id: userId, username: normalized })).publishDir;
const root = (await publishLayoutFor({ id: userId, username: normalized })).publishDir;
const { salt, passwordHash, passwordAlgorithm } = createPasswordRecord(password);
const now = Date.now();
@@ -790,6 +831,9 @@ export function createUserAuth(pool, options = {}) {
}
await conn.commit();
ensureWorkspace(root);
if (subscriptionService && !isAdmin) {
subscriptionService.grantSubscription(userId, 'free', null, null, '注册赠送免费套餐').catch(() => {});
}
const user = await getUserById(userId);
return { ok: true, user: publicUser(user) };
} catch (err) {
@@ -854,10 +898,156 @@ export function createUserAuth(pool, options = {}) {
);
}
if (patch.spaceQuotaBytes !== undefined) {
const quotaBytes = Math.floor(Number(patch.spaceQuotaBytes));
if (!Number.isFinite(quotaBytes) || quotaBytes <= 0) {
return { ok: false, message: '空间大小无效' };
}
const [spaceRows] = await pool.query(
`SELECT id, quota_bytes, used_bytes, reserved_bytes
FROM h5_user_spaces
WHERE user_id = ?
LIMIT 1`,
[userId],
);
const currentSpace = spaceRows[0];
const occupiedBytes = Number(currentSpace?.used_bytes ?? 0) + Number(currentSpace?.reserved_bytes ?? 0);
if (quotaBytes < occupiedBytes) {
return {
ok: false,
message: `空间不能小于已使用容量 ${Math.ceil(occupiedBytes / 1024 / 1024)} MB`,
};
}
if (currentSpace?.id) {
await pool.query(
`UPDATE h5_user_spaces
SET quota_bytes = ?, updated_at = ?
WHERE user_id = ?`,
[quotaBytes, now, userId],
);
} else {
await initializeDefaultSpace(pool, userId, {
quotaBytes,
now,
});
}
}
const updated = await getUserById(userId);
return { ok: true, user: publicUser(updated) };
};
const purchaseSpaceQuota = async (userId, sizeMb) => {
const purchaseMb = Math.floor(Number(sizeMb));
if (!Number.isFinite(purchaseMb) || purchaseMb <= 0) {
return { ok: false, message: '扩容大小无效' };
}
const deltaBytes = purchaseMb * 1024 * 1024;
const costCents = purchaseMb * 200;
const now = Date.now();
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
const [spaceRows] = await conn.query(
`SELECT id, quota_bytes, used_bytes, reserved_bytes
FROM h5_user_spaces
WHERE user_id = ?
LIMIT 1
FOR UPDATE`,
[userId],
);
if (!spaceRows[0]) {
await initializeDefaultSpace(conn, userId, { now });
}
const [walletRows] = await conn.query(
`SELECT balance_cents
FROM h5_user_wallets
WHERE user_id = ?
FOR UPDATE`,
[userId],
);
const balanceCents = Number(walletRows[0]?.balance_cents ?? 0);
if (balanceCents < costCents) {
await conn.rollback();
return {
ok: false,
code: 'INSUFFICIENT_BALANCE',
message: '余额不足,请先充值后再购买空间',
balanceCents,
minRechargeCents: Math.max(500, costCents - balanceCents),
suggestedTiers: loadRechargeConfig().tiersCents,
};
}
await conn.query(
`UPDATE h5_user_wallets
SET balance_cents = balance_cents - ?, updated_at = ?
WHERE user_id = ?`,
[costCents, now, userId],
);
await conn.query(
`UPDATE h5_user_spaces
SET quota_bytes = quota_bytes + ?, updated_at = ?
WHERE user_id = ?`,
[deltaBytes, now, userId],
);
await conn.query(
`INSERT INTO h5_billing_ledger
(user_id, type, amount_cents, tokens, note, operator_id, created_at)
VALUES (?, 'deduct', ?, 0, ?, NULL, ?)`,
[userId, costCents, `space_purchase:${purchaseMb}MB`, now],
);
await conn.query(
`INSERT INTO h5_user_notifications
(id, user_id, channel, notification_type, title, body, data_json, status, read_at, created_at, updated_at)
VALUES (?, ?, 'web', 'space_purchase', ?, ?, ?, 'unread', NULL, ?, ?)`,
[
crypto.randomUUID(),
userId,
'空间扩容成功',
`已购买 ${purchaseMb} MB 空间,支付 ¥${(costCents / 100).toFixed(2)}`,
JSON.stringify({ purchaseMb, deltaBytes, costCents }),
now,
now,
],
);
await conn.commit();
const [updatedSpaceRows] = await pool.query(
`SELECT quota_bytes, used_bytes, reserved_bytes
FROM h5_user_spaces
WHERE user_id = ?
LIMIT 1`,
[userId],
);
const updatedSpace = updatedSpaceRows[0] ?? {};
const updatedUser = await getUserById(userId);
return {
ok: true,
balanceCents: Number(updatedUser?.balance_cents ?? Math.max(0, balanceCents - costCents)),
quota: {
quotaBytes: Number(updatedSpace.quota_bytes ?? 0),
usedBytes: Number(updatedSpace.used_bytes ?? 0),
reservedBytes: Number(updatedSpace.reserved_bytes ?? 0),
availableBytes: Math.max(
0,
Number(updatedSpace.quota_bytes ?? 0)
- Number(updatedSpace.used_bytes ?? 0)
- Number(updatedSpace.reserved_bytes ?? 0),
),
},
};
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
};
const recharge = async (userId, amountCents, operatorId, note = '', options = {}) => {
const amount = Number(amountCents);
if (!Number.isFinite(amount) || amount <= 0) {
@@ -870,6 +1060,7 @@ export function createUserAuth(pool, options = {}) {
const ledgerNote = paymentOrderId
? `order:${paymentOrderId}`
: note || (operatorId ? '管理员充值' : '账户充值');
const rechargeType = paymentOrderId ? 'self_recharge' : operatorId ? 'admin_recharge' : 'recharge';
const now = Date.now();
const ownsConnection = !options.conn;
const conn = options.conn ?? (await pool.getConnection());
@@ -889,6 +1080,31 @@ export function createUserAuth(pool, options = {}) {
VALUES (?, 'recharge', ?, 0, ?, ?, ?)`,
[userId, amount, ledgerNote, operatorId, now],
);
const title = paymentOrderId ? '充值成功' : operatorId ? '管理员已充值' : '账户充值成功';
const body = paymentOrderId
? `你已成功充值 ¥${(amount / 100).toFixed(2)},余额已更新。`
: operatorId
? `管理员已为你充值 ¥${(amount / 100).toFixed(2)},余额已更新。`
: `你的账户已充值 ¥${(amount / 100).toFixed(2)},余额已更新。`;
await conn.query(
`INSERT INTO h5_user_notifications
(id, user_id, channel, notification_type, title, body, data_json, status, read_at, created_at, updated_at)
VALUES (?, ?, 'web', ?, ?, ?, ?, 'unread', NULL, ?, ?)`,
[
crypto.randomUUID(),
userId,
rechargeType,
title,
body,
JSON.stringify({
amountCents: amount,
operatorId: operatorId ?? null,
paymentOrderId,
}),
now,
now,
],
);
if (user.status === 'suspended') {
await conn.query(`UPDATE h5_users SET status = 'active', updated_at = ? WHERE id = ?`, [
now,
@@ -897,6 +1113,25 @@ export function createUserAuth(pool, options = {}) {
}
if (ownsConnection) await conn.commit();
const updated = await getUserById(userId);
if (rechargeNotifier) {
try {
await rechargeNotifier({
userId,
amountCents: amount,
operatorId: operatorId ?? null,
paymentOrderId,
notificationType: rechargeType,
title,
body,
user: publicUser(updated),
});
} catch (err) {
console.warn(
'Recharge notifier failed:',
err instanceof Error ? err.message : String(err),
);
}
}
return { ok: true, user: publicUser(updated) };
} catch (err) {
if (ownsConnection) await conn.rollback();
@@ -956,7 +1191,7 @@ export function createUserAuth(pool, options = {}) {
};
}
const config = loadBillingConfig();
const costCents = computeDeltaCostCents(previous, tokenState, config);
let costCents = computeDeltaCostCents(previous, tokenState, config);
const deltaIn = Math.max(
0,
tokenState.accumulatedInputTokens - Number(previous?.lastInputTokens ?? 0),
@@ -991,6 +1226,16 @@ export function createUserAuth(pool, options = {}) {
],
);
// Subscription quota check: consume tokens from active plan before touching balance.
if (costCents > 0 && subscriptionService) {
const coverage = await subscriptionService.consumeQuota(userId, deltaTokens, conn);
if (coverage.fullyCovers) {
costCents = 0;
} else if (coverage.overageRate < 1.0) {
costCents = Math.max(1, Math.ceil(costCents * coverage.overageRate));
}
}
let balanceAfter = null;
let tokensUsedAfter = null;
if (costCents > 0) {
@@ -1339,9 +1584,12 @@ export function createUserAuth(pool, options = {}) {
const resolveUserCapabilities = async (user) => {
if (!user) return { unrestricted: true, capabilities: {} };
if (user.role === 'admin') {
const skillMap = Object.fromEntries(skillCatalog.map((item) => [item.name, true]));
return {
unrestricted: true,
capabilities: Object.fromEntries(catalogKeys().map((key) => [key, true])),
skills: skillMap,
grantedSkills: grantedSkillNames(skillMap),
};
}
@@ -1384,15 +1632,17 @@ export function createUserAuth(pool, options = {}) {
capabilityState.capabilities,
policyState.policies,
);
// For static_publish users, wire up the sandbox MCP so file operations are
// enforced at the OS level rather than relying on text prompt constraints.
// Wire up the sandbox MCP for user workspace tools and the private data space.
// File operations are OS-bound to the user's workspace; private_data_* tools
// only touch the user's single SQLite data space inside that workspace.
let sandboxMcp = null;
if (effectiveCapabilities.static_publish) {
if (effectiveCapabilities.static_publish || effectiveCapabilities.private_data_space) {
try {
const layout = await publishLayoutFor(user, { migrateLegacy: false });
sandboxMcp = {
serverPath: path.join(h5Root, 'mindspace-sandbox-mcp.mjs'),
serverPath: resolveSandboxMcpServerPath(),
sandboxRoot: layout.publishDir,
userId: user.id,
};
} catch (err) {
console.warn('[getAgentSessionPolicy] sandbox MCP setup failed, falling back:', err?.message);
@@ -1621,40 +1871,45 @@ export function createUserAuth(pool, options = {}) {
const ensureAdminUser = async () => {
const adminUsername = normalizeUsername(process.env.H5_ADMIN_USERNAME ?? 'admin');
const adminPassword = process.env.H5_ADMIN_PASSWORD;
if (!adminPassword) return;
const [rows] = await pool.query(`SELECT id FROM h5_users WHERE username = ? LIMIT 1`, [
adminUsername,
]);
const adminWorkspace = resolveAdminWorkspaceRoot();
if (rows.length === 0) {
if (!adminPassword) return;
await createUser({
username: adminUsername,
password: adminPassword,
displayName: '管理员',
workspaceRoot: adminWorkspace,
balanceCents: 9_999_999_99,
role: 'admin',
});
} else {
const adminId = rows[0].id;
const now = Date.now();
const adminLayout = await publishLayoutFor({
id: adminId,
username: adminUsername,
displayName: '管理员',
});
await pool.query(`UPDATE h5_users SET workspace_root = ?, updated_at = ? WHERE id = ?`, [
adminWorkspace,
adminLayout.publishDir,
now,
adminId,
]);
await pool.query(`DELETE FROM h5_user_path_grants WHERE user_id = ?`, [adminId]);
await pool.query(
`INSERT INTO h5_user_path_grants (user_id, path, mode) VALUES (?, ?, 'readwrite')`,
[adminId, adminWorkspace],
[adminId, adminLayout.publishDir],
);
await pool.query(
`UPDATE h5_user_wallets SET balance_cents = GREATEST(balance_cents, ?), updated_at = ? WHERE user_id = ?`,
[9_999_999_99, now, adminId],
);
}
await syncAdminPassword();
if (adminPassword) {
await syncAdminPassword();
}
await seedRoleCapabilityDefaults();
await upgradeMemoryStoreCapability();
await upgradeDefaultUserCapabilities();
@@ -1768,24 +2023,20 @@ export function createUserAuth(pool, options = {}) {
status = 'active',
now = Date.now(),
}) => {
const existing = await getWechatAgentRoute(appId, openid);
if (existing) {
await pool.query(
`UPDATE h5_wechat_agent_routes
SET user_id = ?, agent_session_id = ?, status = ?, updated_at = ?
WHERE app_id = ? AND openid = ?`,
[userId, agentSessionId, status, now, appId, openid],
);
return existing.id;
}
const id = crypto.randomUUID();
await pool.query(
`INSERT INTO h5_wechat_agent_routes
(id, user_id, app_id, openid, agent_session_id, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
user_id = VALUES(user_id),
agent_session_id = VALUES(agent_session_id),
status = VALUES(status),
updated_at = VALUES(updated_at)`,
[id, userId, appId, openid, agentSessionId, status, now, now],
);
return id;
const route = await getWechatAgentRoute(appId, openid);
return route?.id ?? id;
};
const clearWechatAgentRoute = async (appId, openid) => {
@@ -1842,6 +2093,62 @@ export function createUserAuth(pool, options = {}) {
);
};
const insertWechatMpMessageDetail = async ({
appId,
openid,
userId = null,
msgId = null,
msgType,
displayText = '',
agentText = '',
mediaId = null,
mediaUrl = null,
mediaPublicUrl = null,
mediaFormat = null,
locationLat = null,
locationLng = null,
locationLabel = null,
linkUrl = null,
linkTitle = null,
rawXmlHash = null,
rawJson = null,
now = Date.now(),
}) => {
if (!appId || !openid || !msgType) return null;
const id = crypto.randomUUID();
await pool.query(
`INSERT INTO h5_wechat_mp_message_details
(id, app_id, openid, user_id, msg_id, msg_type, display_text, agent_text,
media_id, media_url, media_public_url, media_format,
location_lat, location_lng, location_label,
link_url, link_title, raw_xml_hash, raw_json, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
id,
appId,
openid,
userId,
msgId ? String(msgId) : null,
String(msgType),
displayText || null,
agentText || null,
mediaId,
mediaUrl,
mediaPublicUrl,
mediaFormat,
locationLat,
locationLng,
locationLabel,
linkUrl,
linkTitle,
rawXmlHash,
rawJson ? JSON.stringify(rawJson) : null,
now,
],
);
return id;
};
const bindWechatToUser = async ({
userId,
appId,
@@ -2082,6 +2389,9 @@ export function createUserAuth(pool, options = {}) {
username: normalized,
slug: normalized,
});
if (subscriptionService) {
subscriptionService.grantSubscription(userId, 'free', null, null, '注册赠送免费套餐').catch(() => {});
}
const user = await getUserById(userId);
return { ok: true, user: publicUser(user) };
} catch (err) {
@@ -2313,12 +2623,16 @@ export function createUserAuth(pool, options = {}) {
getWechatPendingBind,
getWechatBindingStatus,
getWechatOpenidForUser,
setRechargeNotifier(callback) {
rechargeNotifier = typeof callback === 'function' ? callback : null;
},
findWechatUserByOpenid,
getWechatAgentRoute,
upsertWechatAgentRoute,
clearWechatAgentRoute,
recordWechatMpMessage,
finishWechatMpMessage,
insertWechatMpMessageDetail,
resetPassword,
verify,
revoke,
@@ -2335,9 +2649,12 @@ export function createUserAuth(pool, options = {}) {
ownsSession,
listOwnedSessionIds,
canUseChat,
getUserById,
getUserPublic,
listUsers,
createUser,
updateUser,
purchaseSpaceQuota,
recharge,
billSessionUsage,
listUsageRecords,
@@ -2449,12 +2766,26 @@ export function resolveCookieDomainForRequest(req) {
if (hostname.endsWith('.localhost')) {
return '.localhost';
}
if (
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname === '::1' ||
net.isIP(hostname)
) {
return null;
}
}
const hostname = String(req?.get?.('host') ?? req?.hostname ?? '')
.split(':')[0]
.toLowerCase();
if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
if (
!hostname ||
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname === '::1' ||
net.isIP(hostname)
) {
return null;
}
return resolveCookieDomain();