xtra-cli 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/xtra.js +3 -137
- package/dist/commands/access.js +1 -184
- package/dist/commands/admin.js +5 -118
- package/dist/commands/audit.js +1 -67
- package/dist/commands/branch.js +1 -212
- package/dist/commands/checkout.js +1 -80
- package/dist/commands/ci.js +31 -341
- package/dist/commands/completion.js +85 -224
- package/dist/commands/diff.js +6 -162
- package/dist/commands/doctor.js +2 -164
- package/dist/commands/env.js +2 -70
- package/dist/commands/export.js +2 -83
- package/dist/commands/generate.js +3 -179
- package/dist/commands/history.js +4 -77
- package/dist/commands/import.js +1 -121
- package/dist/commands/init.js +7 -205
- package/dist/commands/integration.js +11 -188
- package/dist/commands/jit-run.js +5 -0
- package/dist/commands/local.js +16 -198
- package/dist/commands/login.js +16 -198
- package/dist/commands/login.test.js +16 -51
- package/dist/commands/logout.js +1 -0
- package/dist/commands/logs.js +3 -121
- package/dist/commands/profile.js +14 -184
- package/dist/commands/project.js +1 -165
- package/dist/commands/rollback.js +1 -95
- package/dist/commands/rotate.js +3 -93
- package/dist/commands/run.js +11 -215
- package/dist/commands/scan.js +25 -127
- package/dist/commands/secrets.js +14 -305
- package/dist/commands/simulate.js +6 -109
- package/dist/commands/status.js +2 -93
- package/dist/commands/template.js +22 -276
- package/dist/commands/ui.js +2 -289
- package/dist/commands/watch.js +17 -123
- package/dist/lib/api.js +1 -187
- package/dist/lib/api.test.js +1 -89
- package/dist/lib/audit.js +1 -136
- package/dist/lib/config.js +1 -72
- package/dist/lib/config.test.js +1 -47
- package/dist/lib/crypto.js +1 -50
- package/dist/lib/manifest.js +1 -52
- package/dist/lib/profiles.js +1 -103
- package/package.json +7 -2
package/dist/commands/watch.js
CHANGED
|
@@ -1,123 +1,17 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const api_1 = require("../lib/api");
|
|
19
|
-
const config_1 = require("../lib/config");
|
|
20
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
21
|
-
const child_process_1 = require("child_process");
|
|
22
|
-
let child = null;
|
|
23
|
-
let lastHash = "";
|
|
24
|
-
function hashSecrets(secrets) {
|
|
25
|
-
return JSON.stringify(Object.keys(secrets).sort().map(k => `${k}=${secrets[k]}`));
|
|
26
|
-
}
|
|
27
|
-
async function startProcess(command, args, secrets, useShell) {
|
|
28
|
-
if (child) {
|
|
29
|
-
process.stdout.write(chalk_1.default.cyan("\n [watch] ") + chalk_1.default.yellow("Secret change detected — restarting...\n"));
|
|
30
|
-
child.kill("SIGTERM");
|
|
31
|
-
// Give it 500ms to terminate gracefully
|
|
32
|
-
await new Promise(r => setTimeout(r, 500));
|
|
33
|
-
}
|
|
34
|
-
const envVars = { ...process.env, ...secrets };
|
|
35
|
-
process.stdout.write(chalk_1.default.cyan(" [watch] ") + chalk_1.default.green(`Starting: ${command} ${args.join(" ")}\n`));
|
|
36
|
-
child = (0, child_process_1.spawn)(command, args, {
|
|
37
|
-
env: envVars,
|
|
38
|
-
stdio: "inherit",
|
|
39
|
-
shell: useShell,
|
|
40
|
-
});
|
|
41
|
-
child.on("exit", (code) => {
|
|
42
|
-
if (code !== null && code !== 0) {
|
|
43
|
-
process.stdout.write(chalk_1.default.cyan(" [watch] ") + chalk_1.default.red(`Process exited with code ${code}\n`));
|
|
44
|
-
}
|
|
45
|
-
child = null;
|
|
46
|
-
});
|
|
47
|
-
child.on("error", (err) => {
|
|
48
|
-
process.stdout.write(chalk_1.default.cyan(" [watch] ") + chalk_1.default.red(`Failed to start: ${err.message}\n`));
|
|
49
|
-
child = null;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
exports.watchCommand = new commander_1.Command("watch")
|
|
53
|
-
.description("Live reload — auto-restart process when secrets change in cloud")
|
|
54
|
-
.option("-p, --project <id>", "Project ID")
|
|
55
|
-
.option("-e, --env <environment>", "Environment", "development")
|
|
56
|
-
.option("-b, --branch <branch>", "Branch", "main")
|
|
57
|
-
.option("--interval <seconds>", "Poll interval in seconds", "5")
|
|
58
|
-
.option("--shell", "Use shell mode for the child process (for npm run etc.)", false)
|
|
59
|
-
.argument("<command>", "Command to run")
|
|
60
|
-
.argument("[args...]", "Command arguments")
|
|
61
|
-
.addHelpText("after", `
|
|
62
|
-
Examples:
|
|
63
|
-
$ xtra watch -p proj123 -e dev node app.js
|
|
64
|
-
$ xtra watch -p proj123 -e dev --interval 10 --shell npm run dev
|
|
65
|
-
$ xtra watch -p proj123 -e dev -- node -r dotenv/config server.js
|
|
66
|
-
`)
|
|
67
|
-
.action(async (command, args, options) => {
|
|
68
|
-
let { project, env, branch, interval, shell: useShell } = options;
|
|
69
|
-
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
70
|
-
env = envMap[env] || env;
|
|
71
|
-
if (!project)
|
|
72
|
-
project = (0, config_1.getRcConfig)().project;
|
|
73
|
-
if (!project) {
|
|
74
|
-
console.error(chalk_1.default.red("Error: Project ID required. Use -p <id> or run 'xtra project set'."));
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
// Block production watch — too dangerous
|
|
78
|
-
if (env === "production") {
|
|
79
|
-
console.error(chalk_1.default.red("⚠ xtra watch is not allowed in PRODUCTION for safety reasons."));
|
|
80
|
-
console.error(chalk_1.default.gray(" Use 'xtra run' for one-shot production injection."));
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
const pollMs = Math.max(3, parseInt(interval)) * 1000;
|
|
84
|
-
const isWindows = process.platform === "win32";
|
|
85
|
-
const finalShell = useShell || isWindows;
|
|
86
|
-
console.log(chalk_1.default.bold.cyan("\n● xtra watch") + chalk_1.default.bold(` — watching ${env}/${branch} (every ${interval}s)\n`));
|
|
87
|
-
console.log(chalk_1.default.gray(` Press Ctrl+C to stop.\n`));
|
|
88
|
-
// Graceful shutdown
|
|
89
|
-
process.on("SIGINT", () => {
|
|
90
|
-
if (child)
|
|
91
|
-
child.kill("SIGTERM");
|
|
92
|
-
console.log(chalk_1.default.cyan("\n [watch] ") + chalk_1.default.gray("Stopped."));
|
|
93
|
-
process.exit(0);
|
|
94
|
-
});
|
|
95
|
-
// Initial fetch & start
|
|
96
|
-
try {
|
|
97
|
-
const secrets = await api_1.api.getSecrets(project, env, branch);
|
|
98
|
-
lastHash = hashSecrets(secrets);
|
|
99
|
-
await startProcess(command, args, secrets, finalShell);
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
102
|
-
console.error(chalk_1.default.red("Failed to fetch secrets: " + (e?.response?.data?.error || e.message)));
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
// Poll loop
|
|
106
|
-
setInterval(async () => {
|
|
107
|
-
try {
|
|
108
|
-
const secrets = await api_1.api.getSecrets(project, env, branch);
|
|
109
|
-
const hash = hashSecrets(secrets);
|
|
110
|
-
if (hash !== lastHash) {
|
|
111
|
-
lastHash = hash;
|
|
112
|
-
await startProcess(command, args, secrets, finalShell);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
process.stdout.write(chalk_1.default.cyan(" [watch] ") + chalk_1.default.gray(`${new Date().toLocaleTimeString()} — no changes \r`));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (e) {
|
|
119
|
-
process.stdout.write(chalk_1.default.cyan("\n [watch] ") + chalk_1.default.yellow(`Poll failed: ${e.message}\n`));
|
|
120
|
-
// Don't exit — keep trying
|
|
121
|
-
}
|
|
122
|
-
}, pollMs);
|
|
123
|
-
});
|
|
1
|
+
var N=Object.create;var d=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var E=(e,t)=>{for(var s in t)d(e,s,{get:t[s],enumerable:!0})},S=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of D(t))!J.call(e,o)&&o!==s&&d(e,o,{get:()=>t[o],enumerable:!(n=B(t,o))||n.enumerable});return e};var l=(e,t,s)=>(s=e!=null?N(q(e)):{},S(t||!e||!e.__esModule?d(s,"default",{value:e,enumerable:!0}):s,e)),M=e=>S(d({},"__esModule",{value:!0}),e);var H={};E(H,{watchCommand:()=>F});module.exports=M(H);var T=require("commander");var C=l(require("axios"));var x=l(require("conf")),m=l(require("fs")),k=l(require("path")),w="https://www.xtrasecurity.in/api",g=new x.default({projectName:"xtra-cli",defaults:{apiUrl:w}}),h=g.get("apiUrl");h&&!process.env.XTRA_API_URL&&(h.includes("localhost")||h.includes("xtra-security.vercel.app"))&&g.set("apiUrl",w);var u=()=>({...g.store,apiUrl:process.env.XTRA_API_URL||g.get("apiUrl")||w});var b=()=>g.get("token");var I=()=>{let e={};try{let t=k.default.join(process.cwd(),".xtrarc");m.default.existsSync(t)&&(e=JSON.parse(m.default.readFileSync(t,"utf-8")))}catch{}return{project:e.project||g.get("project")||"",env:e.env||g.get("env")||"development",branch:e.branch||g.get("branch")||"main"}};var P=l(require("fs")),R=l(require("path")),V=JSON.parse(P.readFileSync(R.join(__dirname,"../../package.json"),"utf-8")),r=()=>{let{apiUrl:e}=u(),t=b();return C.default.create({baseURL:e,headers:{"Content-Type":"application/json","X-CLI-Version":V.version,...t?{Authorization:`Bearer ${t}`}:{}}})},f={login:async(e,t,s)=>(await r().post("/auth/cli-login",{email:e,password:t,apiKey:s})).data,getSecrets:async(e,t,s="main")=>(await r().get(`/projects/${e}/envs/${t}/secrets?branch=${s}`)).data,getSecretVersions:async(e,t,s="main")=>(await r().get(`/projects/${e}/envs/${t}/secrets?includeVersions=true&branch=${s}`)).data,setSecrets:async(e,t,s,n,o="main")=>(await r().post(`/projects/${e}/envs/${t}/secrets`,{secrets:s,expectedVersions:n,branch:o})).data,getSecretDetails:async(e,t,s,n="main")=>(await r().get(`/projects/${e}/envs/${t}/secrets/${s}?branch=${n}`)).data,syncLogs:async e=>(await r().post("/audit/cli-logs",{logs:e})).data,linkSecret:async(e,t,s,n,o,i)=>(await r().post(`/projects/${e}/envs/${t}/secrets/link`,{key:s,sourceProjectId:n,sourceEnv:o,sourceKey:i})).data,rotateSecret:async(e,t,s,n,o)=>(await r().post(`/projects/${e}/envs/${t}/secrets/${s}/rotate`,{strategy:n,parsedNewValue:o})).data,promoteSecret:async(e,t,s)=>(await r().post(`/projects/${e}/envs/${t}/secrets/${s}/promote`,{})).data,verifyAuditLogs:async()=>(await r().get("/audit/verify")).data,exportAuditLogs:async(e,t,s,n)=>{let o={format:e};return t&&(o.startDate=t),s&&(o.endDate=s),n&&(o.projectId=n),(await r().post("/audit/export",o,{responseType:e==="csv"?"text":"json"})).data},requestAccess:async(e,t,s,n)=>(await r().post("/access/request",{projectId:e,secretId:n,reason:t,duration:s})).data,approveAccess:async(e,t)=>(await r().post("/access/approve",{requestId:e,decision:t})).data,listAccessRequests:async e=>{let{workspace:t}=u(),s=t?`&workspaceId=${t}`:"";return(await r().get(`/access/list?mode=${e}${s}`)).data},getAccessRequestStatus:async e=>(await r().get(`/access/request?id=${e}`)).data,getProjects:async()=>{let{workspace:e}=u(),t=e?`/project?workspaceId=${e}`:"/project";return(await r().get(t)).data},getBranches:async e=>(await r().get(`/branch?projectId=${e}`)).data,createBranch:async(e,t,s)=>(await r().post("/branch",{projectId:e,name:t,description:s})).data,deleteBranch:async e=>(await r().delete(`/branch/${e}`)).data,updateBranch:async(e,t)=>(await r().put("/branch",{id:e,...t})).data,getRoles:async()=>(await r().get("/admin/roles")).data,getUsers:async e=>{let t=e?`?teamId=${e}`:"";return(await r().get(`/admin/users${t}`)).data},setUserRole:async(e,t,s)=>(await r().put("/admin/users/role",{email:e,role:t,teamId:s})).data,getIntegrationStatus:async e=>(await r().get(`/integrations/${e}`)).data,getIntegrationRepos:async e=>(await r().get(`/integrations/${e}/sync`)).data.repos,syncSecretsToGithub:async e=>(await r().post("/integrations/github/sync",e)).data,exportKubernetesSecret:async(e,t)=>{let s=new URLSearchParams(t).toString();return(await r().get(`/projects/${e}/kubernetes?${s}`)).data},getSecretHistory:async(e,t,s)=>(await r().get(`/projects/${e}/envs/${t}/secrets/${s}/history`)).data,rollbackSecret:async(e,t,s,n)=>(await r().post(`/projects/${e}/envs/${t}/secrets/${s}/history`,{version:n})).data,cloneEnvironment:async(e,t,s,n,o)=>(await r().post(`/projects/${e}/envs/clone`,{fromEnv:t,toEnv:s,overwrite:n,branch:o})).data,generateJitLink:async e=>(await r().post("/jit/generate",e)).data,claimJitLink:async e=>(await r().post("/jit/claim",{token:e})).data,getJitInfo:async e=>(await r().get(`/jit/${e}`)).data,post:async(e,t)=>(await r().post(e,t)).data};var a=l(require("chalk")),L=require("child_process"),p=null,v="";function U(e){return JSON.stringify(Object.keys(e).sort().map(t=>`${t}=${e[t]}`))}async function A(e,t,s,n){p&&(process.stdout.write(a.default.cyan(`
|
|
2
|
+
[watch] `)+a.default.yellow(`Secret change detected \u2014 restarting...
|
|
3
|
+
`)),p.kill("SIGTERM"),await new Promise(i=>setTimeout(i,500)));let o={...process.env,...s};process.stdout.write(a.default.cyan(" [watch] ")+a.default.green(`Starting: ${e} ${t.join(" ")}
|
|
4
|
+
`)),p=(0,L.spawn)(e,t,{env:o,stdio:"inherit",shell:n}),p.on("exit",i=>{i!==null&&i!==0&&process.stdout.write(a.default.cyan(" [watch] ")+a.default.red(`Process exited with code ${i}
|
|
5
|
+
`)),p=null}),p.on("error",i=>{process.stdout.write(a.default.cyan(" [watch] ")+a.default.red(`Failed to start: ${i.message}
|
|
6
|
+
`)),p=null})}var F=new T.Command("watch").description("Live reload \u2014 auto-restart process when secrets change in cloud").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment","development").option("-b, --branch <branch>","Branch","main").option("--interval <seconds>","Poll interval in seconds","5").option("--shell","Use shell mode for the child process (for npm run etc.)",!1).argument("<command>","Command to run").argument("[args...]","Command arguments").addHelpText("after",`
|
|
7
|
+
Examples:
|
|
8
|
+
$ xtra watch -p proj123 -e dev node app.js
|
|
9
|
+
$ xtra watch -p proj123 -e dev --interval 10 --shell npm run dev
|
|
10
|
+
$ xtra watch -p proj123 -e dev -- node -r dotenv/config server.js
|
|
11
|
+
`).action(async(e,t,s)=>{let{project:n,env:o,branch:i,interval:y,shell:_}=s;o={dev:"development",stg:"staging",prod:"production"}[o]||o,n||(n=I().project),n||(console.error(a.default.red("Error: Project ID required. Use -p <id> or run 'xtra project set'.")),process.exit(1)),o==="production"&&(console.error(a.default.red("\u26A0 xtra watch is not allowed in PRODUCTION for safety reasons.")),console.error(a.default.gray(" Use 'xtra run' for one-shot production injection.")),process.exit(1));let G=Math.max(3,parseInt(y))*1e3,O=process.platform==="win32",$=_||O;console.log(a.default.bold.cyan(`
|
|
12
|
+
\u25CF xtra watch`)+a.default.bold(` \u2014 watching ${o}/${i} (every ${y}s)
|
|
13
|
+
`)),console.log(a.default.gray(` Press Ctrl+C to stop.
|
|
14
|
+
`)),process.on("SIGINT",()=>{p&&p.kill("SIGTERM"),console.log(a.default.cyan(`
|
|
15
|
+
[watch] `)+a.default.gray("Stopped.")),process.exit(0)});try{let c=await f.getSecrets(n,o,i);v=U(c),await A(e,t,c,$)}catch(c){console.error(a.default.red("Failed to fetch secrets: "+(c?.response?.data?.error||c.message))),process.exit(1)}setInterval(async()=>{try{let c=await f.getSecrets(n,o,i),j=U(c);j!==v?(v=j,await A(e,t,c,$)):process.stdout.write(a.default.cyan(" [watch] ")+a.default.gray(`${new Date().toLocaleTimeString()} \u2014 no changes \r`))}catch(c){process.stdout.write(a.default.cyan(`
|
|
16
|
+
[watch] `)+a.default.yellow(`Poll failed: ${c.message}
|
|
17
|
+
`))}},G)});0&&(module.exports={watchCommand});
|
package/dist/lib/api.js
CHANGED
|
@@ -1,187 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.api = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const config_1 = require("./config");
|
|
9
|
-
const getClient = () => {
|
|
10
|
-
const { apiUrl } = (0, config_1.getConfig)();
|
|
11
|
-
const token = (0, config_1.getAuthToken)();
|
|
12
|
-
const client = axios_1.default.create({
|
|
13
|
-
baseURL: apiUrl,
|
|
14
|
-
headers: {
|
|
15
|
-
"Content-Type": "application/json",
|
|
16
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
return client;
|
|
20
|
-
};
|
|
21
|
-
exports.api = {
|
|
22
|
-
login: async (email, password, apiKey) => {
|
|
23
|
-
const response = await getClient().post("/auth/cli-login", {
|
|
24
|
-
email,
|
|
25
|
-
password,
|
|
26
|
-
apiKey,
|
|
27
|
-
});
|
|
28
|
-
return response.data;
|
|
29
|
-
},
|
|
30
|
-
// Placeholders for future methods
|
|
31
|
-
getSecrets: async (projectId, env, branch = "main") => {
|
|
32
|
-
// Fixed path to match Next.js dynamic route folder structure
|
|
33
|
-
const response = await getClient().get(`/projects/${projectId}/envs/${env}/secrets?branch=${branch}`);
|
|
34
|
-
return response.data;
|
|
35
|
-
},
|
|
36
|
-
getSecretVersions: async (projectId, env, branch = "main") => {
|
|
37
|
-
const response = await getClient().get(`/projects/${projectId}/envs/${env}/secrets?includeVersions=true&branch=${branch}`);
|
|
38
|
-
return response.data;
|
|
39
|
-
},
|
|
40
|
-
setSecrets: async (projectId, env, secrets, expectedVersions, branch = "main") => {
|
|
41
|
-
const response = await getClient().post(`/projects/${projectId}/envs/${env}/secrets`, { secrets, expectedVersions, branch });
|
|
42
|
-
return response.data;
|
|
43
|
-
},
|
|
44
|
-
getSecretDetails: async (projectId, env, key, branch = "main") => {
|
|
45
|
-
const response = await getClient().get(`/projects/${projectId}/envs/${env}/secrets/${key}?branch=${branch}`);
|
|
46
|
-
return response.data;
|
|
47
|
-
},
|
|
48
|
-
syncLogs: async (logs) => {
|
|
49
|
-
const response = await getClient().post("/audit/cli-logs", { logs });
|
|
50
|
-
return response.data;
|
|
51
|
-
},
|
|
52
|
-
linkSecret: async (projectId, env, key, sourceProjectId, sourceEnv, sourceKey) => {
|
|
53
|
-
const response = await getClient().post(`/projects/${projectId}/envs/${env}/secrets/link`, {
|
|
54
|
-
key,
|
|
55
|
-
sourceProjectId,
|
|
56
|
-
sourceEnv,
|
|
57
|
-
sourceKey
|
|
58
|
-
});
|
|
59
|
-
return response.data;
|
|
60
|
-
},
|
|
61
|
-
rotateSecret: async (projectId, env, key, strategy, parsedNewValue) => {
|
|
62
|
-
const response = await getClient().post(`/projects/${projectId}/envs/${env}/secrets/${key}/rotate`, {
|
|
63
|
-
strategy,
|
|
64
|
-
parsedNewValue
|
|
65
|
-
});
|
|
66
|
-
return response.data;
|
|
67
|
-
},
|
|
68
|
-
promoteSecret: async (projectId, env, key) => {
|
|
69
|
-
const response = await getClient().post(`/projects/${projectId}/envs/${env}/secrets/${key}/promote`, {});
|
|
70
|
-
return response.data;
|
|
71
|
-
},
|
|
72
|
-
verifyAuditLogs: async () => {
|
|
73
|
-
const response = await getClient().get("/audit/verify");
|
|
74
|
-
return response.data;
|
|
75
|
-
},
|
|
76
|
-
exportAuditLogs: async (format, start, end, projectId) => {
|
|
77
|
-
const body = { format };
|
|
78
|
-
if (start)
|
|
79
|
-
body.startDate = start;
|
|
80
|
-
if (end)
|
|
81
|
-
body.endDate = end;
|
|
82
|
-
if (projectId)
|
|
83
|
-
body.projectId = projectId;
|
|
84
|
-
const response = await getClient().post("/audit/export", body, { responseType: format === "csv" ? "text" : "json" });
|
|
85
|
-
return response.data;
|
|
86
|
-
},
|
|
87
|
-
// JIT Access
|
|
88
|
-
requestAccess: async (projectId, reason, duration, secretId) => {
|
|
89
|
-
const response = await getClient().post("/access/request", { projectId, secretId, reason, duration });
|
|
90
|
-
return response.data;
|
|
91
|
-
},
|
|
92
|
-
approveAccess: async (requestId, decision) => {
|
|
93
|
-
const response = await getClient().post("/access/approve", { requestId, decision });
|
|
94
|
-
return response.data;
|
|
95
|
-
},
|
|
96
|
-
listAccessRequests: async (mode) => {
|
|
97
|
-
const response = await getClient().get(`/access/list?mode=${mode}`);
|
|
98
|
-
return response.data;
|
|
99
|
-
},
|
|
100
|
-
// Project Management
|
|
101
|
-
getProjects: async () => {
|
|
102
|
-
const { workspace } = (0, config_1.getConfig)();
|
|
103
|
-
const url = workspace ? `/project?workspaceId=${workspace}` : "/project";
|
|
104
|
-
const response = await getClient().get(url);
|
|
105
|
-
return response.data;
|
|
106
|
-
},
|
|
107
|
-
// Branch Management
|
|
108
|
-
getBranches: async (projectId) => {
|
|
109
|
-
const response = await getClient().get(`/branch?projectId=${projectId}`);
|
|
110
|
-
return response.data;
|
|
111
|
-
},
|
|
112
|
-
createBranch: async (projectId, name, description) => {
|
|
113
|
-
const response = await getClient().post("/branch", { projectId, name, description });
|
|
114
|
-
return response.data;
|
|
115
|
-
},
|
|
116
|
-
deleteBranch: async (branchId) => {
|
|
117
|
-
const response = await getClient().delete(`/branch/${branchId}`);
|
|
118
|
-
return response.data;
|
|
119
|
-
},
|
|
120
|
-
updateBranch: async (branchId, updates) => {
|
|
121
|
-
const response = await getClient().put("/branch", { id: branchId, ...updates });
|
|
122
|
-
return response.data;
|
|
123
|
-
},
|
|
124
|
-
// Admin - Role Management
|
|
125
|
-
getRoles: async () => {
|
|
126
|
-
const response = await getClient().get("/admin/roles");
|
|
127
|
-
return response.data;
|
|
128
|
-
},
|
|
129
|
-
getUsers: async (teamId) => {
|
|
130
|
-
const params = teamId ? `?teamId=${teamId}` : "";
|
|
131
|
-
const response = await getClient().get(`/admin/users${params}`);
|
|
132
|
-
return response.data;
|
|
133
|
-
},
|
|
134
|
-
setUserRole: async (email, role, teamId) => {
|
|
135
|
-
const response = await getClient().put("/admin/users/role", { email, role, teamId });
|
|
136
|
-
return response.data;
|
|
137
|
-
},
|
|
138
|
-
// Integrations
|
|
139
|
-
getIntegrationStatus: async (provider) => {
|
|
140
|
-
const response = await getClient().get(`/integrations/${provider}`);
|
|
141
|
-
return response.data;
|
|
142
|
-
},
|
|
143
|
-
getIntegrationRepos: async (provider) => {
|
|
144
|
-
const response = await getClient().get(`/integrations/${provider}/sync`);
|
|
145
|
-
return response.data.repos;
|
|
146
|
-
},
|
|
147
|
-
syncSecretsToGithub: async (data) => {
|
|
148
|
-
const response = await getClient().post("/api/integrations/github/sync", data);
|
|
149
|
-
return response.data;
|
|
150
|
-
},
|
|
151
|
-
exportKubernetesSecret: async (projectId, params) => {
|
|
152
|
-
const query = new URLSearchParams(params).toString();
|
|
153
|
-
const response = await getClient().get(`/projects/${projectId}/kubernetes?${query}`);
|
|
154
|
-
return response.data; // This returns the YAML string directly
|
|
155
|
-
},
|
|
156
|
-
// Advanced Features
|
|
157
|
-
getSecretHistory: async (projectId, env, key) => {
|
|
158
|
-
const response = await getClient().get(`/projects/${projectId}/envs/${env}/secrets/${key}/history`);
|
|
159
|
-
return response.data;
|
|
160
|
-
},
|
|
161
|
-
rollbackSecret: async (projectId, env, key, version) => {
|
|
162
|
-
const response = await getClient().post(`/projects/${projectId}/envs/${env}/secrets/${key}/history`, { version });
|
|
163
|
-
return response.data;
|
|
164
|
-
},
|
|
165
|
-
cloneEnvironment: async (projectId, fromEnv, toEnv, overwrite, branch) => {
|
|
166
|
-
const response = await getClient().post(`/projects/${projectId}/envs/clone`, { fromEnv, toEnv, overwrite, branch });
|
|
167
|
-
return response.data;
|
|
168
|
-
},
|
|
169
|
-
// JIT Link Management
|
|
170
|
-
generateJitLink: async (opts) => {
|
|
171
|
-
const response = await getClient().post("/jit/generate", opts);
|
|
172
|
-
return response.data;
|
|
173
|
-
},
|
|
174
|
-
claimJitLink: async (token) => {
|
|
175
|
-
const response = await getClient().post("/jit/claim", { token });
|
|
176
|
-
return response.data;
|
|
177
|
-
},
|
|
178
|
-
getJitInfo: async (token) => {
|
|
179
|
-
const response = await getClient().get(`/jit/${token}`);
|
|
180
|
-
return response.data;
|
|
181
|
-
},
|
|
182
|
-
// Generic POST method for audit logging and other uses
|
|
183
|
-
post: async (endpoint, data) => {
|
|
184
|
-
const response = await getClient().post(endpoint, data);
|
|
185
|
-
return response.data;
|
|
186
|
-
}
|
|
187
|
-
};
|
|
1
|
+
var $=Object.create;var c=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var k=(s,e)=>{for(var t in e)c(s,t,{get:e[t],enumerable:!0})},l=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of S(e))!b.call(s,a)&&a!==t&&c(s,a,{get:()=>e[a],enumerable:!(r=j(e,a))||r.enumerable});return s};var p=(s,e,t)=>(t=s!=null?$(v(s)):{},l(e||!s||!s.__esModule?c(t,"default",{value:s,enumerable:!0}):t,s)),x=s=>l(c({},"__esModule",{value:!0}),s);var C={};k(C,{api:()=>U});module.exports=x(C);var w=p(require("axios"));var y=p(require("conf"));var d="https://www.xtrasecurity.in/api",i=new y.default({projectName:"xtra-cli",defaults:{apiUrl:d}}),u=i.get("apiUrl");u&&!process.env.XTRA_API_URL&&(u.includes("localhost")||u.includes("xtra-security.vercel.app"))&&i.set("apiUrl",d);var g=()=>({...i.store,apiUrl:process.env.XTRA_API_URL||i.get("apiUrl")||d});var m=()=>i.get("token");var f=p(require("fs")),h=p(require("path")),I=JSON.parse(f.readFileSync(h.join(__dirname,"../../package.json"),"utf-8")),n=()=>{let{apiUrl:s}=g(),e=m();return w.default.create({baseURL:s,headers:{"Content-Type":"application/json","X-CLI-Version":I.version,...e?{Authorization:`Bearer ${e}`}:{}}})},U={login:async(s,e,t)=>(await n().post("/auth/cli-login",{email:s,password:e,apiKey:t})).data,getSecrets:async(s,e,t="main")=>(await n().get(`/projects/${s}/envs/${e}/secrets?branch=${t}`)).data,getSecretVersions:async(s,e,t="main")=>(await n().get(`/projects/${s}/envs/${e}/secrets?includeVersions=true&branch=${t}`)).data,setSecrets:async(s,e,t,r,a="main")=>(await n().post(`/projects/${s}/envs/${e}/secrets`,{secrets:t,expectedVersions:r,branch:a})).data,getSecretDetails:async(s,e,t,r="main")=>(await n().get(`/projects/${s}/envs/${e}/secrets/${t}?branch=${r}`)).data,syncLogs:async s=>(await n().post("/audit/cli-logs",{logs:s})).data,linkSecret:async(s,e,t,r,a,o)=>(await n().post(`/projects/${s}/envs/${e}/secrets/link`,{key:t,sourceProjectId:r,sourceEnv:a,sourceKey:o})).data,rotateSecret:async(s,e,t,r,a)=>(await n().post(`/projects/${s}/envs/${e}/secrets/${t}/rotate`,{strategy:r,parsedNewValue:a})).data,promoteSecret:async(s,e,t)=>(await n().post(`/projects/${s}/envs/${e}/secrets/${t}/promote`,{})).data,verifyAuditLogs:async()=>(await n().get("/audit/verify")).data,exportAuditLogs:async(s,e,t,r)=>{let a={format:s};return e&&(a.startDate=e),t&&(a.endDate=t),r&&(a.projectId=r),(await n().post("/audit/export",a,{responseType:s==="csv"?"text":"json"})).data},requestAccess:async(s,e,t,r)=>(await n().post("/access/request",{projectId:s,secretId:r,reason:e,duration:t})).data,approveAccess:async(s,e)=>(await n().post("/access/approve",{requestId:s,decision:e})).data,listAccessRequests:async s=>{let{workspace:e}=g(),t=e?`&workspaceId=${e}`:"";return(await n().get(`/access/list?mode=${s}${t}`)).data},getAccessRequestStatus:async s=>(await n().get(`/access/request?id=${s}`)).data,getProjects:async()=>{let{workspace:s}=g(),e=s?`/project?workspaceId=${s}`:"/project";return(await n().get(e)).data},getBranches:async s=>(await n().get(`/branch?projectId=${s}`)).data,createBranch:async(s,e,t)=>(await n().post("/branch",{projectId:s,name:e,description:t})).data,deleteBranch:async s=>(await n().delete(`/branch/${s}`)).data,updateBranch:async(s,e)=>(await n().put("/branch",{id:s,...e})).data,getRoles:async()=>(await n().get("/admin/roles")).data,getUsers:async s=>{let e=s?`?teamId=${s}`:"";return(await n().get(`/admin/users${e}`)).data},setUserRole:async(s,e,t)=>(await n().put("/admin/users/role",{email:s,role:e,teamId:t})).data,getIntegrationStatus:async s=>(await n().get(`/integrations/${s}`)).data,getIntegrationRepos:async s=>(await n().get(`/integrations/${s}/sync`)).data.repos,syncSecretsToGithub:async s=>(await n().post("/integrations/github/sync",s)).data,exportKubernetesSecret:async(s,e)=>{let t=new URLSearchParams(e).toString();return(await n().get(`/projects/${s}/kubernetes?${t}`)).data},getSecretHistory:async(s,e,t)=>(await n().get(`/projects/${s}/envs/${e}/secrets/${t}/history`)).data,rollbackSecret:async(s,e,t,r)=>(await n().post(`/projects/${s}/envs/${e}/secrets/${t}/history`,{version:r})).data,cloneEnvironment:async(s,e,t,r,a)=>(await n().post(`/projects/${s}/envs/clone`,{fromEnv:e,toEnv:t,overwrite:r,branch:a})).data,generateJitLink:async s=>(await n().post("/jit/generate",s)).data,claimJitLink:async s=>(await n().post("/jit/claim",{token:s})).data,getJitInfo:async s=>(await n().get(`/jit/${s}`)).data,post:async(s,e)=>(await n().post(s,e)).data};0&&(module.exports={api});
|
package/dist/lib/api.test.js
CHANGED
|
@@ -1,89 +1 @@
|
|
|
1
|
-
"use
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const api_1 = require("./api");
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const config_1 = require("./config");
|
|
9
|
-
// Mock axios and config
|
|
10
|
-
jest.mock('axios');
|
|
11
|
-
jest.mock('./config');
|
|
12
|
-
const mockAxios = axios_1.default;
|
|
13
|
-
describe('API Client', () => {
|
|
14
|
-
const mockCreate = jest.fn();
|
|
15
|
-
const mockGet = jest.fn();
|
|
16
|
-
const mockPost = jest.fn();
|
|
17
|
-
beforeAll(() => {
|
|
18
|
-
mockAxios.create = mockCreate;
|
|
19
|
-
config_1.getConfig.mockReturnValue({ apiUrl: 'http://localhost:3000/api' });
|
|
20
|
-
config_1.getAuthToken.mockReturnValue('test-token-123');
|
|
21
|
-
mockCreate.mockReturnValue({
|
|
22
|
-
get: mockGet,
|
|
23
|
-
post: mockPost,
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
jest.clearAllMocks();
|
|
28
|
-
});
|
|
29
|
-
describe('login', () => {
|
|
30
|
-
it('should send login request with email and password', async () => {
|
|
31
|
-
mockPost.mockResolvedValue({ data: { token: 'new-token' } });
|
|
32
|
-
const result = await api_1.api.login('user@example.com', 'password123');
|
|
33
|
-
expect(mockPost).toHaveBeenCalledWith('/auth/cli-login', {
|
|
34
|
-
email: 'user@example.com',
|
|
35
|
-
password: 'password123',
|
|
36
|
-
apiKey: undefined
|
|
37
|
-
});
|
|
38
|
-
expect(result).toEqual({ token: 'new-token' });
|
|
39
|
-
});
|
|
40
|
-
it('should send login request with API key', async () => {
|
|
41
|
-
mockPost.mockResolvedValue({ data: { token: 'api-key-token' } });
|
|
42
|
-
const result = await api_1.api.login(undefined, undefined, 'xtra_key_abc123');
|
|
43
|
-
expect(mockPost).toHaveBeenCalledWith('/auth/cli-login', {
|
|
44
|
-
email: undefined,
|
|
45
|
-
password: undefined,
|
|
46
|
-
apiKey: 'xtra_key_abc123'
|
|
47
|
-
});
|
|
48
|
-
expect(result).toEqual({ token: 'api-key-token' });
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('getSecrets', () => {
|
|
52
|
-
it('should fetch secrets for project and environment', async () => {
|
|
53
|
-
const mockSecrets = { secrets: [{ key: 'DB_URL', value: 'postgres://...' }] };
|
|
54
|
-
mockGet.mockResolvedValue({ data: mockSecrets });
|
|
55
|
-
const result = await api_1.api.getSecrets('proj-123', 'production', 'main');
|
|
56
|
-
expect(mockGet).toHaveBeenCalledWith('/projects/proj-123/envs/production/secrets?branch=main');
|
|
57
|
-
expect(result).toEqual(mockSecrets);
|
|
58
|
-
});
|
|
59
|
-
it('should use default branch if not specified', async () => {
|
|
60
|
-
mockGet.mockResolvedValue({ data: { secrets: [] } });
|
|
61
|
-
await api_1.api.getSecrets('proj-123', 'dev');
|
|
62
|
-
expect(mockGet).toHaveBeenCalledWith('/projects/proj-123/envs/dev/secrets?branch=main');
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
describe('setSecrets', () => {
|
|
66
|
-
it('should post secrets to API', async () => {
|
|
67
|
-
const secrets = { DB_URL: 'postgres://new', API_KEY: 'key123' };
|
|
68
|
-
mockPost.mockResolvedValue({ data: { success: true } });
|
|
69
|
-
const result = await api_1.api.setSecrets('proj-123', 'staging', secrets);
|
|
70
|
-
expect(mockPost).toHaveBeenCalledWith('/projects/proj-123/envs/staging/secrets', {
|
|
71
|
-
secrets,
|
|
72
|
-
expectedVersions: undefined,
|
|
73
|
-
branch: 'main'
|
|
74
|
-
});
|
|
75
|
-
expect(result).toEqual({ success: true });
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe('rotateSecret', () => {
|
|
79
|
-
it('should rotate secret with strategy', async () => {
|
|
80
|
-
mockPost.mockResolvedValue({ data: { newValue: 'rotated-value' } });
|
|
81
|
-
const result = await api_1.api.rotateSecret('proj-123', 'prod', 'API_KEY', 'regenerate');
|
|
82
|
-
expect(mockPost).toHaveBeenCalledWith('/projects/proj-123/envs/prod/secrets/API_KEY/rotate', {
|
|
83
|
-
strategy: 'regenerate',
|
|
84
|
-
parsedNewValue: undefined
|
|
85
|
-
});
|
|
86
|
-
expect(result).toEqual({ newValue: 'rotated-value' });
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
});
|
|
1
|
+
var j=Object.create;var y=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var $=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of x(t))!S.call(e,o)&&o!==s&&y(e,o,{get:()=>t[o],enumerable:!(n=v(t,o))||n.enumerable});return e};var p=(e,t,s)=>(s=e!=null?j(b(e)):{},$(t||!e||!e.__esModule?y(s,"default",{value:e,enumerable:!0}):s,e));var h=p(require("axios"));var m=p(require("conf"));var u="https://www.xtrasecurity.in/api",g=new m.default({projectName:"xtra-cli",defaults:{apiUrl:u}}),l=g.get("apiUrl");l&&!process.env.XTRA_API_URL&&(l.includes("localhost")||l.includes("xtra-security.vercel.app"))&&g.set("apiUrl",u);var c=()=>({...g.store,apiUrl:process.env.XTRA_API_URL||g.get("apiUrl")||u});var d=()=>g.get("token");var w=p(require("fs")),f=p(require("path")),R=JSON.parse(w.readFileSync(f.join(__dirname,"../../package.json"),"utf-8")),r=()=>{let{apiUrl:e}=c(),t=d();return h.default.create({baseURL:e,headers:{"Content-Type":"application/json","X-CLI-Version":R.version,...t?{Authorization:`Bearer ${t}`}:{}}})},a={login:async(e,t,s)=>(await r().post("/auth/cli-login",{email:e,password:t,apiKey:s})).data,getSecrets:async(e,t,s="main")=>(await r().get(`/projects/${e}/envs/${t}/secrets?branch=${s}`)).data,getSecretVersions:async(e,t,s="main")=>(await r().get(`/projects/${e}/envs/${t}/secrets?includeVersions=true&branch=${s}`)).data,setSecrets:async(e,t,s,n,o="main")=>(await r().post(`/projects/${e}/envs/${t}/secrets`,{secrets:s,expectedVersions:n,branch:o})).data,getSecretDetails:async(e,t,s,n="main")=>(await r().get(`/projects/${e}/envs/${t}/secrets/${s}?branch=${n}`)).data,syncLogs:async e=>(await r().post("/audit/cli-logs",{logs:e})).data,linkSecret:async(e,t,s,n,o,i)=>(await r().post(`/projects/${e}/envs/${t}/secrets/link`,{key:s,sourceProjectId:n,sourceEnv:o,sourceKey:i})).data,rotateSecret:async(e,t,s,n,o)=>(await r().post(`/projects/${e}/envs/${t}/secrets/${s}/rotate`,{strategy:n,parsedNewValue:o})).data,promoteSecret:async(e,t,s)=>(await r().post(`/projects/${e}/envs/${t}/secrets/${s}/promote`,{})).data,verifyAuditLogs:async()=>(await r().get("/audit/verify")).data,exportAuditLogs:async(e,t,s,n)=>{let o={format:e};return t&&(o.startDate=t),s&&(o.endDate=s),n&&(o.projectId=n),(await r().post("/audit/export",o,{responseType:e==="csv"?"text":"json"})).data},requestAccess:async(e,t,s,n)=>(await r().post("/access/request",{projectId:e,secretId:n,reason:t,duration:s})).data,approveAccess:async(e,t)=>(await r().post("/access/approve",{requestId:e,decision:t})).data,listAccessRequests:async e=>{let{workspace:t}=c(),s=t?`&workspaceId=${t}`:"";return(await r().get(`/access/list?mode=${e}${s}`)).data},getAccessRequestStatus:async e=>(await r().get(`/access/request?id=${e}`)).data,getProjects:async()=>{let{workspace:e}=c(),t=e?`/project?workspaceId=${e}`:"/project";return(await r().get(t)).data},getBranches:async e=>(await r().get(`/branch?projectId=${e}`)).data,createBranch:async(e,t,s)=>(await r().post("/branch",{projectId:e,name:t,description:s})).data,deleteBranch:async e=>(await r().delete(`/branch/${e}`)).data,updateBranch:async(e,t)=>(await r().put("/branch",{id:e,...t})).data,getRoles:async()=>(await r().get("/admin/roles")).data,getUsers:async e=>{let t=e?`?teamId=${e}`:"";return(await r().get(`/admin/users${t}`)).data},setUserRole:async(e,t,s)=>(await r().put("/admin/users/role",{email:e,role:t,teamId:s})).data,getIntegrationStatus:async e=>(await r().get(`/integrations/${e}`)).data,getIntegrationRepos:async e=>(await r().get(`/integrations/${e}/sync`)).data.repos,syncSecretsToGithub:async e=>(await r().post("/integrations/github/sync",e)).data,exportKubernetesSecret:async(e,t)=>{let s=new URLSearchParams(t).toString();return(await r().get(`/projects/${e}/kubernetes?${s}`)).data},getSecretHistory:async(e,t,s)=>(await r().get(`/projects/${e}/envs/${t}/secrets/${s}/history`)).data,rollbackSecret:async(e,t,s,n)=>(await r().post(`/projects/${e}/envs/${t}/secrets/${s}/history`,{version:n})).data,cloneEnvironment:async(e,t,s,n,o)=>(await r().post(`/projects/${e}/envs/clone`,{fromEnv:t,toEnv:s,overwrite:n,branch:o})).data,generateJitLink:async e=>(await r().post("/jit/generate",e)).data,claimJitLink:async e=>(await r().post("/jit/claim",{token:e})).data,getJitInfo:async e=>(await r().get(`/jit/${e}`)).data,post:async(e,t)=>(await r().post(e,t)).data};var k=p(require("axios"));jest.mock("axios");jest.mock("./config");var I=k.default;describe("API Client",()=>{let e=jest.fn(),t=jest.fn(),s=jest.fn();beforeAll(()=>{I.create=e,c.mockReturnValue({apiUrl:"http://localhost:3000/api"}),d.mockReturnValue("test-token-123"),e.mockReturnValue({get:t,post:s})}),beforeEach(()=>{jest.clearAllMocks()}),describe("login",()=>{it("should send login request with email and password",async()=>{s.mockResolvedValue({data:{token:"new-token"}});let n=await a.login("user@example.com","password123");expect(s).toHaveBeenCalledWith("/auth/cli-login",{email:"user@example.com",password:"password123",apiKey:void 0}),expect(n).toEqual({token:"new-token"})}),it("should send login request with API key",async()=>{s.mockResolvedValue({data:{token:"api-key-token"}});let n=await a.login(void 0,void 0,"xtra_key_abc123");expect(s).toHaveBeenCalledWith("/auth/cli-login",{email:void 0,password:void 0,apiKey:"xtra_key_abc123"}),expect(n).toEqual({token:"api-key-token"})})}),describe("getSecrets",()=>{it("should fetch secrets for project and environment",async()=>{let n={secrets:[{key:"DB_URL",value:"postgres://..."}]};t.mockResolvedValue({data:n});let o=await a.getSecrets("proj-123","production","main");expect(t).toHaveBeenCalledWith("/projects/proj-123/envs/production/secrets?branch=main"),expect(o).toEqual(n)}),it("should use default branch if not specified",async()=>{t.mockResolvedValue({data:{secrets:[]}}),await a.getSecrets("proj-123","dev"),expect(t).toHaveBeenCalledWith("/projects/proj-123/envs/dev/secrets?branch=main")})}),describe("setSecrets",()=>{it("should post secrets to API",async()=>{let n={DB_URL:"postgres://new",API_KEY:"key123"};s.mockResolvedValue({data:{success:!0}});let o=await a.setSecrets("proj-123","staging",n);expect(s).toHaveBeenCalledWith("/projects/proj-123/envs/staging/secrets",{secrets:n,expectedVersions:void 0,branch:"main"}),expect(o).toEqual({success:!0})})}),describe("rotateSecret",()=>{it("should rotate secret with strategy",async()=>{s.mockResolvedValue({data:{newValue:"rotated-value"}});let n=await a.rotateSecret("proj-123","prod","API_KEY","regenerate");expect(s).toHaveBeenCalledWith("/projects/proj-123/envs/prod/secrets/API_KEY/rotate",{strategy:"regenerate",parsedNewValue:void 0}),expect(n).toEqual({newValue:"rotated-value"})})})});
|
package/dist/lib/audit.js
CHANGED
|
@@ -1,136 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.logAudit = logAudit;
|
|
7
|
-
exports.loadAuditLogs = loadAuditLogs;
|
|
8
|
-
exports.saveAuditLogs = saveAuditLogs;
|
|
9
|
-
exports.syncAuditLogs = syncAuditLogs;
|
|
10
|
-
const fs_1 = __importDefault(require("fs"));
|
|
11
|
-
const path_1 = __importDefault(require("path"));
|
|
12
|
-
const uuid_1 = require("uuid");
|
|
13
|
-
const crypto_1 = require("./crypto");
|
|
14
|
-
const config_1 = require("./config");
|
|
15
|
-
const api_1 = require("./api");
|
|
16
|
-
const MANIFEST_DIR = path_1.default.join(process.cwd(), ".xtra");
|
|
17
|
-
const AUDIT_FILE = path_1.default.join(MANIFEST_DIR, "audit.enc");
|
|
18
|
-
/**
|
|
19
|
-
* Sanitize sensitive data from details
|
|
20
|
-
*/
|
|
21
|
-
function sanitizeDetails(details) {
|
|
22
|
-
if (!details || typeof details !== 'object')
|
|
23
|
-
return details;
|
|
24
|
-
const sanitized = { ...details };
|
|
25
|
-
const sensitiveKeys = ['password', 'token', 'secret', 'value', 'apiKey', 'secretValue'];
|
|
26
|
-
for (const key of Object.keys(sanitized)) {
|
|
27
|
-
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
|
|
28
|
-
sanitized[key] = '[REDACTED]';
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return sanitized;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Send audit log to backend API
|
|
35
|
-
*/
|
|
36
|
-
async function sendToBackend(entry) {
|
|
37
|
-
try {
|
|
38
|
-
const config = (0, config_1.getConfig)();
|
|
39
|
-
// Skip if not authenticated
|
|
40
|
-
if (!config.token) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const payload = {
|
|
44
|
-
action: entry.action,
|
|
45
|
-
entity: 'cli-project',
|
|
46
|
-
entityId: entry.projectId || 'global',
|
|
47
|
-
details: sanitizeDetails(entry.details),
|
|
48
|
-
timestamp: entry.timestamp,
|
|
49
|
-
projectId: entry.projectId,
|
|
50
|
-
};
|
|
51
|
-
await api_1.api.post('/audit/cli-logs', { logs: [payload] });
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
// Silently fail - don't disrupt CLI operations
|
|
56
|
-
if (process.env.DEBUG) {
|
|
57
|
-
console.error('Failed to send audit log to backend:', error);
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function logAudit(action, projectId, environment, details) {
|
|
63
|
-
const entry = {
|
|
64
|
-
id: (0, uuid_1.v4)(),
|
|
65
|
-
timestamp: new Date().toISOString(),
|
|
66
|
-
action,
|
|
67
|
-
projectId,
|
|
68
|
-
environment,
|
|
69
|
-
details,
|
|
70
|
-
synced: false
|
|
71
|
-
};
|
|
72
|
-
// Save locally
|
|
73
|
-
const logs = loadAuditLogs();
|
|
74
|
-
logs.push(entry);
|
|
75
|
-
saveAuditLogs(logs);
|
|
76
|
-
// Send to backend (non-blocking)
|
|
77
|
-
sendToBackend(entry).then((synced) => {
|
|
78
|
-
if (synced) {
|
|
79
|
-
// Mark as synced in local logs
|
|
80
|
-
entry.synced = true;
|
|
81
|
-
const updatedLogs = loadAuditLogs();
|
|
82
|
-
const index = updatedLogs.findIndex(l => l.id === entry.id);
|
|
83
|
-
if (index !== -1) {
|
|
84
|
-
updatedLogs[index].synced = true;
|
|
85
|
-
saveAuditLogs(updatedLogs);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}).catch(() => {
|
|
89
|
-
// Ignore errors
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function loadAuditLogs() {
|
|
93
|
-
if (!fs_1.default.existsSync(AUDIT_FILE)) {
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const encryptedStr = fs_1.default.readFileSync(AUDIT_FILE, "utf-8");
|
|
98
|
-
const encryptedObj = JSON.parse(encryptedStr);
|
|
99
|
-
const decrypted = (0, crypto_1.decrypt)(encryptedObj);
|
|
100
|
-
return JSON.parse(decrypted);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
// console.error("Failed to load audit logs", error);
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function saveAuditLogs(logs) {
|
|
108
|
-
if (!fs_1.default.existsSync(MANIFEST_DIR)) {
|
|
109
|
-
fs_1.default.mkdirSync(MANIFEST_DIR, { recursive: true });
|
|
110
|
-
}
|
|
111
|
-
const serialized = JSON.stringify(logs);
|
|
112
|
-
const encryptedObj = (0, crypto_1.encrypt)(serialized);
|
|
113
|
-
fs_1.default.writeFileSync(AUDIT_FILE, JSON.stringify(encryptedObj), "utf-8");
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Sync unsynced audit logs to backend
|
|
117
|
-
*/
|
|
118
|
-
async function syncAuditLogs() {
|
|
119
|
-
const logs = loadAuditLogs();
|
|
120
|
-
const unsynced = logs.filter(l => !l.synced);
|
|
121
|
-
if (unsynced.length === 0) {
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
let syncedCount = 0;
|
|
125
|
-
for (const entry of unsynced) {
|
|
126
|
-
const success = await sendToBackend(entry);
|
|
127
|
-
if (success) {
|
|
128
|
-
entry.synced = true;
|
|
129
|
-
syncedCount++;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (syncedCount > 0) {
|
|
133
|
-
saveAuditLogs(logs);
|
|
134
|
-
}
|
|
135
|
-
return syncedCount;
|
|
136
|
-
}
|
|
1
|
+
var J=Object.create;var u=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,K=Object.prototype.hasOwnProperty;var V=(t,e)=>{for(var s in e)u(t,s,{get:e[s],enumerable:!0})},$=(t,e,s,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of G(e))!K.call(t,r)&&r!==s&&u(t,r,{get:()=>e[r],enumerable:!(n=F(e,r))||n.enumerable});return t};var i=(t,e,s)=>(s=t!=null?J(q(t)):{},$(e||!t||!t.__esModule?u(s,"default",{value:t,enumerable:!0}):s,t)),z=t=>$(u({},"__esModule",{value:!0}),t);var W={};V(W,{loadAuditLogs:()=>y,logAudit:()=>M,saveAuditLogs:()=>f,syncAuditLogs:()=>Q});module.exports=z(W);var g=i(require("fs")),x=i(require("path")),D=require("uuid");var c=i(require("crypto")),k=i(require("conf")),I=require("node-machine-id"),A="aes-256-gcm",b=new k.default({projectName:"xtra-cli-crypto"});function C(){let t=b.get("masterKey");t||(t=c.default.randomBytes(32).toString("hex"),b.set("masterKey",t));let e=(0,I.machineIdSync)();return c.default.createHash("sha256").update(t+e).digest()}var U=t=>{let e=c.default.randomBytes(16),s=C(),n=c.default.createCipheriv(A,s,e),r=n.update(t,"utf8","hex");r+=n.final("hex");let a=n.getAuthTag();return{iv:e.toString("hex"),content:r,tag:a.toString("hex")}},R=t=>{let e=C(),s=Buffer.from(t.iv,"hex"),n=Buffer.from(t.tag,"hex"),r=c.default.createDecipheriv(A,e,s);r.setAuthTag(n);let a=r.update(t.content,"hex","utf8");return a+=r.final("utf8"),a};var L=i(require("conf"));var h="https://www.xtrasecurity.in/api",d=new L.default({projectName:"xtra-cli",defaults:{apiUrl:h}}),m=d.get("apiUrl");m&&!process.env.XTRA_API_URL&&(m.includes("localhost")||m.includes("xtra-security.vercel.app"))&&d.set("apiUrl",h);var p=()=>({...d.store,apiUrl:process.env.XTRA_API_URL||d.get("apiUrl")||h});var B=()=>d.get("token");var O=i(require("axios"));var T=i(require("fs")),P=i(require("path")),H=JSON.parse(T.readFileSync(P.join(__dirname,"../../package.json"),"utf-8")),o=()=>{let{apiUrl:t}=p(),e=B();return O.default.create({baseURL:t,headers:{"Content-Type":"application/json","X-CLI-Version":H.version,...e?{Authorization:`Bearer ${e}`}:{}}})},_={login:async(t,e,s)=>(await o().post("/auth/cli-login",{email:t,password:e,apiKey:s})).data,getSecrets:async(t,e,s="main")=>(await o().get(`/projects/${t}/envs/${e}/secrets?branch=${s}`)).data,getSecretVersions:async(t,e,s="main")=>(await o().get(`/projects/${t}/envs/${e}/secrets?includeVersions=true&branch=${s}`)).data,setSecrets:async(t,e,s,n,r="main")=>(await o().post(`/projects/${t}/envs/${e}/secrets`,{secrets:s,expectedVersions:n,branch:r})).data,getSecretDetails:async(t,e,s,n="main")=>(await o().get(`/projects/${t}/envs/${e}/secrets/${s}?branch=${n}`)).data,syncLogs:async t=>(await o().post("/audit/cli-logs",{logs:t})).data,linkSecret:async(t,e,s,n,r,a)=>(await o().post(`/projects/${t}/envs/${e}/secrets/link`,{key:s,sourceProjectId:n,sourceEnv:r,sourceKey:a})).data,rotateSecret:async(t,e,s,n,r)=>(await o().post(`/projects/${t}/envs/${e}/secrets/${s}/rotate`,{strategy:n,parsedNewValue:r})).data,promoteSecret:async(t,e,s)=>(await o().post(`/projects/${t}/envs/${e}/secrets/${s}/promote`,{})).data,verifyAuditLogs:async()=>(await o().get("/audit/verify")).data,exportAuditLogs:async(t,e,s,n)=>{let r={format:t};return e&&(r.startDate=e),s&&(r.endDate=s),n&&(r.projectId=n),(await o().post("/audit/export",r,{responseType:t==="csv"?"text":"json"})).data},requestAccess:async(t,e,s,n)=>(await o().post("/access/request",{projectId:t,secretId:n,reason:e,duration:s})).data,approveAccess:async(t,e)=>(await o().post("/access/approve",{requestId:t,decision:e})).data,listAccessRequests:async t=>{let{workspace:e}=p(),s=e?`&workspaceId=${e}`:"";return(await o().get(`/access/list?mode=${t}${s}`)).data},getAccessRequestStatus:async t=>(await o().get(`/access/request?id=${t}`)).data,getProjects:async()=>{let{workspace:t}=p(),e=t?`/project?workspaceId=${t}`:"/project";return(await o().get(e)).data},getBranches:async t=>(await o().get(`/branch?projectId=${t}`)).data,createBranch:async(t,e,s)=>(await o().post("/branch",{projectId:t,name:e,description:s})).data,deleteBranch:async t=>(await o().delete(`/branch/${t}`)).data,updateBranch:async(t,e)=>(await o().put("/branch",{id:t,...e})).data,getRoles:async()=>(await o().get("/admin/roles")).data,getUsers:async t=>{let e=t?`?teamId=${t}`:"";return(await o().get(`/admin/users${e}`)).data},setUserRole:async(t,e,s)=>(await o().put("/admin/users/role",{email:t,role:e,teamId:s})).data,getIntegrationStatus:async t=>(await o().get(`/integrations/${t}`)).data,getIntegrationRepos:async t=>(await o().get(`/integrations/${t}/sync`)).data.repos,syncSecretsToGithub:async t=>(await o().post("/integrations/github/sync",t)).data,exportKubernetesSecret:async(t,e)=>{let s=new URLSearchParams(e).toString();return(await o().get(`/projects/${t}/kubernetes?${s}`)).data},getSecretHistory:async(t,e,s)=>(await o().get(`/projects/${t}/envs/${e}/secrets/${s}/history`)).data,rollbackSecret:async(t,e,s,n)=>(await o().post(`/projects/${t}/envs/${e}/secrets/${s}/history`,{version:n})).data,cloneEnvironment:async(t,e,s,n,r)=>(await o().post(`/projects/${t}/envs/clone`,{fromEnv:e,toEnv:s,overwrite:n,branch:r})).data,generateJitLink:async t=>(await o().post("/jit/generate",t)).data,claimJitLink:async t=>(await o().post("/jit/claim",{token:t})).data,getJitInfo:async t=>(await o().get(`/jit/${t}`)).data,post:async(t,e)=>(await o().post(t,e)).data};var w=x.default.join(process.cwd(),".xtra"),S=x.default.join(w,"audit.enc");function X(t){if(!t||typeof t!="object")return t;let e={...t},s=["password","token","secret","value","apiKey","secretValue"];for(let n of Object.keys(e))s.some(r=>n.toLowerCase().includes(r))&&(e[n]="[REDACTED]");return e}async function N(t){try{if(!p().token)return!1;let s={action:t.action,entity:"cli-project",entityId:t.projectId||"global",details:X(t.details),timestamp:t.timestamp,projectId:t.projectId};return await _.post("/audit/cli-logs",{logs:[s]}),!0}catch(e){return process.env.DEBUG&&console.error("Failed to send audit log to backend:",e),!1}}function M(t,e,s,n){let r={id:(0,D.v4)(),timestamp:new Date().toISOString(),action:t,projectId:e,environment:s,details:n,synced:!1},a=y();a.push(r),f(a),N(r).then(v=>{if(v){r.synced=!0;let l=y(),j=l.findIndex(E=>E.id===r.id);j!==-1&&(l[j].synced=!0,f(l))}}).catch(()=>{})}function y(){if(!g.default.existsSync(S))return[];try{let t=g.default.readFileSync(S,"utf-8"),e=JSON.parse(t),s=R(e);return JSON.parse(s)}catch{return[]}}function f(t){g.default.existsSync(w)||g.default.mkdirSync(w,{recursive:!0});let e=JSON.stringify(t),s=U(e);g.default.writeFileSync(S,JSON.stringify(s),"utf-8")}async function Q(){let t=y(),e=t.filter(n=>!n.synced);if(e.length===0)return 0;let s=0;for(let n of e)await N(n)&&(n.synced=!0,s++);return s>0&&f(t),s}0&&(module.exports={loadAuditLogs,logAudit,saveAuditLogs,syncAuditLogs});
|