@lark-project/cli
Version:
飞书项目插件开发工具
88 lines (87 loc) • 4.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireAuth = exports.buildAuthGuidance = exports.ensureRealSiteDomain = void 0;
const auth_config_1 = require("../v2/utils/auth-config");
// siteDomain placeholder patterns — catches AI fabricating a fake site
// (e.g. "https://meego.example.com") instead of asking the user.
const SITE_DOMAIN_PLACEHOLDER_PATTERNS = [
{ re: /example\.(com|org|net)/i, reason: 'example.com/.org/.net is a reserved documentation domain' },
{ re: /\.(test|example|invalid|localhost)(?:[\/:]|$)/i, reason: 'RFC 2606 reserved TLD (never resolvable)' },
{ re: /\byour-(domain|site|host|company|org)\b/i, reason: 'looks like a scaffolding placeholder' },
{ re: /\b(placeholder|sample|mock|fake|dummy|foo\.bar)\b/i, reason: 'contains an obvious placeholder token' },
{ re: /\/\/localhost(?:[:\/]|$)/i, reason: 'localhost is not a real Meego site' },
];
/**
* Reject obviously fake siteDomain values (placeholder patterns). Does NOT
* verify reachability — AI can still invent a real-looking internal hostname.
* But catches the common fabrication mode (example.com, your-domain, etc.).
* On fail: stderr guidance + exit 1.
*/
function ensureRealSiteDomain(siteDomain) {
validateSiteDomainNotPlaceholder(siteDomain);
}
exports.ensureRealSiteDomain = ensureRealSiteDomain;
function validateSiteDomainNotPlaceholder(siteDomain) {
for (const { re, reason } of SITE_DOMAIN_PLACEHOLDER_PATTERNS) {
if (re.test(siteDomain)) {
process.stderr.write([
`Refusing to proceed: siteDomain "${siteDomain}" looks like a placeholder.`,
`Reason: ${reason}.`,
'',
'Ask the user which Meego site they actually use. Common options:',
' - https://project.feishu.cn (飞书项目 / Feishu Project)',
' - https://meegle.com (Meegle)',
' - or the user\'s custom domain',
'',
'Then re-run the command with the real --site-domain <url>.',
].join('\n') + '\n');
process.exit(1);
}
}
}
/**
* Build a user-facing authentication guidance message for the given origin.
* Format is AI-friendly: clean text with no log4js prefix, structured so the AI
* can relay it verbatim to the user (or the user can copy the command directly).
*/
function buildAuthGuidance(origin) {
return [
`Authentication required for ${origin}.`,
'',
'Run ONE of the following to log in:',
'',
' [A] Developer Token (permanent, recommended)',
` 1. Open ${origin}/openapp/settings and copy your Developer Token`,
' 2. Run:',
` lpm login --site-domain ${origin} --token <developer_token>`,
'',
' [B] Device Code OAuth (temporary, auto-refresh)',
` lpm login --site-domain ${origin}`,
' Follow the browser prompt to complete authorization.',
].join('\n');
}
exports.buildAuthGuidance = buildAuthGuidance;
/**
* Preflight credential check for commands that require user identity.
* On failure: writes the guidance to stderr as plain text (bypassing log4js
* formatting so command lines can be copied without prefix noise) and exits 1.
* Token expiry / refresh is still handled in-flight by getToolAuthHeaders().
*/
function requireAuth(siteDomain) {
let origin;
try {
origin = new URL(siteDomain).origin;
}
catch (_a) {
process.stderr.write(`Invalid siteDomain: ${siteDomain}\n`);
process.exit(1);
}
// Hard防线:拒绝假站点,防 AI 默默填充 example.com 之类
validateSiteDomainNotPlaceholder(siteDomain);
const profile = (0, auth_config_1.loadEffectiveProfile)(origin);
if (!profile || !(0, auth_config_1.resolveToken)(profile)) {
process.stderr.write(buildAuthGuidance(origin) + '\n');
process.exit(1);
}
}
exports.requireAuth = requireAuth;