@openparachute/hub 0.6.1-rc.1 → 0.6.1-rc.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparachute/hub",
3
- "version": "0.6.1-rc.1",
3
+ "version": "0.6.1-rc.2",
4
4
  "description": "parachute \u2014 the local hub for the Parachute ecosystem (discovery, ports, lifecycle, soon OAuth).",
5
5
  "license": "AGPL-3.0",
6
6
  "publishConfig": {
@@ -1696,7 +1696,76 @@ describe("parachute auth mint-token", () => {
1696
1696
  expect(stdout.trim().split(".").length).toBe(3);
1697
1697
  expect(stderr).toContain("--ttl is deprecated");
1698
1698
  expect(stderr).toContain("--expires-in");
1699
- expect(stderr).toContain("0.6.0");
1699
+ expect(stderr).toContain("future release");
1700
+ } finally {
1701
+ tmp.cleanup();
1702
+ }
1703
+ });
1704
+
1705
+ test("--ephemeral mints a short-lived (1h) token", async () => {
1706
+ const tmp = makeTmp();
1707
+ try {
1708
+ const deps: AuthDeps = {
1709
+ dbPath: tmp.dbPath,
1710
+ configDir: tmp.dir,
1711
+ isInteractive: () => false,
1712
+ };
1713
+ await captureOutput(() => auth(["set-password", "--password", "pw"], deps));
1714
+ const { code, stdout } = await captureOutput(() =>
1715
+ auth(["mint-token", "--scope", "vault:default:read", "--ephemeral"], deps),
1716
+ );
1717
+ expect(code).toBe(0);
1718
+ const token = stdout.trim();
1719
+ const db = openHubDb(tmp.dbPath);
1720
+ try {
1721
+ const validated = await validateAccessToken(db, token);
1722
+ const exp = validated.payload.exp as number;
1723
+ const iat = validated.payload.iat as number;
1724
+ expect(exp - iat).toBe(60 * 60);
1725
+ } finally {
1726
+ db.close();
1727
+ }
1728
+ } finally {
1729
+ tmp.cleanup();
1730
+ }
1731
+ });
1732
+
1733
+ test("--ephemeral is mutually exclusive with --expires-in", async () => {
1734
+ const tmp = makeTmp();
1735
+ try {
1736
+ const deps: AuthDeps = {
1737
+ dbPath: tmp.dbPath,
1738
+ configDir: tmp.dir,
1739
+ isInteractive: () => false,
1740
+ };
1741
+ await captureOutput(() => auth(["set-password", "--password", "pw"], deps));
1742
+ const { code, stdout, stderr } = await captureOutput(() =>
1743
+ auth(["mint-token", "--scope", "vault:read", "--ephemeral", "--expires-in", "3600"], deps),
1744
+ );
1745
+ expect(code).toBe(1);
1746
+ expect(stderr).toContain("--ephemeral");
1747
+ // No token leaked to stdout on the conflict error.
1748
+ expect(stdout).toBe("");
1749
+ } finally {
1750
+ tmp.cleanup();
1751
+ }
1752
+ });
1753
+
1754
+ test("--ephemeral is mutually exclusive with the deprecated --ttl too", async () => {
1755
+ const tmp = makeTmp();
1756
+ try {
1757
+ const deps: AuthDeps = {
1758
+ dbPath: tmp.dbPath,
1759
+ configDir: tmp.dir,
1760
+ isInteractive: () => false,
1761
+ };
1762
+ await captureOutput(() => auth(["set-password", "--password", "pw"], deps));
1763
+ const { code, stdout, stderr } = await captureOutput(() =>
1764
+ auth(["mint-token", "--scope", "vault:read", "--ephemeral", "--ttl", "1h"], deps),
1765
+ );
1766
+ expect(code).toBe(1);
1767
+ expect(stderr).toContain("--ephemeral");
1768
+ expect(stdout).toBe("");
1700
1769
  } finally {
1701
1770
  tmp.cleanup();
1702
1771
  }
@@ -120,10 +120,13 @@ Usage:
120
120
  Mint a fresh ~/.parachute/operator.token
121
121
  (set = install|start|expose|auth|vault|admin,
122
122
  default admin)
123
- parachute auth mint-token --scope <scope> [--aud <aud>] [--expires-in <seconds>]
123
+ parachute auth mint-token --scope <scope> [--aud <aud>]
124
+ [--ephemeral | --expires-in <seconds>]
124
125
  [--sub <sub>] [--permissions <json>]
125
126
  Mint a scope-narrow JWT against the
126
127
  operator's identity (stdout = JWT).
128
+ --ephemeral = short-lived (1h), ideal for
129
+ scripting; default lifetime is 90d.
127
130
  --ttl <duration> is the deprecated
128
131
  alias (use --expires-in seconds).
129
132
  parachute auth revoke-token <jti> Mark a registry-row token revoked
@@ -194,6 +197,12 @@ audience defaults via the same inference rule the OAuth flow uses
194
197
  colon-prefixed scope's namespace, fallback \`hub\`). Lifetime defaults
195
198
  to 90d, caps at 365d.
196
199
 
200
+ --ephemeral mints a short-lived (1h) token — the right default for
201
+ scripting, where the credential only needs to outlive the script run.
202
+ Prefer it over a long-lived token for one-off automation:
203
+ \`parachute auth mint-token --scope vault:default:read --ephemeral\`.
204
+ Mutually exclusive with --expires-in / --ttl.
205
+
197
206
  --scope accepts space-separated multi-scope (e.g.
198
207
  \`--scope "vault:default:read agent:wovenboulder:invoke"\`).
199
208
 
@@ -201,7 +210,7 @@ to 90d, caps at 365d.
201
210
  \`--expires-in 86400\` for 1 day). The legacy \`--ttl\` flag accepts a
202
211
  duration suffix (\`90d\` / \`24h\` / \`30m\` / \`60s\`) and is supported as
203
212
  a deprecated alias; passing it emits a one-line stderr deprecation
204
- notice. \`--ttl\` will be removed in 0.6.0.
213
+ notice. \`--ttl\` will be removed in a future release.
205
214
 
206
215
  --permissions accepts a JSON object encoding fine-grained constraints
207
216
  beyond OAuth scope (e.g.
@@ -748,6 +757,8 @@ interface MintTokenFlags {
748
757
  permissions?: string;
749
758
  /** True when --ttl was used (deprecated alias). Triggers a one-line stderr warning. */
750
759
  ttlDeprecationSeen?: boolean;
760
+ /** --ephemeral: short-lived token (EPHEMERAL_TTL_SECONDS), the scripting default. */
761
+ ephemeral?: boolean;
751
762
  error?: string;
752
763
  }
753
764
 
@@ -759,6 +770,7 @@ function parseMintTokenFlags(args: readonly string[]): MintTokenFlags {
759
770
  let sub: string | undefined;
760
771
  let permissions: string | undefined;
761
772
  let ttlDeprecationSeen = false;
773
+ let ephemeral = false;
762
774
  for (let i = 0; i < args.length; i++) {
763
775
  const a = args[i];
764
776
  if (a === "--scope") {
@@ -805,6 +817,8 @@ function parseMintTokenFlags(args: readonly string[]): MintTokenFlags {
805
817
  } else if (a?.startsWith("--permissions=")) {
806
818
  permissions = a.slice("--permissions=".length);
807
819
  if (!permissions) return { error: "--permissions requires a value" };
820
+ } else if (a === "--ephemeral") {
821
+ ephemeral = true;
808
822
  } else {
809
823
  return { error: `unknown flag "${a}"` };
810
824
  }
@@ -812,11 +826,21 @@ function parseMintTokenFlags(args: readonly string[]): MintTokenFlags {
812
826
  if (ttl !== undefined && expiresIn !== undefined) {
813
827
  return { error: "pass --expires-in OR --ttl, not both (--ttl is the deprecated alias)" };
814
828
  }
815
- return { scope, aud, ttl, expiresIn, sub, permissions, ttlDeprecationSeen };
829
+ if (ephemeral && (ttl !== undefined || expiresIn !== undefined)) {
830
+ return {
831
+ error: "pass --ephemeral OR an explicit lifetime (--expires-in/--ttl), not both",
832
+ };
833
+ }
834
+ return { scope, aud, ttl, expiresIn, sub, permissions, ttlDeprecationSeen, ephemeral };
816
835
  }
817
836
 
818
837
  const MINT_TOKEN_TTL_DEFAULT_SECONDS = 90 * 24 * 60 * 60;
819
838
  const MINT_TOKEN_TTL_MAX_SECONDS = 365 * 24 * 60 * 60;
839
+ // --ephemeral: short-lived token for scripting, where the credential only needs
840
+ // to outlive the script run. 1h is long enough to mint → iterate → run, short
841
+ // enough that a leaked scripting token isn't a standing liability like the 90d
842
+ // default would be.
843
+ const EPHEMERAL_TTL_SECONDS = 60 * 60;
820
844
 
821
845
  /**
822
846
  * Parse a Go-ish duration string: integer + one of d/h/m/s. Caps at 365d.
@@ -870,7 +894,7 @@ async function runMintToken(args: readonly string[], deps: AuthDeps): Promise<nu
870
894
  if (!flags.scope) {
871
895
  console.error("parachute auth mint-token: --scope is required");
872
896
  console.error(
873
- "usage: parachute auth mint-token --scope <scope> [--aud <aud>] [--expires-in <seconds>] [--sub <sub>] [--permissions <json>]",
897
+ "usage: parachute auth mint-token --scope <scope> [--aud <aud>] [--ephemeral | --expires-in <seconds>] [--sub <sub>] [--permissions <json>]",
874
898
  );
875
899
  return 1;
876
900
  }
@@ -918,7 +942,11 @@ async function runMintToken(args: readonly string[], deps: AuthDeps): Promise<nu
918
942
  }
919
943
 
920
944
  let ttlSeconds = MINT_TOKEN_TTL_DEFAULT_SECONDS;
921
- if (flags.expiresIn) {
945
+ if (flags.ephemeral) {
946
+ // Short-lived scripting default — takes precedence (the parse layer already
947
+ // rejected --ephemeral alongside an explicit --expires-in/--ttl).
948
+ ttlSeconds = EPHEMERAL_TTL_SECONDS;
949
+ } else if (flags.expiresIn) {
922
950
  const parsed = parseExpiresIn(flags.expiresIn);
923
951
  if ("error" in parsed) {
924
952
  console.error(`parachute auth mint-token: ${parsed.error}`);
@@ -935,7 +963,7 @@ async function runMintToken(args: readonly string[], deps: AuthDeps): Promise<nu
935
963
  }
936
964
  if (flags.ttlDeprecationSeen) {
937
965
  console.error(
938
- "parachute auth mint-token: --ttl is deprecated; use --expires-in <seconds> instead (will be removed in 0.6.0)",
966
+ "parachute auth mint-token: --ttl is deprecated; use --expires-in <seconds> instead (will be removed in a future release)",
939
967
  );
940
968
  }
941
969