@openparachute/vault 0.4.9-rc.11 → 0.4.9-rc.12
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 +1 -1
- package/src/auth-hub-jwt.test.ts +115 -0
- package/src/auth.ts +44 -6
package/package.json
CHANGED
package/src/auth-hub-jwt.test.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { writeVaultConfig, readVaultConfig } from "./config.ts";
|
|
|
30
30
|
import { getVaultStore, clearVaultStoreCache } from "./vault-store.ts";
|
|
31
31
|
import { authenticateVaultRequest, authenticateGlobalRequest } from "./auth.ts";
|
|
32
32
|
import { resetJwksCache, resetRevocationCache } from "./hub-jwt.ts";
|
|
33
|
+
import { generateToken, createToken } from "./token-store.ts";
|
|
33
34
|
|
|
34
35
|
interface Keypair {
|
|
35
36
|
privateKey: CryptoKey;
|
|
@@ -665,3 +666,117 @@ describe("authenticateVaultRequest — hub JWT tag-scoping (auth-unification C0)
|
|
|
665
666
|
});
|
|
666
667
|
}
|
|
667
668
|
});
|
|
669
|
+
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
// pvt_* deprecation warning (vault#282 Stage 1 — soft deprecation, NON-BREAKING)
|
|
672
|
+
//
|
|
673
|
+
// Every successful pvt_* (vault-DB token-store) authentication emits a
|
|
674
|
+
// one-time-per-token deprecation warning signalling that pvt_* tokens will be
|
|
675
|
+
// REJECTED at vault 0.6.0. Auth OUTCOMES are unchanged: the token still
|
|
676
|
+
// validates + authorizes exactly as before. Hub-issued JWTs — the migration
|
|
677
|
+
// target — never trigger the pvt_* warning.
|
|
678
|
+
//
|
|
679
|
+
// The `warnPvtDeprecationOnce` cache is process-global and keyed on the
|
|
680
|
+
// token's display id (`t_<hashprefix>`). Each test mints a fresh random pvt_*,
|
|
681
|
+
// so display ids never collide across tests; the "warns once" assertion holds
|
|
682
|
+
// within a single test by making two requests with the same token.
|
|
683
|
+
// ---------------------------------------------------------------------------
|
|
684
|
+
|
|
685
|
+
/** Mint a fresh pvt_* token directly into a vault's DB and return it. */
|
|
686
|
+
function mintPvtToken(vaultName: string): string {
|
|
687
|
+
const store = getVaultStore(vaultName);
|
|
688
|
+
const { fullToken } = generateToken();
|
|
689
|
+
createToken(store.db, fullToken, { label: "deprecation-test", permission: "full" });
|
|
690
|
+
return fullToken;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
describe("pvt_* deprecation warning (vault#282 Stage 1)", () => {
|
|
694
|
+
test("pvt_* auth still SUCCEEDS and emits the deprecation warning once", async () => {
|
|
695
|
+
seedVault("journal");
|
|
696
|
+
const token = mintPvtToken("journal");
|
|
697
|
+
const config = readVaultConfig("journal")!;
|
|
698
|
+
const store = getVaultStore("journal");
|
|
699
|
+
|
|
700
|
+
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
|
|
701
|
+
try {
|
|
702
|
+
// First request: auth outcome unchanged (full permission), warning fires.
|
|
703
|
+
const r1 = await authenticateVaultRequest(bearer(token), config, store.db);
|
|
704
|
+
expect("error" in r1).toBe(false);
|
|
705
|
+
if (!("error" in r1)) {
|
|
706
|
+
expect(r1.permission).toBe("full");
|
|
707
|
+
expect(r1.scopes).toContain("vault:admin");
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const deprecationCalls = warnSpy.mock.calls.filter((c) =>
|
|
711
|
+
String(c[0]).includes("[deprecation]"),
|
|
712
|
+
);
|
|
713
|
+
expect(deprecationCalls.length).toBe(1);
|
|
714
|
+
const msg = String(deprecationCalls[0]![0]);
|
|
715
|
+
// Shape: names pvt_*, the 0.6.0 rejection, the issue, the mint paths, the guide.
|
|
716
|
+
expect(msg).toContain("pvt_* token");
|
|
717
|
+
expect(msg).toContain("DEPRECATED");
|
|
718
|
+
expect(msg).toContain("REJECTED at vault 0.6.0");
|
|
719
|
+
expect(msg).toContain("vault#282");
|
|
720
|
+
expect(msg).toContain("parachute vault mcp-install");
|
|
721
|
+
expect(msg).toContain("parachute auth mint-token");
|
|
722
|
+
expect(msg).toContain("UPGRADING.md");
|
|
723
|
+
// The display id of the presented token appears in the message.
|
|
724
|
+
expect(msg).toMatch(/pvt_\* token t_[0-9a-f]+ authenticated/);
|
|
725
|
+
|
|
726
|
+
// Second request with the SAME token: still authorizes, no second warn.
|
|
727
|
+
const r2 = await authenticateVaultRequest(bearer(token), config, store.db);
|
|
728
|
+
expect("error" in r2).toBe(false);
|
|
729
|
+
const stillOne = warnSpy.mock.calls.filter((c) =>
|
|
730
|
+
String(c[0]).includes("[deprecation]"),
|
|
731
|
+
);
|
|
732
|
+
expect(stillOne.length).toBe(1);
|
|
733
|
+
} finally {
|
|
734
|
+
warnSpy.mockRestore();
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("pvt_* auth on the global (unified) surface also SUCCEEDS and warns once", async () => {
|
|
739
|
+
seedVault("journal");
|
|
740
|
+
const token = mintPvtToken("journal");
|
|
741
|
+
|
|
742
|
+
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
|
|
743
|
+
try {
|
|
744
|
+
const result = await authenticateGlobalRequest(bearer(token));
|
|
745
|
+
expect("error" in result).toBe(false);
|
|
746
|
+
if (!("error" in result)) expect(result.permission).toBe("full");
|
|
747
|
+
|
|
748
|
+
const deprecationCalls = warnSpy.mock.calls.filter((c) =>
|
|
749
|
+
String(c[0]).includes("[deprecation]"),
|
|
750
|
+
);
|
|
751
|
+
expect(deprecationCalls.length).toBe(1);
|
|
752
|
+
expect(String(deprecationCalls[0]![0])).toContain("vault#282");
|
|
753
|
+
} finally {
|
|
754
|
+
warnSpy.mockRestore();
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test("hub-JWT auth does NOT emit the pvt_* deprecation warning", async () => {
|
|
759
|
+
seedVault("journal");
|
|
760
|
+
const token = await signJwt(kp, {
|
|
761
|
+
iss: fixture.origin,
|
|
762
|
+
aud: "vault.journal",
|
|
763
|
+
scope: "vault:journal:write",
|
|
764
|
+
});
|
|
765
|
+
const config = readVaultConfig("journal")!;
|
|
766
|
+
const store = getVaultStore("journal");
|
|
767
|
+
|
|
768
|
+
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
|
|
769
|
+
try {
|
|
770
|
+
const result = await authenticateVaultRequest(bearer(token), config, store.db);
|
|
771
|
+
// Auth succeeds (the migration target works) ...
|
|
772
|
+
expect("error" in result).toBe(false);
|
|
773
|
+
// ... and no pvt_* deprecation warning is emitted for a hub JWT.
|
|
774
|
+
const deprecationCalls = warnSpy.mock.calls.filter((c) =>
|
|
775
|
+
String(c[0]).includes("[deprecation]"),
|
|
776
|
+
);
|
|
777
|
+
expect(deprecationCalls.length).toBe(0);
|
|
778
|
+
} finally {
|
|
779
|
+
warnSpy.mockRestore();
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
});
|
package/src/auth.ts
CHANGED
|
@@ -169,6 +169,40 @@ export function warnLegacyOnce(cacheKey: string, context: string): void {
|
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Doc link operators are sent to when a `pvt_*` token authenticates. Points
|
|
174
|
+
* at the pvt_* → hub-JWT migration section of vault's UPGRADING.md.
|
|
175
|
+
*/
|
|
176
|
+
const PVT_MIGRATION_DOC =
|
|
177
|
+
"https://github.com/ParachuteComputer/parachute-vault/blob/main/UPGRADING.md#pvt_-token-deprecation--will-be-rejected-at-060";
|
|
178
|
+
|
|
179
|
+
// One-shot pvt_* deprecation warning tracker, keyed by the token's display id
|
|
180
|
+
// (`t_<hashprefix>`) so each distinct token warns exactly once per process.
|
|
181
|
+
const warnedPvtTokens = new Set<string>();
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Log a one-time (per token-hash) deprecation warning for a successfully-
|
|
185
|
+
* authenticated `pvt_*` vault-DB token. Stage 1 of vault#282: pvt_* still
|
|
186
|
+
* AUTHENTICATES and AUTHORIZES exactly as before — this only signals that the
|
|
187
|
+
* credential is on a deprecation clock and will be REJECTED at vault 0.6.0.
|
|
188
|
+
*
|
|
189
|
+
* Keyed on the token's display id (`t_<hashprefix>` — the `jti` ResolvedToken
|
|
190
|
+
* surfaces) so a given token logs once per process regardless of how many
|
|
191
|
+
* requests it makes or whether it carries explicit scopes. Folds in the
|
|
192
|
+
* narrower pre-existing "vault token without scopes column" warning — a legacy-
|
|
193
|
+
* derived pvt_* gets THIS warning, not both, so we never double-warn one token.
|
|
194
|
+
*/
|
|
195
|
+
export function warnPvtDeprecationOnce(displayId: string): void {
|
|
196
|
+
if (warnedPvtTokens.has(displayId)) return;
|
|
197
|
+
warnedPvtTokens.add(displayId);
|
|
198
|
+
console.warn(
|
|
199
|
+
`[deprecation] pvt_* token ${displayId} authenticated — pvt_* tokens are DEPRECATED and will be REJECTED at vault 0.6.0 (vault#282). ` +
|
|
200
|
+
"Migrate to a hub-issued JWT: run `parachute vault mcp-install` (MCP clients) or " +
|
|
201
|
+
"`parachute auth mint-token --scope vault:<name>:<verb>` (scripts). " +
|
|
202
|
+
`Guide: ${PVT_MIGRATION_DOC}.`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
172
206
|
/** Read-only tools (the only tools allowed for "read" permission). */
|
|
173
207
|
const READ_TOOLS = new Set([
|
|
174
208
|
"query-notes",
|
|
@@ -292,9 +326,12 @@ export async function authenticateVaultRequest(
|
|
|
292
326
|
),
|
|
293
327
|
};
|
|
294
328
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
329
|
+
// vault#282 Stage 1: every successful pvt_* (vault-DB token-store)
|
|
330
|
+
// authentication is on a deprecation clock. One-time per token-hash
|
|
331
|
+
// (keyed on the display id). Folds in the old narrower "vault token
|
|
332
|
+
// without scopes column" warning — a legacy-derived pvt_* gets THIS
|
|
333
|
+
// warning, not both. Auth OUTCOME is unchanged; this only signals.
|
|
334
|
+
warnPvtDeprecationOnce(resolved.jti);
|
|
298
335
|
return {
|
|
299
336
|
permission: resolved.permission,
|
|
300
337
|
scopes: resolved.scopes,
|
|
@@ -621,9 +658,10 @@ export async function authenticateGlobalRequest(
|
|
|
621
658
|
const store = getVaultStore(vaultName);
|
|
622
659
|
const resolved = resolveToken(store.db, key);
|
|
623
660
|
if (resolved) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
661
|
+
// vault#282 Stage 1: pvt_* resolved on the unified surface is on the
|
|
662
|
+
// same deprecation clock. One-time per token-hash; folds in the old
|
|
663
|
+
// narrower legacy-scopes warning. Auth OUTCOME unchanged.
|
|
664
|
+
warnPvtDeprecationOnce(resolved.jti);
|
|
627
665
|
return {
|
|
628
666
|
permission: resolved.permission,
|
|
629
667
|
scopes: resolved.scopes,
|