@elding/sdk 0.6.0 → 0.6.2

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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @elding/sdk
2
+
3
+ Utilise tes clés API sans jamais les écrire dans ton code. Elding garde la clé
4
+ hors de ton app : ton code manipule un placeholder, la vraie valeur est injectée
5
+ au dernier moment et verrouillée à un seul domaine (anti-exfiltration).
6
+
7
+ ```bash
8
+ npm install @elding/sdk
9
+ ```
10
+
11
+ ## Pour une clé API HTTP : `configure()`
12
+
13
+ Utilise `configure()` pour **toute clé d'API HTTP** (OpenAI, Mistral, Stripe,
14
+ Resend, etc.). Retourne de quoi faire un `fetch` normal, sans jamais exposer la clé.
15
+
16
+ ```ts
17
+ import { configure } from "@elding/sdk";
18
+
19
+ // "MISTRAL_KEY" = nom du secret dans ton vault Elding.
20
+ // 2e arg = le domaine de l'API (verrouillage anti-fuite).
21
+ const { apiKey, baseURL } = await configure("MISTRAL_KEY", "https://api.mistral.ai");
22
+
23
+ const res = await fetch(`${baseURL}/v1/chat/completions`, {
24
+ method: "POST",
25
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
26
+ body: JSON.stringify({ model: "mistral-small-latest", messages: [...] }),
27
+ });
28
+ ```
29
+
30
+ Important :
31
+ - **Utilise toujours `baseURL`** dans ton `fetch`, jamais l'URL de l'API en dur.
32
+ En dev, `baseURL` pointe vers le proxy local qui injecte la vraie clé.
33
+ - Mets `apiKey` dans un **header** (`Authorization`, `x-api-key`…), jamais dans l'URL.
34
+ - `configure()` est réservé aux **API HTTP**. Pour autre chose, voir `secret()`.
35
+
36
+ ## Pour un secret non-HTTP : `secret()`
37
+
38
+ `DATABASE_URL`, `REDIS_URL`, `JWT_SECRET`… tout ce qui n'est pas une API HTTP.
39
+ Retourne la valeur brute. Jamais dans `process.env`, effacée de la mémoire après
40
+ 5 min (réglable via `ELDING_CACHE_TTL_MS`).
41
+
42
+ ```ts
43
+ import { secret } from "@elding/sdk";
44
+
45
+ const dbUrl = await secret("DATABASE_URL");
46
+ ```
47
+
48
+ ## Plusieurs secrets d'un coup : `client()`
49
+
50
+ ```ts
51
+ import { client } from "@elding/sdk";
52
+
53
+ const elding = await client();
54
+ const dbUrl = elding.secret("DATABASE_URL");
55
+ ```
56
+
57
+ ## Règle simple
58
+
59
+ - Clé qui part dans une **requête HTTP** → `configure(nom, "https://api.exemple.com")`.
60
+ - N'importe quel **autre secret** → `secret(nom)`.
61
+ - **Jamais** de clé en dur, jamais dans `process.env`, jamais dans l'URL.
62
+
63
+ ## Dev vs prod
64
+
65
+ - **Dev** (`elding proxy -- npm run dev`) : la clé n'entre jamais dans ton process.
66
+ - **Prod** (serverless) : la clé est récupérée du vault au runtime. Elle reste
67
+ verrouillée au domaine, surveillée, révocable en un clic.
package/dist/apiUrl.js CHANGED
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveBaseUrl = resolveBaseUrl;
7
7
  const net_1 = __importDefault(require("net"));
8
- const DEFAULT_BASE_URL = "https://elding-dev.vercel.app";
8
+ const DEFAULT_BASE_URL = "https://elding.app";
9
9
  function stripBrackets(hostname) {
10
10
  return hostname.startsWith("[") && hostname.endsWith("]")
11
11
  ? hostname.slice(1, -1)
package/dist/client.d.ts CHANGED
@@ -2,6 +2,7 @@ export type ClientOptions = {
2
2
  setId?: string;
3
3
  refreshToken?: string;
4
4
  envFallback?: boolean;
5
+ cacheTtlMs?: number;
5
6
  };
6
7
  export declare class EldingClient {
7
8
  private secrets;
@@ -11,5 +12,6 @@ export declare class EldingClient {
11
12
  secret(name: string): string;
12
13
  secretOrUndefined(name: string): string | undefined;
13
14
  all(): Record<string, string>;
15
+ wipe(): void;
14
16
  allWithEnvFallback(): Record<string, string>;
15
17
  }
package/dist/client.js CHANGED
@@ -11,10 +11,10 @@ class EldingClient {
11
11
  this.envFallback = envFallback;
12
12
  }
13
13
  static async create(options = {}) {
14
- // 1. Resolve setId
15
- const setId = options.setId ?? (0, config_js_1.readProjectConfig)()?.setId;
14
+ // 1. Resolve setId : option explicite → .elding.json (dev) → ELDING_SET_ID (serverless/CI)
15
+ const setId = options.setId ?? (0, config_js_1.readProjectConfig)()?.setId ?? process.env.ELDING_SET_ID?.trim();
16
16
  if (!setId)
17
- throw new Error("[elding] setId introuvable. Lancez `elding init` ou passez setId en option.");
17
+ throw new Error("[elding] setId introuvable. Lancez `elding init`, passez setId en option, ou définissez ELDING_SET_ID.");
18
18
  // 2. Resolve refresh token
19
19
  const refreshToken = options.refreshToken ?? (0, config_js_1.readGlobalConfig)()?.refreshToken;
20
20
  if (!refreshToken)
@@ -48,6 +48,13 @@ class EldingClient {
48
48
  all() {
49
49
  return { ...this.secrets };
50
50
  }
51
+ // Efface les secrets de la mémoire du client. Best-effort : les strings JS sont
52
+ // immuables (on ne peut pas écraser les octets), mais on supprime les références
53
+ // pour que le GC les récupère et qu'aucun secret ne survive au-delà du TTL.
54
+ wipe() {
55
+ for (const name of Object.keys(this.secrets))
56
+ delete this.secrets[name];
57
+ }
51
58
  // Explicit escape hatch for legacy code that intentionally wants process.env fallback values.
52
59
  allWithEnvFallback() {
53
60
  if (!this.envFallback)
package/dist/config.js CHANGED
@@ -30,8 +30,14 @@ function tokenFromFile() {
30
30
  return null;
31
31
  }
32
32
  }
33
+ // Fallback serverless (Vercel, CI) : aucun keychain ni fichier ~/.elding,
34
+ // le token est fourni via la variable d'env ELDING_REFRESH_TOKEN.
35
+ function tokenFromEnv() {
36
+ const t = process.env.ELDING_REFRESH_TOKEN;
37
+ return t && t.trim() ? t.trim() : null;
38
+ }
33
39
  function readGlobalConfig() {
34
- const token = tokenFromKeychain() ?? tokenFromFile();
40
+ const token = tokenFromKeychain() ?? tokenFromFile() ?? tokenFromEnv();
35
41
  if (!token || !REFRESH_TOKEN.test(token))
36
42
  return null;
37
43
  return { refreshToken: token };
@@ -4,6 +4,8 @@ export type ProviderConfig = {
4
4
  baseURL?: string;
5
5
  defaultHeaders?: Record<string, string>;
6
6
  };
7
+ /** Efface immédiatement les secrets gardés en mémoire par le SDK. */
8
+ export declare function clearSecretCache(): void;
7
9
  export declare function configure(secretName: string, target: string, options?: ClientOptions): Promise<ProviderConfig>;
8
10
  /**
9
11
  * Récupère la VALEUR BRUTE d'un secret (DATABASE_URL, config, secrets non-HTTP).
package/dist/configure.js CHANGED
@@ -1,17 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearSecretCache = clearSecretCache;
3
4
  exports.configure = configure;
4
5
  exports.secret = secret;
5
6
  const proxy_js_1 = require("./proxy.js");
6
7
  const client_js_1 = require("./client.js");
7
8
  const SECRET_NAME = /^[A-Z0-9_]+$/;
8
- // Client mis en cache : on ne fetch les secrets qu'une seule fois par process.
9
+ // Durée de vie max des secrets en mémoire. Contrairement à un `.env` ou à
10
+ // `doppler run` (valeurs présentes tout le process), Elding ne garde les secrets
11
+ // que quelques minutes puis les efface : fenêtre d'exposition minimale.
12
+ // Défaut 5 min. Réglable via l'option `cacheTtlMs` ou ELDING_CACHE_TTL_MS.
13
+ const DEFAULT_CACHE_TTL_MS = 300_000;
14
+ function resolveTtl(options) {
15
+ const fromEnv = Number(process.env.ELDING_CACHE_TTL_MS);
16
+ const ttl = options.cacheTtlMs ?? (Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : DEFAULT_CACHE_TTL_MS);
17
+ return Math.max(1_000, ttl);
18
+ }
9
19
  let clientPromise = null;
20
+ let cachedAt = 0;
10
21
  function getClient(options) {
11
- if (!clientPromise)
22
+ const ttl = resolveTtl(options);
23
+ if (clientPromise && Date.now() - cachedAt > ttl)
24
+ clearSecretCache();
25
+ if (!clientPromise) {
26
+ cachedAt = Date.now();
12
27
  clientPromise = client_js_1.EldingClient.create(options);
28
+ // Auto-efface après le TTL, sans bloquer la sortie du process (timer unref).
29
+ const t = setTimeout(clearSecretCache, ttl);
30
+ t.unref?.();
31
+ }
13
32
  return clientPromise;
14
33
  }
34
+ /** Efface immédiatement les secrets gardés en mémoire par le SDK. */
35
+ function clearSecretCache() {
36
+ const p = clientPromise;
37
+ clientPromise = null;
38
+ cachedAt = 0;
39
+ p?.then((c) => c.wipe()).catch(() => { });
40
+ }
15
41
  /**
16
42
  * Config unifiée pour un provider : utilise le proxy si actif (clé jamais en mémoire),
17
43
  * sinon récupère la vraie clé du vault (mode client). Le même code marche en dev
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EldingClient, type ClientOptions } from "./client.js";
2
- export { configure, secret, type ProviderConfig } from "./configure.js";
2
+ export { configure, secret, clearSecretCache, type ProviderConfig } from "./configure.js";
3
3
  export { EldingClient };
4
4
  export type { ClientOptions };
5
5
  export { isProxyActive } from "./proxy.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isProxyActive = exports.EldingClient = exports.secret = exports.configure = void 0;
3
+ exports.isProxyActive = exports.EldingClient = exports.clearSecretCache = exports.secret = exports.configure = void 0;
4
4
  exports.client = client;
5
5
  const client_js_1 = require("./client.js");
6
6
  Object.defineProperty(exports, "EldingClient", { enumerable: true, get: function () { return client_js_1.EldingClient; } });
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "EldingClient", { enumerable: true, get: function
8
8
  var configure_js_1 = require("./configure.js");
9
9
  Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return configure_js_1.configure; } });
10
10
  Object.defineProperty(exports, "secret", { enumerable: true, get: function () { return configure_js_1.secret; } });
11
+ Object.defineProperty(exports, "clearSecretCache", { enumerable: true, get: function () { return configure_js_1.clearSecretCache; } });
11
12
  // ── Avancé : vérifier le mode (proxy actif ou non) ──
12
13
  var proxy_js_1 = require("./proxy.js");
13
14
  Object.defineProperty(exports, "isProxyActive", { enumerable: true, get: function () { return proxy_js_1.isProxyActive; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elding/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Elding SDK — accès aux secrets depuis le code, zéro .env",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",