|
|
@@ -0,0 +1,126 @@
|
|
|
+/**
|
|
|
+ * 用账号密码**自动登录 AMS OAuth**,把拿到的 token 写进 `.dms-token`。
|
|
|
+ *
|
|
|
+ * 为什么要有它:DMS token 约 24 小时过期,以前要「打开某个网站 → 登录 → F12 复制 → 粘贴」。
|
|
|
+ * 有了这个脚本就是一条命令(`npm run dms:login`),`npm run dev` 前还会自动续(见 package.json 的 predev)。
|
|
|
+ *
|
|
|
+ * 接口来源:隔壁项目 `zhaoshang_admin_ui` 的 `src/services/auth.ts` —— 那边已实测通过
|
|
|
+ * (`harness/product-specs/auth.md` 的 `auth-ams-001`),本脚本照抄它的请求形态:
|
|
|
+ * POST {OAUTH_TARGET}/oauth/api/user/login
|
|
|
+ * Content-Type: application/x-www-form-urlencoded
|
|
|
+ * body: userName / password / clientId
|
|
|
+ * 响应: { code: 200, message: <token>, content: { id, userName, identityName, failureTime } }
|
|
|
+ * —— 注意 token 在 `message` 里(不是 `content`),无验证码。
|
|
|
+ *
|
|
|
+ * 凭据从哪来(按顺序):
|
|
|
+ * ① 参数:node harness/tools/dms-login.mjs <userName> <password>
|
|
|
+ * ② 环境变量:DMS_USERNAME / DMS_PASSWORD
|
|
|
+ * ③ 本机文件 `.dms-account`(gitignore,两行 `username=` / `password=`)
|
|
|
+ *
|
|
|
+ * 用法:
|
|
|
+ * npm run dms:login # 登录并写入 .dms-token
|
|
|
+ * npm run dms:login -- --if-needed # 当前 token 还有效就跳过(predev 用这个)
|
|
|
+ *
|
|
|
+ * ⚠️ 密码是**明文**存在 `.dms-account` 里的(与 `.env.*.local` 同一性质:本机、不入库)。
|
|
|
+ * 不愿意存密码就别用这个脚本,改用 `npm run dms:token` 从剪贴板粘贴 —— 那条路不碰密码。
|
|
|
+ */
|
|
|
+
|
|
|
+import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
+import { fileURLToPath } from 'node:url';
|
|
|
+import { dirname, join } from 'node:path';
|
|
|
+import { DMS_TOKEN_FILE, readDmsToken, parseJwtPayload, describeDmsToken } from './dms-token.mjs';
|
|
|
+
|
|
|
+const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
|
+/** 本机账号文件(gitignore):两行 `username=` / `password=` */
|
|
|
+const ACCOUNT_FILE = join(repoRoot, '.dms-account');
|
|
|
+
|
|
|
+const OAUTH_TARGET = (process.env.OAUTH_TARGET || 'http://121.43.55.7:10086').replace(/\/+$/, '');
|
|
|
+const CLIENT_ID = process.env.OAUTH_CLIENT_ID || '0';
|
|
|
+
|
|
|
+const args = process.argv.slice(2).filter((a) => !a.startsWith('--'));
|
|
|
+const ifNeeded = process.argv.includes('--if-needed');
|
|
|
+
|
|
|
+/** 简单 KEY=VALUE 解析(够用即可,别为两个字段引入 dotenv) */
|
|
|
+const parseAccountFile = (text) => {
|
|
|
+ const out = {};
|
|
|
+ for (const line of String(text).split(/\r?\n/)) {
|
|
|
+ const m = line.match(/^\s*([A-Za-z_][\w]*)\s*=\s*(.*)$/);
|
|
|
+ if (m) out[m[1].toLowerCase()] = m[2].trim().replace(/^["']|["']$/g, '');
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+};
|
|
|
+
|
|
|
+const readAccount = () => {
|
|
|
+ const fromFile = existsSync(ACCOUNT_FILE) ? parseAccountFile(readFileSync(ACCOUNT_FILE, 'utf8')) : {};
|
|
|
+ return {
|
|
|
+ userName: String(args[0] || process.env.DMS_USERNAME || fromFile.username || '').trim(),
|
|
|
+ password: String(args[1] || process.env.DMS_PASSWORD || fromFile.password || '').trim(),
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+/* ---------------- --if-needed:当前 token 还够用就直接退出 ---------------- */
|
|
|
+if (ifNeeded) {
|
|
|
+ const token = readDmsToken();
|
|
|
+ const exp = Number(parseJwtPayload(token)?.exp);
|
|
|
+ // 留 20 分钟余量:dev server 一开就是几小时,临过期才续会中途断掉
|
|
|
+ if (Number.isFinite(exp) && exp * 1000 - Date.now() > 20 * 60 * 1000) {
|
|
|
+ console.log(`[dms] token 仍有效(${describeDmsToken(token)}),跳过自动登录。`);
|
|
|
+ process.exit(0);
|
|
|
+ }
|
|
|
+ if (!readAccount().userName) {
|
|
|
+ // 没配账号就安静跳过 —— 绝不能因为「不想自动登录」把 npm run dev 拦下来
|
|
|
+ console.log('[dms] 当前没有可用的 DMS token,也没配自动登录账号(见 harness/tools/dms-login.mjs 头注释)。');
|
|
|
+ console.log('[dms] 手动方式:在浏览器复制 token 后跑 `npm run dms:token`。');
|
|
|
+ process.exit(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const { userName, password } = readAccount();
|
|
|
+if (!userName || !password) {
|
|
|
+ console.error(`缺账号或密码。三种给法:`);
|
|
|
+ console.error(` ① 参数:node harness/tools/dms-login.mjs <userName> <password>`);
|
|
|
+ console.error(` ② 环境变量:DMS_USERNAME / DMS_PASSWORD`);
|
|
|
+ console.error(` ③ 本机文件 ${ACCOUNT_FILE},内容两行:`);
|
|
|
+ console.error(` username=你的账号`);
|
|
|
+ console.error(` password=你的密码`);
|
|
|
+ process.exit(1);
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------------- 登录 ---------------- */
|
|
|
+const url = `${OAUTH_TARGET}/oauth/api/user/login`;
|
|
|
+let json = null;
|
|
|
+try {
|
|
|
+ const res = await fetch(url, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
|
+ body: new URLSearchParams({ userName, password, clientId: CLIENT_ID }).toString(),
|
|
|
+ });
|
|
|
+ const text = await res.text();
|
|
|
+ try {
|
|
|
+ json = JSON.parse(text);
|
|
|
+ } catch {
|
|
|
+ console.error(`登录响应不是 JSON(HTTP ${res.status}):${text.slice(0, 200)}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+} catch (err) {
|
|
|
+ console.error(`连不上 OAuth(${url}):${err?.message || err}`);
|
|
|
+ process.exit(1);
|
|
|
+}
|
|
|
+
|
|
|
+if (json?.code !== 200 || !json?.message) {
|
|
|
+ // 常见:202 用户不存在 / 203 密码错误(实际文案以后端返回为准,原样透出便于排查)
|
|
|
+ console.error(`登录失败:code=${json?.code} message=${json?.message ?? '(空)'}`);
|
|
|
+ process.exit(1);
|
|
|
+}
|
|
|
+
|
|
|
+const token = String(json.message).trim();
|
|
|
+if (!parseJwtPayload(token)) {
|
|
|
+ console.error('登录返回的 token 不是 JWT,已中止(不会写入 .dms-token)。');
|
|
|
+ console.error(`原始响应:${JSON.stringify(json).slice(0, 200)}`);
|
|
|
+ process.exit(1);
|
|
|
+}
|
|
|
+
|
|
|
+writeFileSync(DMS_TOKEN_FILE, `${token}\n`, 'utf8');
|
|
|
+console.log(`已登录并写入 ${DMS_TOKEN_FILE}`);
|
|
|
+console.log(` ${describeDmsToken(token)}`);
|
|
|
+console.log('dev server **不用重启**(代理每个请求现读这个文件)。');
|