fix: Add username to the tokenStoreKey so the saved token is invalidated when the user changes in the environment (#426)

Co-authored-by: Gregory Schier <gschier1990@gmail.com>
This commit is contained in:
Michaël Arnauts
2026-08-14 13:50:48 -07:00
committed by GitHub
co-authored by Gregory Schier
parent 4f03c5c390
commit be004425fa
4 changed files with 68 additions and 0 deletions
@@ -32,6 +32,7 @@ export async function getPassword(
clientId,
accessTokenUrl,
authorizationUrl: null,
username,
};
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
accessTokenUrl,
+9
View File
@@ -103,6 +103,7 @@ export const plugin: PluginDefinition = {
authorizationUrl: stringArg(values, "authorizationUrl"),
accessTokenUrl: stringArg(values, "accessTokenUrl"),
clientId: stringArg(values, "clientId"),
username: usernameArg(values),
};
const token = await getToken(ctx, tokenArgs);
if (token == null) {
@@ -128,6 +129,7 @@ export const plugin: PluginDefinition = {
authorizationUrl: stringArg(values, "authorizationUrl"),
accessTokenUrl: stringArg(values, "accessTokenUrl"),
clientId: stringArg(values, "clientId"),
username: usernameArg(values),
};
if (await deleteToken(ctx, tokenArgs)) {
await ctx.toast.show({
@@ -478,6 +480,7 @@ export const plugin: PluginDefinition = {
authorizationUrl: stringArg(values, "authorizationUrl"),
accessTokenUrl: stringArg(values, "accessTokenUrl"),
clientId: stringArg(values, "clientId"),
username: usernameArg(values),
};
const token = await getToken(ctx, tokenArgs);
if (token == null) {
@@ -609,6 +612,12 @@ function stringArgOrNull(
return `${arg}`;
}
/** Only the password grant stores its token under a username, so the others must not key by one */
function usernameArg(values: Record<string, JsonPrimitive | undefined>): string | null {
const grantType = String(values.grantType ?? defaultGrantType);
return grantType === "password" ? stringArgOrNull(values, "username") : null;
}
function stringArg(values: Record<string, JsonPrimitive | undefined>, name: string): string {
const arg = stringArgOrNull(values, name);
if (!arg) return "";
+2
View File
@@ -47,6 +47,7 @@ export interface TokenStoreArgs {
clientId: string;
accessTokenUrl: string | null;
authorizationUrl: string | null;
username?: string | null;
}
/**
@@ -59,6 +60,7 @@ function tokenStoreKey(args: TokenStoreArgs) {
if (args.clientId) hash.update(args.clientId.trim());
if (args.accessTokenUrl) hash.update(args.accessTokenUrl.trim().replace(/^https?:\/\//, ""));
if (args.authorizationUrl) hash.update(args.authorizationUrl.trim().replace(/^https?:\/\//, ""));
if (args.username) hash.update(args.username);
const key = hash.digest("hex");
return ["token", key].join("::");
}
+56
View File
@@ -0,0 +1,56 @@
import { describe, expect, test } from "vite-plus/test";
import type { TokenStoreArgs } from "../src/store";
import { getToken, storeToken } from "../src/store";
function createMockContext() {
const values = new Map<string, unknown>();
return {
store: {
async set<T>(key: string, value: T) {
values.set(key, value);
},
async get<T>(key: string) {
return values.get(key) as T | undefined;
},
},
} as never;
}
describe("token store", () => {
test("separates password grant tokens when the username changes", async () => {
const ctx = createMockContext();
const aliceArgs: TokenStoreArgs = {
contextId: "request-1",
clientId: "client-123",
accessTokenUrl: "https://auth.example.com/token",
authorizationUrl: null,
username: "alice@example.com",
};
const bobArgs: TokenStoreArgs = { ...aliceArgs, username: "bob@example.com" };
await storeToken(ctx, aliceArgs, { access_token: "alice-token" });
expect((await getToken(ctx, aliceArgs))?.response.access_token).toBe("alice-token");
expect(await getToken(ctx, bobArgs)).toBeUndefined();
});
test("keeps the same key for grants without a username", async () => {
const ctx = createMockContext();
const args: TokenStoreArgs = {
contextId: "request-1",
clientId: "client-123",
accessTokenUrl: "https://auth.example.com/token",
authorizationUrl: null,
};
await storeToken(ctx, args, { access_token: "cc-token" });
expect((await getToken(ctx, { ...args, username: null }))?.response.access_token).toBe(
"cc-token",
);
expect((await getToken(ctx, { ...args, username: "" }))?.response.access_token).toBe("cc-token");
});
});