@elding/cli 0.1.0 → 0.3.0

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.
@@ -0,0 +1 @@
1
+ export declare function doctor(): Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doctor = doctor;
4
+ const config_js_1 = require("../lib/config.js");
5
+ const api_js_1 = require("../lib/api.js");
6
+ async function doctor() {
7
+ const { default: chalk } = await import("chalk");
8
+ const ok = (m) => console.log(`${chalk.green("✓")} ${m}`);
9
+ const warn = (m) => console.log(`${chalk.yellow("!")} ${m}`);
10
+ const fail = (m) => console.log(`${chalk.red("✗")} ${m}`);
11
+ console.log(chalk.dim(`API : ${api_js_1.BASE_URL}\n`));
12
+ // 1. Token local
13
+ const config = (0, config_js_1.readConfig)();
14
+ if (!config) {
15
+ fail("Non connecté — lancez `elding login`");
16
+ return;
17
+ }
18
+ ok("Token local présent (~/.elding/config.json)");
19
+ // 2. Token valide + utilisateur
20
+ let accessToken;
21
+ try {
22
+ accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
23
+ const me = await (0, api_js_1.getMe)(accessToken);
24
+ ok(`Authentifié : ${me.email}`);
25
+ }
26
+ catch {
27
+ fail("Token expiré ou révoqué — relancez `elding login`");
28
+ return;
29
+ }
30
+ // 3. Set du projet
31
+ const project = (0, config_js_1.readProject)();
32
+ if (!project) {
33
+ warn("Aucun set configuré — lancez `elding init` ou `elding use <set>`");
34
+ return;
35
+ }
36
+ ok(`Set actif : ${project.setName}`);
37
+ // 4. Clés + verrouillage host
38
+ try {
39
+ const keys = await (0, api_js_1.listKeys)(accessToken, project.setId);
40
+ if (keys.length === 0)
41
+ warn("Le set ne contient aucune clé");
42
+ else {
43
+ const locked = keys.filter((k) => k.allowedHost).length;
44
+ ok(`${keys.length} clé(s) — ${locked} verrouillée(s) sur un domaine`);
45
+ if (locked < keys.length)
46
+ warn(`${keys.length - locked} clé(s) sans domaine : exfiltrables via le proxy`);
47
+ }
48
+ }
49
+ catch {
50
+ fail("Impossible de lire les clés du set");
51
+ }
52
+ // 5. Proxy
53
+ if (process.env.ELDING_PROXY_URL)
54
+ ok(`Proxy actif : ${process.env.ELDING_PROXY_URL}`);
55
+ else
56
+ console.log(chalk.dim("○ Proxy inactif (normal hors `elding proxy`)"));
57
+ }
@@ -0,0 +1 @@
1
+ export declare function keys(): Promise<void>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.keys = keys;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const session_js_1 = require("../lib/session.js");
7
+ async function keys() {
8
+ const { default: chalk } = await import("chalk");
9
+ const { default: ora } = await import("ora");
10
+ const project = (0, config_js_1.readProject)();
11
+ if (!project) {
12
+ console.error(chalk.red("Projet non initialisé. Lancez `elding init` ou `elding use <set>`."));
13
+ process.exit(1);
14
+ }
15
+ const spinner = ora("Récupération des clés...").start();
16
+ const accessToken = await (0, session_js_1.requireAccessToken)();
17
+ const list = await (0, api_js_1.listKeys)(accessToken, project.setId);
18
+ spinner.stop();
19
+ console.log(chalk.dim(`Set : ${project.setName}`));
20
+ if (list.length === 0) {
21
+ console.log(chalk.yellow("Aucune clé dans ce set."));
22
+ return;
23
+ }
24
+ for (const k of list) {
25
+ const host = k.allowedHost
26
+ ? chalk.dim(` → ${k.allowedHost}`)
27
+ : chalk.yellow(" → aucun domaine (non verrouillé)");
28
+ console.log(` ${k.name}${host}`);
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export declare function logout(): Promise<void>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logout = logout;
4
+ const config_js_1 = require("../lib/config.js");
5
+ const api_js_1 = require("../lib/api.js");
6
+ async function logout() {
7
+ const { default: chalk } = await import("chalk");
8
+ const { default: ora } = await import("ora");
9
+ const config = (0, config_js_1.readConfig)();
10
+ if (!config) {
11
+ console.log(chalk.dim("Déjà déconnecté."));
12
+ return;
13
+ }
14
+ const spinner = ora("Déconnexion...").start();
15
+ // Révoque côté serveur (best-effort) puis efface le token local
16
+ await (0, api_js_1.revokeToken)(config.refreshToken);
17
+ (0, config_js_1.clearConfig)();
18
+ spinner.succeed(chalk.green("Déconnecté. Token révoqué et supprimé localement."));
19
+ }
@@ -0,0 +1 @@
1
+ export declare function open(): Promise<void>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.open = open;
4
+ const api_js_1 = require("../lib/api.js");
5
+ async function open() {
6
+ const { default: chalk } = await import("chalk");
7
+ const { default: openUrl } = await import("open");
8
+ const url = `${api_js_1.BASE_URL}/vault`;
9
+ await openUrl(url).catch(() => {
10
+ console.log(chalk.cyan(`Ouvrez manuellement : ${url}`));
11
+ });
12
+ console.log(chalk.dim(`Vault ouvert : ${url}`));
13
+ }
@@ -0,0 +1 @@
1
+ export declare function proxy(cmd: string, args: string[], verbose?: boolean): Promise<void>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.proxy = proxy;
4
+ const child_process_1 = require("child_process");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const api_js_1 = require("../lib/api.js");
7
+ const proxyServer_js_1 = require("../lib/proxyServer.js");
8
+ async function proxy(cmd, args, verbose = false) {
9
+ const { default: chalk } = await import("chalk");
10
+ const { default: ora } = await import("ora");
11
+ const config = (0, config_js_1.readConfig)();
12
+ if (!config) {
13
+ console.error(chalk.red("Non connecté. Lancez `elding login` d'abord."));
14
+ process.exit(1);
15
+ }
16
+ const project = (0, config_js_1.readProject)();
17
+ if (!project) {
18
+ console.error(chalk.red("Projet non initialisé. Lancez `elding init` d'abord."));
19
+ process.exit(1);
20
+ }
21
+ const spinner = ora("Démarrage du proxy...").start();
22
+ let secrets;
23
+ let hosts;
24
+ try {
25
+ const accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
26
+ ({ secrets, hosts } = await (0, api_js_1.fetchSecrets)(accessToken, project.setId));
27
+ }
28
+ catch (err) {
29
+ spinner.fail(chalk.red(err instanceof Error ? err.message : "Erreur inconnue"));
30
+ process.exit(1);
31
+ }
32
+ const server = await (0, proxyServer_js_1.startProxy)(secrets, hosts, verbose);
33
+ spinner.succeed(chalk.green(`Proxy actif sur ${server.url} — ${Object.keys(secrets).length} secret(s)`));
34
+ console.log(chalk.dim("Les clés restent dans le proxy, jamais dans la mémoire de l'app."));
35
+ const child = (0, child_process_1.spawn)(cmd, args, {
36
+ env: {
37
+ ...process.env,
38
+ ELDING_PROXY_URL: server.url,
39
+ ELDING_PROXY_TOKEN: server.token,
40
+ },
41
+ stdio: "inherit",
42
+ shell: true,
43
+ });
44
+ const shutdown = () => server.close();
45
+ process.on("SIGINT", shutdown);
46
+ process.on("SIGTERM", shutdown);
47
+ child.on("exit", (code) => {
48
+ server.close();
49
+ process.exit(code ?? 0);
50
+ });
51
+ }
@@ -21,7 +21,7 @@ async function run(cmd, args) {
21
21
  let secrets;
22
22
  try {
23
23
  const accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
24
- secrets = await (0, api_js_1.fetchSecrets)(accessToken, project.setId);
24
+ ({ secrets } = await (0, api_js_1.fetchSecrets)(accessToken, project.setId, "run"));
25
25
  spinner.succeed(chalk.green(`${Object.keys(secrets).length} secret(s) chargé(s).`));
26
26
  }
27
27
  catch (err) {
@@ -0,0 +1 @@
1
+ export declare function sets(): Promise<void>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sets = sets;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const session_js_1 = require("../lib/session.js");
7
+ async function sets() {
8
+ const { default: chalk } = await import("chalk");
9
+ const { default: ora } = await import("ora");
10
+ const spinner = ora("Récupération des sets...").start();
11
+ const accessToken = await (0, session_js_1.requireAccessToken)();
12
+ const list = await (0, api_js_1.listSets)(accessToken);
13
+ spinner.stop();
14
+ if (list.length === 0) {
15
+ console.log(chalk.yellow("Aucun set. Créez-en un sur le vault."));
16
+ return;
17
+ }
18
+ const activeId = (0, config_js_1.readProject)()?.setId;
19
+ for (const s of list) {
20
+ const marker = s.id === activeId ? chalk.green("●") : chalk.dim("○");
21
+ const name = s.id === activeId ? chalk.green(s.name) : s.name;
22
+ console.log(`${marker} ${name} ${chalk.dim(`(${s.id})`)}`);
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ export declare function status(): Promise<void>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.status = status;
4
+ const config_js_1 = require("../lib/config.js");
5
+ const api_js_1 = require("../lib/api.js");
6
+ async function status() {
7
+ const { default: chalk } = await import("chalk");
8
+ const config = (0, config_js_1.readConfig)();
9
+ if (!config) {
10
+ console.log(chalk.yellow("Non connecté.") + chalk.dim(" Lancez `elding login`."));
11
+ return;
12
+ }
13
+ // Vérifie la validité du token + récupère l'utilisateur
14
+ let user = null;
15
+ try {
16
+ const accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
17
+ user = await (0, api_js_1.getMe)(accessToken);
18
+ }
19
+ catch {
20
+ console.log(chalk.red("Token expiré ou révoqué.") + chalk.dim(" Relancez `elding login`."));
21
+ return;
22
+ }
23
+ const project = (0, config_js_1.readProject)();
24
+ const proxyActive = !!process.env.ELDING_PROXY_URL;
25
+ console.log(chalk.green("● Connecté"));
26
+ console.log(` ${chalk.dim("Utilisateur")} ${user.email}${user.name ? chalk.dim(` (${user.name})`) : ""}`);
27
+ console.log(` ${chalk.dim("Set actif")} ${project ? `${project.setName} ${chalk.dim(`(${project.setId})`)}` : chalk.yellow("aucun — `elding init`")}`);
28
+ console.log(` ${chalk.dim("Proxy")} ${proxyActive ? chalk.green("actif") : chalk.dim("inactif")}`);
29
+ }
@@ -0,0 +1 @@
1
+ export declare function use(nameArg: string): Promise<void>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.use = use;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const session_js_1 = require("../lib/session.js");
7
+ async function use(nameArg) {
8
+ const { default: chalk } = await import("chalk");
9
+ const { default: ora } = await import("ora");
10
+ const spinner = ora("Récupération des sets...").start();
11
+ const accessToken = await (0, session_js_1.requireAccessToken)();
12
+ const list = await (0, api_js_1.listSets)(accessToken);
13
+ spinner.stop();
14
+ const query = nameArg.trim().toLowerCase();
15
+ const matches = list.filter((s) => s.name.toLowerCase() === query);
16
+ const candidates = matches.length ? matches : list.filter((s) => s.name.toLowerCase().includes(query));
17
+ if (candidates.length === 0) {
18
+ console.error(chalk.red(`Aucun set nommé "${nameArg}".`) + chalk.dim(" Voir `elding sets`."));
19
+ process.exit(1);
20
+ }
21
+ if (candidates.length > 1) {
22
+ console.error(chalk.red(`Plusieurs sets correspondent à "${nameArg}" :`));
23
+ candidates.forEach((s) => console.error(chalk.dim(` - ${s.name}`)));
24
+ process.exit(1);
25
+ }
26
+ const set = candidates[0];
27
+ (0, config_js_1.writeProject)({ setId: set.id, setName: set.name });
28
+ console.log(chalk.green(`✓ Set actif : "${set.name}"`));
29
+ }
@@ -0,0 +1 @@
1
+ export declare function whoami(): Promise<void>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.whoami = whoami;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const session_js_1 = require("../lib/session.js");
6
+ async function whoami() {
7
+ const { default: chalk } = await import("chalk");
8
+ const accessToken = await (0, session_js_1.requireAccessToken)();
9
+ const me = await (0, api_js_1.getMe)(accessToken);
10
+ console.log(`${me.email}${me.name ? chalk.dim(` (${me.name})`) : ""}`);
11
+ }
package/dist/index.js CHANGED
@@ -5,6 +5,15 @@ const commander_1 = require("commander");
5
5
  const login_js_1 = require("./commands/login.js");
6
6
  const init_js_1 = require("./commands/init.js");
7
7
  const run_js_1 = require("./commands/run.js");
8
+ const proxy_js_1 = require("./commands/proxy.js");
9
+ const logout_js_1 = require("./commands/logout.js");
10
+ const status_js_1 = require("./commands/status.js");
11
+ const sets_js_1 = require("./commands/sets.js");
12
+ const use_js_1 = require("./commands/use.js");
13
+ const keys_js_1 = require("./commands/keys.js");
14
+ const doctor_js_1 = require("./commands/doctor.js");
15
+ const open_js_1 = require("./commands/open.js");
16
+ const whoami_js_1 = require("./commands/whoami.js");
8
17
  const program = new commander_1.Command();
9
18
  program
10
19
  .name("elding")
@@ -39,4 +48,88 @@ program
39
48
  process.exit(1);
40
49
  });
41
50
  });
51
+ program
52
+ .command("proxy <cmd>")
53
+ .description("Lancer une commande derrière un proxy local qui injecte les clés — jamais en mémoire de l'app")
54
+ .option("-v, --verbose", "Logger chaque requête (méthode/host/status/latence)")
55
+ .allowUnknownOption()
56
+ .action(async (cmd, options, command) => {
57
+ const args = command.args.slice(1);
58
+ await (0, proxy_js_1.proxy)(cmd, args, !!options.verbose).catch((err) => {
59
+ console.error(err.message);
60
+ process.exit(1);
61
+ });
62
+ });
63
+ program
64
+ .command("logout")
65
+ .description("Révoquer le token et le supprimer localement")
66
+ .action(async () => {
67
+ await (0, logout_js_1.logout)().catch((err) => {
68
+ console.error(err.message);
69
+ process.exit(1);
70
+ });
71
+ });
72
+ program
73
+ .command("status")
74
+ .description("Afficher l'état de connexion, le set actif et le proxy")
75
+ .action(async () => {
76
+ await (0, status_js_1.status)().catch((err) => {
77
+ console.error(err.message);
78
+ process.exit(1);
79
+ });
80
+ });
81
+ program
82
+ .command("sets")
83
+ .description("Lister les sets accessibles")
84
+ .action(async () => {
85
+ await (0, sets_js_1.sets)().catch((err) => {
86
+ console.error(err.message);
87
+ process.exit(1);
88
+ });
89
+ });
90
+ program
91
+ .command("use <name>")
92
+ .description("Changer le set actif du projet")
93
+ .action(async (name) => {
94
+ await (0, use_js_1.use)(name).catch((err) => {
95
+ console.error(err.message);
96
+ process.exit(1);
97
+ });
98
+ });
99
+ program
100
+ .command("keys")
101
+ .description("Lister les noms de clés du set actif (jamais les valeurs)")
102
+ .action(async () => {
103
+ await (0, keys_js_1.keys)().catch((err) => {
104
+ console.error(err.message);
105
+ process.exit(1);
106
+ });
107
+ });
108
+ program
109
+ .command("doctor")
110
+ .description("Diagnostiquer la configuration (connexion, set, clés, proxy)")
111
+ .action(async () => {
112
+ await (0, doctor_js_1.doctor)().catch((err) => {
113
+ console.error(err.message);
114
+ process.exit(1);
115
+ });
116
+ });
117
+ program
118
+ .command("open")
119
+ .description("Ouvrir le vault dans le navigateur")
120
+ .action(async () => {
121
+ await (0, open_js_1.open)().catch((err) => {
122
+ console.error(err.message);
123
+ process.exit(1);
124
+ });
125
+ });
126
+ program
127
+ .command("whoami")
128
+ .description("Afficher l'utilisateur connecté")
129
+ .action(async () => {
130
+ await (0, whoami_js_1.whoami)().catch((err) => {
131
+ console.error(err.message);
132
+ process.exit(1);
133
+ });
134
+ });
42
135
  program.parse();
package/dist/lib/api.d.ts CHANGED
@@ -1,7 +1,22 @@
1
+ export declare const BASE_URL: string;
1
2
  export type ApiKeySetItem = {
2
3
  id: string;
3
4
  name: string;
4
5
  };
6
+ export type KeyItem = {
7
+ name: string;
8
+ allowedHost: string | null;
9
+ };
10
+ export declare function listKeys(accessToken: string, setId: string): Promise<KeyItem[]>;
5
11
  export declare function exchangeToken(refreshToken: string): Promise<string>;
12
+ export declare function revokeToken(refreshToken: string): Promise<void>;
13
+ export declare function getMe(accessToken: string): Promise<{
14
+ email: string;
15
+ name: string | null;
16
+ }>;
6
17
  export declare function listSets(accessToken: string): Promise<ApiKeySetItem[]>;
7
- export declare function fetchSecrets(accessToken: string, setId: string): Promise<Record<string, string>>;
18
+ export type SecretsResult = {
19
+ secrets: Record<string, string>;
20
+ hosts: Record<string, string>;
21
+ };
22
+ export declare function fetchSecrets(accessToken: string, setId: string, mode?: "run" | "proxy"): Promise<SecretsResult>;
package/dist/lib/api.js CHANGED
@@ -1,11 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASE_URL = void 0;
4
+ exports.listKeys = listKeys;
3
5
  exports.exchangeToken = exchangeToken;
6
+ exports.revokeToken = revokeToken;
7
+ exports.getMe = getMe;
4
8
  exports.listSets = listSets;
5
9
  exports.fetchSecrets = fetchSecrets;
6
- const BASE_URL = process.env.ELDING_API_URL ?? "https://app.elding.io";
10
+ exports.BASE_URL = process.env.ELDING_API_URL ?? "https://app.elding.io";
11
+ async function listKeys(accessToken, setId) {
12
+ const res = await fetch(`${exports.BASE_URL}/api/cli/keys?setId=${encodeURIComponent(setId)}`, {
13
+ headers: { Authorization: `Bearer ${accessToken}` },
14
+ });
15
+ if (!res.ok)
16
+ throw new Error("Impossible de récupérer les clés");
17
+ const body = (await res.json());
18
+ return Array.isArray(body.keys) ? body.keys : [];
19
+ }
7
20
  async function exchangeToken(refreshToken) {
8
- const res = await fetch(`${BASE_URL}/api/cli/auth/token`, {
21
+ const res = await fetch(`${exports.BASE_URL}/api/cli/auth/token`, {
9
22
  method: "POST",
10
23
  headers: { "Content-Type": "application/json" },
11
24
  body: JSON.stringify({ refreshToken }),
@@ -15,8 +28,26 @@ async function exchangeToken(refreshToken) {
15
28
  throw new Error(body.error ?? "Impossible d'obtenir un access token");
16
29
  return body.accessToken;
17
30
  }
31
+ async function revokeToken(refreshToken) {
32
+ await fetch(`${exports.BASE_URL}/api/cli/auth/logout`, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify({ refreshToken }),
36
+ }).catch(() => { });
37
+ }
38
+ async function getMe(accessToken) {
39
+ const res = await fetch(`${exports.BASE_URL}/api/cli/me`, {
40
+ headers: { Authorization: `Bearer ${accessToken}` },
41
+ });
42
+ if (!res.ok)
43
+ throw new Error("Impossible de récupérer l'utilisateur");
44
+ const body = (await res.json());
45
+ if (!body.success || !body.user)
46
+ throw new Error("Réponse invalide");
47
+ return body.user;
48
+ }
18
49
  async function listSets(accessToken) {
19
- const res = await fetch(`${BASE_URL}/api/cli/sets`, {
50
+ const res = await fetch(`${exports.BASE_URL}/api/cli/sets`, {
20
51
  headers: { Authorization: `Bearer ${accessToken}` },
21
52
  });
22
53
  if (!res.ok)
@@ -24,10 +55,8 @@ async function listSets(accessToken) {
24
55
  const body = (await res.json());
25
56
  return Array.isArray(body.sets) ? body.sets : [];
26
57
  }
27
- async function fetchSecrets(accessToken, setId) {
28
- const res = await fetch(`${BASE_URL}/api/cli/secrets?setId=${encodeURIComponent(setId)}`, {
29
- headers: { Authorization: `Bearer ${accessToken}` },
30
- });
58
+ async function fetchSecrets(accessToken, setId, mode = "proxy") {
59
+ const res = await fetch(`${exports.BASE_URL}/api/cli/secrets?setId=${encodeURIComponent(setId)}&mode=${mode}`, { headers: { Authorization: `Bearer ${accessToken}` } });
31
60
  if (!res.ok) {
32
61
  const body = (await res.json().catch(() => ({})));
33
62
  throw new Error(body.error ?? `Erreur ${res.status}`);
@@ -35,7 +64,10 @@ async function fetchSecrets(accessToken, setId) {
35
64
  const body = (await res.json());
36
65
  if (!body.success || !body.secrets || typeof body.secrets !== "object")
37
66
  throw new Error("Réponse invalide");
38
- return sanitizeSecrets(body.secrets);
67
+ return {
68
+ secrets: sanitizeSecrets(body.secrets),
69
+ hosts: body.hosts && typeof body.hosts === "object" ? sanitizeSecrets(body.hosts) : {},
70
+ };
39
71
  }
40
72
  const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
41
73
  // Garde seulement les paires clé/valeur string sûres — évite injection d'objets ou pollution de prototype dans process.env
@@ -0,0 +1,6 @@
1
+ export type ProxyServer = {
2
+ url: string;
3
+ token: string;
4
+ close: () => void;
5
+ };
6
+ export declare function startProxy(secrets: Record<string, string>, hosts?: Record<string, string>, verbose?: boolean): Promise<ProxyServer>;
@@ -0,0 +1,154 @@
1
+ "use strict";
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.startProxy = startProxy;
7
+ const http_1 = __importDefault(require("http"));
8
+ const crypto_1 = require("crypto");
9
+ const PLACEHOLDER = /\{\{([A-Z0-9_]+)\}\}/g;
10
+ // Bloque loopback + plages privées + métadata cloud — empêche le proxy de servir de pivot SSRF
11
+ function isBlockedHost(hostname) {
12
+ const h = hostname.replace(/^\[|\]$/g, "");
13
+ if (h === "localhost" || h === "::1")
14
+ return true;
15
+ if (/^127\./.test(h))
16
+ return true;
17
+ if (/^10\./.test(h))
18
+ return true;
19
+ if (/^192\.168\./.test(h))
20
+ return true;
21
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(h))
22
+ return true;
23
+ if (/^169\.254\./.test(h))
24
+ return true; // link-local + metadata (169.254.169.254)
25
+ if (/^fe80:/i.test(h) || /^fc00:/i.test(h) || /^fd/i.test(h))
26
+ return true;
27
+ return false;
28
+ }
29
+ // hosts: map nom_secret -> domaine autorisé. Si défini, le secret ne peut être envoyé qu'à ce host.
30
+ function startProxy(secrets, hosts = {}, verbose = false) {
31
+ const token = (0, crypto_1.randomBytes)(24).toString("hex");
32
+ // Log une ligne par requête — jamais les valeurs, seulement les placeholders référencés
33
+ const log = (method, host, path, status, ms, note = "") => {
34
+ if (!verbose)
35
+ return;
36
+ const code = status === "BLOCKED" ? "BLOCKED" : String(status);
37
+ console.error(`[elding] ${method} ${host}${path} → ${code} ${ms}ms${note ? " " + note : ""}`);
38
+ };
39
+ const placeholdersIn = (headers) => {
40
+ const names = new Set();
41
+ for (const v of Object.values(headers))
42
+ for (const m of v.matchAll(PLACEHOLDER))
43
+ names.add(m[1]);
44
+ return names.size ? `{{${[...names].join(",")}}}` : "";
45
+ };
46
+ // Vérifie que chaque placeholder utilisé est autorisé pour ce host. Retourne le nom fautif, ou null.
47
+ const findHostViolation = (val, targetHost) => {
48
+ for (const m of val.matchAll(PLACEHOLDER)) {
49
+ const name = m[1];
50
+ const allowed = hosts[name];
51
+ if (allowed && allowed !== targetHost)
52
+ return name;
53
+ }
54
+ return null;
55
+ };
56
+ const substitute = (val) => val.replace(PLACEHOLDER, (_, name) => secrets[name] ?? "");
57
+ const server = http_1.default.createServer(async (req, res) => {
58
+ const started = Date.now();
59
+ try {
60
+ if (req.headers["x-elding-token"] !== token) {
61
+ res.writeHead(401);
62
+ res.end("unauthorized");
63
+ return;
64
+ }
65
+ const target = req.headers["x-elding-target"];
66
+ if (typeof target !== "string") {
67
+ res.writeHead(400);
68
+ res.end("missing x-elding-target");
69
+ return;
70
+ }
71
+ let targetUrl;
72
+ try {
73
+ targetUrl = new URL(target);
74
+ }
75
+ catch {
76
+ res.writeHead(400);
77
+ res.end("bad target");
78
+ return;
79
+ }
80
+ if (targetUrl.protocol !== "https:") {
81
+ res.writeHead(400);
82
+ res.end("https only");
83
+ return;
84
+ }
85
+ if (isBlockedHost(targetUrl.hostname)) {
86
+ log(req.method ?? "?", targetUrl.hostname, req.url ?? "/", "BLOCKED", Date.now() - started, "host bloqué");
87
+ res.writeHead(403);
88
+ res.end("blocked host");
89
+ return;
90
+ }
91
+ const upstream = new URL(req.url ?? "/", targetUrl);
92
+ upstream.protocol = "https:";
93
+ upstream.host = targetUrl.host;
94
+ // Enforce host binding : un secret lié à un domaine ne peut partir que vers celui-ci
95
+ for (const v of Object.values(req.headers)) {
96
+ if (typeof v !== "string")
97
+ continue;
98
+ const bad = findHostViolation(v, targetUrl.hostname);
99
+ if (bad) {
100
+ log(req.method ?? "?", targetUrl.hostname, upstream.pathname, "BLOCKED", Date.now() - started, `${bad} non autorisé`);
101
+ res.writeHead(403);
102
+ res.end(`secret "${bad}" non autorisé pour ${targetUrl.hostname}`);
103
+ return;
104
+ }
105
+ }
106
+ const headers = {};
107
+ const rawHeaders = {};
108
+ for (const [k, v] of Object.entries(req.headers)) {
109
+ const lk = k.toLowerCase();
110
+ if (lk.startsWith("x-elding-"))
111
+ continue;
112
+ if (lk === "host" || lk === "connection" || lk === "content-length")
113
+ continue;
114
+ if (typeof v === "string") {
115
+ rawHeaders[k] = v;
116
+ headers[k] = substitute(v);
117
+ }
118
+ }
119
+ const chunks = [];
120
+ for await (const c of req)
121
+ chunks.push(c);
122
+ const hasBody = chunks.length > 0 && req.method !== "GET" && req.method !== "HEAD";
123
+ const upstreamRes = await fetch(upstream.toString(), {
124
+ method: req.method,
125
+ headers,
126
+ body: hasBody ? Buffer.concat(chunks) : undefined,
127
+ });
128
+ log(req.method ?? "?", targetUrl.hostname, upstream.pathname, upstreamRes.status, Date.now() - started, placeholdersIn(rawHeaders));
129
+ res.writeHead(upstreamRes.status, Object.fromEntries(upstreamRes.headers));
130
+ if (upstreamRes.body) {
131
+ const reader = upstreamRes.body.getReader();
132
+ for (;;) {
133
+ const { done, value } = await reader.read();
134
+ if (done)
135
+ break;
136
+ res.write(value);
137
+ }
138
+ }
139
+ res.end();
140
+ }
141
+ catch {
142
+ if (!res.headersSent)
143
+ res.writeHead(502);
144
+ res.end("proxy error");
145
+ }
146
+ });
147
+ return new Promise((resolve) => {
148
+ server.listen(0, "127.0.0.1", () => {
149
+ const addr = server.address();
150
+ const port = typeof addr === "object" && addr ? addr.port : 0;
151
+ resolve({ url: `http://127.0.0.1:${port}`, token, close: () => server.close() });
152
+ });
153
+ });
154
+ }
@@ -0,0 +1 @@
1
+ export declare function requireAccessToken(): Promise<string>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireAccessToken = requireAccessToken;
4
+ const config_js_1 = require("./config.js");
5
+ const api_js_1 = require("./api.js");
6
+ // Lit le token local, l'échange contre un access token. Lève une erreur claire sinon.
7
+ async function requireAccessToken() {
8
+ const config = (0, config_js_1.readConfig)();
9
+ if (!config)
10
+ throw new Error("Non connecté. Lancez `elding login` d'abord.");
11
+ try {
12
+ return await (0, api_js_1.exchangeToken)(config.refreshToken);
13
+ }
14
+ catch {
15
+ throw new Error("Token expiré ou révoqué. Relancez `elding login`.");
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elding/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Elding CLI — zero .env, secrets from vault",
5
5
  "bin": {
6
6
  "elding": "./dist/index.js"