#!/usr/bin/env node /** * .gitconfig * * [credential] * helper = /bin/git-credential * useHttpPath = true */ const axios = require('axios'); const axiosRetry = require('axios-retry').default; axiosRetry(axios, { retries: 3 }); const action = process.argv.slice(-1)[0]; const log = (string) => { if (process.env.debug) { console.error(string); } } if (action === "store"){ console.error(`[git-credential]: ${action} ignored`); process.exit(0); } if (action === "erase"){ console.error(`[git-credential]: ${action} ignored`); process.exit(0); } if (action === "get"){ // show some log console.error(`[git-credential]: user: ${process.env.PLUGIN_BUILD_USER}`); // 这个不能删 // git 会等待 10 秒 // 否则会让用户输入账号密码,会导致进程挂起 setTimeout(function(){ console.error(`[git-credential]: timeout occurred`); process.exit(1); }, 8000) const buff = []; const finish = function(){ const input = Buffer.concat(buff).toString('UTF-8'); const data = parseText(input, '='); if (data.path.endsWith(".git")) { data.path = data.path.slice(0, -4); } else if (data.path.endsWith(".git/info/lfs")) { data.path = data.path.slice(0, -13); } auth(data).then(() => { process.exit(0); }, (e) => { console.error(e); process.exit(2); }); } process.stdin.on('data', (data) => { buff.push(data) // 回车处理 if(data.length === 1 && data[0] === 0x0a){ finish(); } }); process.stdin.on('end', () => { finish(); }); } async function canIUse(host, slug, username) { if (slug === process.env.PLUGIN_REPO_SLUG) { console.error('[git-credential]: hit self by env'); return true; } if (process.env.PLUGIN_NO_CHECK_AUTH === 'true') { console.error('[git-credential]: hit PLUGIN_NO_CHECK_AUTH'); return true; } if (process.env.PLUGIN_CAN_I_VIEW_URL) { console.error(`[git-credential]: start check permission from ${process.env.PLUGIN_CAN_I_VIEW_URL}`); const res = await axios .post(process.env.PLUGIN_CAN_I_VIEW_URL, { host, slug, username, }) .then(({ data }) => data) .catch((error) => { console.error( `[git-credential]: check permission fail: ${error.message}` ); return { success: false }; }); console.error(`[git-credential]: res: ${JSON.stringify(res)}`); return res.success && res.data.allow; } return false; } /** * { * protocol: 'https', * host: 'git-domain.com', * path: 'git-group/git-repo' * } * @param data object * @param data.protocol string * @param data.host string * @param data.path string */ async function auth(data) { const username = process.env.PLUGIN_BUILD_USER; const { host, path: slug } = data; console.error(`[git-credential]: for ${data.protocol}://${data.host}/${data.path}`); // 必须校验域名,否则传入第三方域名,就会被盗密码了 if (host !== process.env.PLUGIN_GIT_DOAMIN) { throw Error(`unknown host: ${host}`); } const allowView = await canIUse(host, slug, username); console.error(`[git-credential]: allow ${username} view ${allowView}`); if (!allowView) throw Error(`user ${username} can't view this project: ${slug}`); console.log(`username=${process.env.PLUGIN_GIT_USERNAME}`); console.log(`password=${process.env.PLUGIN_GIT_PASSWORD}`); console.error(`[git-credential]: done`); } function parseText(content, separator) { try { return content.split(/(?:\r\n|\r|\n)/) .filter(Boolean) .map((line) => { const key = line.substring(0, line.indexOf(separator)).trim(); const value = line.substring(line.indexOf(separator) + 1).trim(); return { key, value }; }) .reduce((accumulator, { key, value }) => { if (key) accumulator[key] = value; return accumulator; }, {}); } catch (e) { this.throw(`parse plain txt failed. ${e.message}`); } }