fix(auth): await OAuth storage load() to fix remote OAuth races
This commit is contained in:
parent
7bd26c938e
commit
a24091484c
25 changed files with 245 additions and 133 deletions
|
|
@ -896,7 +896,7 @@ function App({
|
||||||
|
|
||||||
const handleClearOAuth = useCallback(async () => {
|
const handleClearOAuth = useCallback(async () => {
|
||||||
if (!selectedInspectorClient) return;
|
if (!selectedInspectorClient) return;
|
||||||
selectedInspectorClient.clearOAuthTokens();
|
await selectedInspectorClient.clearOAuthTokens();
|
||||||
setOauthStatus("idle");
|
setOauthStatus("idle");
|
||||||
setOauthMessage(null);
|
setOauthMessage(null);
|
||||||
setConnectError(null);
|
setConnectError(null);
|
||||||
|
|
|
||||||
|
|
@ -2191,6 +2191,7 @@ function App() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
await webOAuthStorage.load();
|
||||||
const client = setupClientForServer(server, sessionId);
|
const client = setupClientForServer(server, sessionId);
|
||||||
setActiveServerId(server.id);
|
setActiveServerId(server.id);
|
||||||
try {
|
try {
|
||||||
|
|
@ -2246,7 +2247,7 @@ function App() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [servers, setupClientForServer, showReAuthBanner]);
|
}, [servers, setupClientForServer, showReAuthBanner, webOAuthStorage]);
|
||||||
|
|
||||||
const onToggleConnection = useCallback(
|
const onToggleConnection = useCallback(
|
||||||
async (id: string) => {
|
async (id: string) => {
|
||||||
|
|
@ -3273,7 +3274,7 @@ function App() {
|
||||||
const clearServerOAuthAndDisconnect = useCallback(
|
const clearServerOAuthAndDisconnect = useCallback(
|
||||||
async (server: { id: string; name: string; config: MCPServerConfig }) => {
|
async (server: { id: string; name: string; config: MCPServerConfig }) => {
|
||||||
const isActive = server.id === activeServerId;
|
const isActive = server.id === activeServerId;
|
||||||
const cleared = clearServerOAuthState({
|
const cleared = await clearServerOAuthState({
|
||||||
config: server.config,
|
config: server.config,
|
||||||
inspectorClient: isActive ? inspectorClient : null,
|
inspectorClient: isActive ? inspectorClient : null,
|
||||||
isActiveConnection: isActive,
|
isActiveConnection: isActive,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ function createStorage(
|
||||||
}> = {},
|
}> = {},
|
||||||
): OAuthStorage {
|
): OAuthStorage {
|
||||||
return {
|
return {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getTokens: vi.fn().mockResolvedValue(overrides.tokens),
|
getTokens: vi.fn().mockResolvedValue(overrides.tokens),
|
||||||
getClientInformation: vi.fn(async (_url, isPreregistered) =>
|
getClientInformation: vi.fn(async (_url, isPreregistered) =>
|
||||||
isPreregistered ? overrides.preregistered : overrides.dynamic,
|
isPreregistered ? overrides.preregistered : overrides.dynamic,
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ function createMemoryStorage(
|
||||||
const clientInfoByKey: Record<string, unknown> = {};
|
const clientInfoByKey: Record<string, unknown> = {};
|
||||||
const codeVerifierByKey: Record<string, string> = {};
|
const codeVerifierByKey: Record<string, string> = {};
|
||||||
return {
|
return {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getIdpSession: vi.fn(async (issuer: string) => idpSessions[issuer]),
|
getIdpSession: vi.fn(async (issuer: string) => idpSessions[issuer]),
|
||||||
saveIdpSession: vi.fn(async (issuer: string, updates) => {
|
saveIdpSession: vi.fn(async (issuer: string, updates) => {
|
||||||
idpSessions[issuer] = { ...idpSessions[issuer], ...updates };
|
idpSessions[issuer] = { ...idpSessions[issuer], ...updates };
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ describe("idpOidc refresh", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Object.keys(idpSessions).forEach((k) => delete idpSessions[k]);
|
Object.keys(idpSessions).forEach((k) => delete idpSessions[k]);
|
||||||
storage = {
|
storage = {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getIdpSession: vi.fn(async (issuer: string) => idpSessions[issuer]),
|
getIdpSession: vi.fn(async (issuer: string) => idpSessions[issuer]),
|
||||||
saveIdpSession: vi.fn(async (issuer: string, updates) => {
|
saveIdpSession: vi.fn(async (issuer: string, updates) => {
|
||||||
idpSessions[issuer] = { ...idpSessions[issuer], ...updates };
|
idpSessions[issuer] = { ...idpSessions[issuer], ...updates };
|
||||||
|
|
@ -264,6 +265,8 @@ describe("startIdpOidcAuthorization", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Object.keys(saved).forEach((k) => delete saved[k]);
|
Object.keys(saved).forEach((k) => delete saved[k]);
|
||||||
storage = {
|
storage = {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
|
getServerMetadata: vi.fn(() => null),
|
||||||
saveCodeVerifier: vi.fn(async (key: string, value: string) => {
|
saveCodeVerifier: vi.fn(async (key: string, value: string) => {
|
||||||
saved[`cv:${key}`] = value;
|
saved[`cv:${key}`] = value;
|
||||||
}),
|
}),
|
||||||
|
|
@ -331,6 +334,7 @@ describe("completeIdpOidcAuthorization", () => {
|
||||||
|
|
||||||
function buildStorage(overrides: Partial<OAuthStorage> = {}): OAuthStorage {
|
function buildStorage(overrides: Partial<OAuthStorage> = {}): OAuthStorage {
|
||||||
return {
|
return {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getServerMetadata: vi.fn(() => minimalOAuthAsMetadata(IDP_ISSUER)),
|
getServerMetadata: vi.fn(() => minimalOAuthAsMetadata(IDP_ISSUER)),
|
||||||
getClientInformation: vi.fn(async () => ({
|
getClientInformation: vi.fn(async () => ({
|
||||||
client_id: "idp-client",
|
client_id: "idp-client",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ describe("idpSession", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
storage = {
|
storage = {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getIdpSession: vi.fn(),
|
getIdpSession: vi.fn(),
|
||||||
saveIdpSession: vi.fn(),
|
saveIdpSession: vi.fn(),
|
||||||
clearIdpSession: vi.fn(),
|
clearIdpSession: vi.fn(),
|
||||||
|
|
@ -73,15 +74,15 @@ describe("idpSession", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearEmaIdpSession clears idp session, leg-1 key, and tagged resource servers", () => {
|
it("clearEmaIdpSession clears idp session, leg-1 key, and tagged resource servers", async () => {
|
||||||
clearEmaIdpSession(storage, "https://idp.test/");
|
await clearEmaIdpSession(storage, "https://idp.test/");
|
||||||
expect(storage.clearIdpSession).toHaveBeenCalledWith("https://idp.test");
|
expect(storage.clearIdpSession).toHaveBeenCalledWith("https://idp.test");
|
||||||
expect(storage.clear).toHaveBeenCalledWith("ema-idp:https://idp.test");
|
expect(storage.clear).toHaveBeenCalledWith("ema-idp:https://idp.test");
|
||||||
expect(storage.clearEnterpriseManagedResourceServers).toHaveBeenCalled();
|
expect(storage.clearEnterpriseManagedResourceServers).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearEmaIdpSession no-ops when issuer normalizes to empty", () => {
|
it("clearEmaIdpSession no-ops when issuer normalizes to empty", async () => {
|
||||||
clearEmaIdpSession(storage, "");
|
await clearEmaIdpSession(storage, "");
|
||||||
expect(storage.clearIdpSession).not.toHaveBeenCalled();
|
expect(storage.clearIdpSession).not.toHaveBeenCalled();
|
||||||
expect(storage.clear).not.toHaveBeenCalled();
|
expect(storage.clear).not.toHaveBeenCalled();
|
||||||
expect(
|
expect(
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@ describe("OAuthNavigation", () => {
|
||||||
|
|
||||||
function makeStorage(): OAuthStorage {
|
function makeStorage(): OAuthStorage {
|
||||||
return {
|
return {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getScope: vi.fn(() => undefined),
|
getScope: vi.fn(() => undefined),
|
||||||
getClientInformation: vi.fn(async () => undefined),
|
getClientInformation: vi.fn(async () => undefined),
|
||||||
saveClientInformation: vi.fn(async () => undefined),
|
saveClientInformation: vi.fn(async () => undefined),
|
||||||
|
|
@ -240,11 +241,11 @@ describe("OAuthNavigation", () => {
|
||||||
return new BaseOAuthClientProvider(SERVER, config);
|
return new BaseOAuthClientProvider(SERVER, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("clear() delegates to storage.clear with the server url", () => {
|
it("clear() delegates to storage.clear with the server url", async () => {
|
||||||
const storage = makeStorage();
|
const storage = makeStorage();
|
||||||
const provider = makeProvider(storage);
|
const provider = makeProvider(storage);
|
||||||
|
|
||||||
provider.clear();
|
await provider.clear();
|
||||||
|
|
||||||
expect(storage.clear).toHaveBeenCalledWith(SERVER);
|
expect(storage.clear).toHaveBeenCalledWith(SERVER);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
client_secret: "test-secret",
|
client_secret: "test-secret",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(testServerUrl, clientInfo, {
|
await storage.saveClientInformation(testServerUrl, clientInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
const result = await storage.getClientInformation(testServerUrl);
|
const result = await storage.getClientInformation(testServerUrl);
|
||||||
|
|
@ -93,7 +93,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
|
|
||||||
// Use the storage API instead of manually setting sessionStorage
|
// Use the storage API instead of manually setting sessionStorage
|
||||||
// since BrowserOAuthStorage now uses Zustand with a different storage format
|
// since BrowserOAuthStorage now uses Zustand with a different storage format
|
||||||
storage.savePreregisteredClientInformation(
|
await storage.savePreregisteredClientInformation(
|
||||||
testServerUrl,
|
testServerUrl,
|
||||||
preregisteredInfo,
|
preregisteredInfo,
|
||||||
);
|
);
|
||||||
|
|
@ -113,7 +113,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
client_id: "test-client-id",
|
client_id: "test-client-id",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(testServerUrl, clientInfo, {
|
await storage.saveClientInformation(testServerUrl, clientInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
const result = await storage.getClientInformation(testServerUrl);
|
const result = await storage.getClientInformation(testServerUrl);
|
||||||
|
|
@ -130,10 +130,10 @@ describe("BrowserOAuthStorage", () => {
|
||||||
client_id: "second-id",
|
client_id: "second-id",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(testServerUrl, firstInfo, {
|
await storage.saveClientInformation(testServerUrl, firstInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
storage.saveClientInformation(testServerUrl, secondInfo, {
|
await storage.saveClientInformation(testServerUrl, secondInfo, {
|
||||||
registrationKind: "cimd",
|
registrationKind: "cimd",
|
||||||
});
|
});
|
||||||
const result = await storage.getClientInformation(testServerUrl);
|
const result = await storage.getClientInformation(testServerUrl);
|
||||||
|
|
@ -163,7 +163,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
expires_in: 3600,
|
expires_in: 3600,
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveTokens(testServerUrl, tokens);
|
await storage.saveTokens(testServerUrl, tokens);
|
||||||
const result = await storage.getTokens(testServerUrl);
|
const result = await storage.getTokens(testServerUrl);
|
||||||
|
|
||||||
expect(result).toEqual(tokens);
|
expect(result).toEqual(tokens);
|
||||||
|
|
@ -177,7 +177,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveTokens(testServerUrl, tokens);
|
await storage.saveTokens(testServerUrl, tokens);
|
||||||
const result = await storage.getTokens(testServerUrl);
|
const result = await storage.getTokens(testServerUrl);
|
||||||
|
|
||||||
expect(result).toEqual(tokens);
|
expect(result).toEqual(tokens);
|
||||||
|
|
@ -194,7 +194,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
await storage.saveTokens(emaUrl, tokens, { enterpriseManaged: true });
|
await storage.saveTokens(emaUrl, tokens, { enterpriseManaged: true });
|
||||||
await storage.saveTokens(standardUrl, tokens);
|
await storage.saveTokens(standardUrl, tokens);
|
||||||
|
|
||||||
storage.clearEnterpriseManagedResourceServers();
|
await storage.clearEnterpriseManagedResourceServers();
|
||||||
|
|
||||||
expect(await storage.getTokens(emaUrl)).toBeUndefined();
|
expect(await storage.getTokens(emaUrl)).toBeUndefined();
|
||||||
expect(await storage.getTokens(standardUrl)).toEqual(tokens);
|
expect(await storage.getTokens(standardUrl)).toEqual(tokens);
|
||||||
|
|
@ -210,7 +210,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
it("should return stored code verifier", async () => {
|
it("should return stored code verifier", async () => {
|
||||||
const codeVerifier = "test-code-verifier";
|
const codeVerifier = "test-code-verifier";
|
||||||
|
|
||||||
storage.saveCodeVerifier(testServerUrl, codeVerifier);
|
await storage.saveCodeVerifier(testServerUrl, codeVerifier);
|
||||||
const result = await storage.getCodeVerifier(testServerUrl);
|
const result = await storage.getCodeVerifier(testServerUrl);
|
||||||
|
|
||||||
expect(result).toBe(codeVerifier);
|
expect(result).toBe(codeVerifier);
|
||||||
|
|
@ -221,7 +221,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
it("should save code verifier", async () => {
|
it("should save code verifier", async () => {
|
||||||
const codeVerifier = "test-code-verifier";
|
const codeVerifier = "test-code-verifier";
|
||||||
|
|
||||||
storage.saveCodeVerifier(testServerUrl, codeVerifier);
|
await storage.saveCodeVerifier(testServerUrl, codeVerifier);
|
||||||
const result = await storage.getCodeVerifier(testServerUrl);
|
const result = await storage.getCodeVerifier(testServerUrl);
|
||||||
|
|
||||||
expect(result).toBe(codeVerifier);
|
expect(result).toBe(codeVerifier);
|
||||||
|
|
@ -237,7 +237,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
it("should return stored scope", async () => {
|
it("should return stored scope", async () => {
|
||||||
const scope = "read write";
|
const scope = "read write";
|
||||||
|
|
||||||
storage.saveScope(testServerUrl, scope);
|
await storage.saveScope(testServerUrl, scope);
|
||||||
const result = await storage.getScope(testServerUrl);
|
const result = await storage.getScope(testServerUrl);
|
||||||
|
|
||||||
expect(result).toBe(scope);
|
expect(result).toBe(scope);
|
||||||
|
|
@ -248,7 +248,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
it("should save scope", async () => {
|
it("should save scope", async () => {
|
||||||
const scope = "read write";
|
const scope = "read write";
|
||||||
|
|
||||||
storage.saveScope(testServerUrl, scope);
|
await storage.saveScope(testServerUrl, scope);
|
||||||
const result = await storage.getScope(testServerUrl);
|
const result = await storage.getScope(testServerUrl);
|
||||||
|
|
||||||
expect(result).toBe(scope);
|
expect(result).toBe(scope);
|
||||||
|
|
@ -269,7 +269,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
response_types_supported: ["code"],
|
response_types_supported: ["code"],
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveServerMetadata(testServerUrl, metadata);
|
await storage.saveServerMetadata(testServerUrl, metadata);
|
||||||
const result = await storage.getServerMetadata(testServerUrl);
|
const result = await storage.getServerMetadata(testServerUrl);
|
||||||
|
|
||||||
expect(result).toEqual(metadata);
|
expect(result).toEqual(metadata);
|
||||||
|
|
@ -285,7 +285,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
response_types_supported: ["code"],
|
response_types_supported: ["code"],
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveServerMetadata(testServerUrl, metadata);
|
await storage.saveServerMetadata(testServerUrl, metadata);
|
||||||
const result = await storage.getServerMetadata(testServerUrl);
|
const result = await storage.getServerMetadata(testServerUrl);
|
||||||
|
|
||||||
expect(result).toEqual(metadata);
|
expect(result).toEqual(metadata);
|
||||||
|
|
@ -294,7 +294,7 @@ describe("BrowserOAuthStorage", () => {
|
||||||
|
|
||||||
describe("clearClientInformation", () => {
|
describe("clearClientInformation", () => {
|
||||||
it("removes the dynamically-registered client info by default", async () => {
|
it("removes the dynamically-registered client info by default", async () => {
|
||||||
storage.saveClientInformation(
|
await storage.saveClientInformation(
|
||||||
testServerUrl,
|
testServerUrl,
|
||||||
{ client_id: "dyn" },
|
{ client_id: "dyn" },
|
||||||
{ registrationKind: "dcr" },
|
{ registrationKind: "dcr" },
|
||||||
|
|
@ -302,18 +302,18 @@ describe("BrowserOAuthStorage", () => {
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toEqual({
|
expect(await storage.getClientInformation(testServerUrl)).toEqual({
|
||||||
client_id: "dyn",
|
client_id: "dyn",
|
||||||
});
|
});
|
||||||
storage.clearClientInformation(testServerUrl);
|
await storage.clearClientInformation(testServerUrl);
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes the preregistered client info when isPreregistered=true", async () => {
|
it("removes the preregistered client info when isPreregistered=true", async () => {
|
||||||
storage.savePreregisteredClientInformation(testServerUrl, {
|
await storage.savePreregisteredClientInformation(testServerUrl, {
|
||||||
client_id: "pre",
|
client_id: "pre",
|
||||||
});
|
});
|
||||||
expect(await storage.getClientInformation(testServerUrl, true)).toEqual({
|
expect(await storage.getClientInformation(testServerUrl, true)).toEqual({
|
||||||
client_id: "pre",
|
client_id: "pre",
|
||||||
});
|
});
|
||||||
storage.clearClientInformation(testServerUrl, true);
|
await storage.clearClientInformation(testServerUrl, true);
|
||||||
expect(
|
expect(
|
||||||
await storage.getClientInformation(testServerUrl, true),
|
await storage.getClientInformation(testServerUrl, true),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
|
|
@ -322,26 +322,26 @@ describe("BrowserOAuthStorage", () => {
|
||||||
|
|
||||||
describe("individual clear methods", () => {
|
describe("individual clear methods", () => {
|
||||||
it("clearTokens removes only tokens", async () => {
|
it("clearTokens removes only tokens", async () => {
|
||||||
storage.saveTokens(testServerUrl, {
|
await storage.saveTokens(testServerUrl, {
|
||||||
access_token: "t",
|
access_token: "t",
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
});
|
});
|
||||||
expect(await storage.getTokens(testServerUrl)).toBeDefined();
|
expect(await storage.getTokens(testServerUrl)).toBeDefined();
|
||||||
storage.clearTokens(testServerUrl);
|
await storage.clearTokens(testServerUrl);
|
||||||
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearCodeVerifier removes only the PKCE verifier", async () => {
|
it("clearCodeVerifier removes only the PKCE verifier", async () => {
|
||||||
storage.saveCodeVerifier(testServerUrl, "verifier");
|
await storage.saveCodeVerifier(testServerUrl, "verifier");
|
||||||
expect(storage.getCodeVerifier(testServerUrl)).toBe("verifier");
|
expect(storage.getCodeVerifier(testServerUrl)).toBe("verifier");
|
||||||
storage.clearCodeVerifier(testServerUrl);
|
await storage.clearCodeVerifier(testServerUrl);
|
||||||
expect(storage.getCodeVerifier(testServerUrl)).toBeUndefined();
|
expect(storage.getCodeVerifier(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearScope removes only the scope", async () => {
|
it("clearScope removes only the scope", async () => {
|
||||||
storage.saveScope(testServerUrl, "read");
|
await storage.saveScope(testServerUrl, "read");
|
||||||
expect(storage.getScope(testServerUrl)).toBe("read");
|
expect(storage.getScope(testServerUrl)).toBe("read");
|
||||||
storage.clearScope(testServerUrl);
|
await storage.clearScope(testServerUrl);
|
||||||
expect(storage.getScope(testServerUrl)).toBeUndefined();
|
expect(storage.getScope(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -352,9 +352,9 @@ describe("BrowserOAuthStorage", () => {
|
||||||
token_endpoint: "http://localhost:3000/token",
|
token_endpoint: "http://localhost:3000/token",
|
||||||
response_types_supported: ["code"],
|
response_types_supported: ["code"],
|
||||||
};
|
};
|
||||||
storage.saveServerMetadata(testServerUrl, metadata);
|
await storage.saveServerMetadata(testServerUrl, metadata);
|
||||||
expect(storage.getServerMetadata(testServerUrl)).toEqual(metadata);
|
expect(storage.getServerMetadata(testServerUrl)).toEqual(metadata);
|
||||||
storage.clearServerMetadata(testServerUrl);
|
await storage.clearServerMetadata(testServerUrl);
|
||||||
expect(storage.getServerMetadata(testServerUrl)).toBeNull();
|
expect(storage.getServerMetadata(testServerUrl)).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -369,12 +369,12 @@ describe("BrowserOAuthStorage", () => {
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(testServerUrl, clientInfo, {
|
await storage.saveClientInformation(testServerUrl, clientInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
storage.saveTokens(testServerUrl, tokens);
|
await storage.saveTokens(testServerUrl, tokens);
|
||||||
|
|
||||||
storage.clear(testServerUrl);
|
await storage.clear(testServerUrl);
|
||||||
|
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
||||||
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
||||||
|
|
@ -386,14 +386,14 @@ describe("BrowserOAuthStorage", () => {
|
||||||
client_id: "test-client-id",
|
client_id: "test-client-id",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(testServerUrl, clientInfo, {
|
await storage.saveClientInformation(testServerUrl, clientInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
storage.saveClientInformation(otherServerUrl, clientInfo, {
|
await storage.saveClientInformation(otherServerUrl, clientInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
|
|
||||||
storage.clear(testServerUrl);
|
await storage.clear(testServerUrl);
|
||||||
|
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
||||||
expect(await storage.getClientInformation(otherServerUrl)).toEqual(
|
expect(await storage.getClientInformation(otherServerUrl)).toEqual(
|
||||||
|
|
@ -415,10 +415,10 @@ describe("BrowserOAuthStorage", () => {
|
||||||
client_id: "client-2",
|
client_id: "client-2",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(server1Url, clientInfo1, {
|
await storage.saveClientInformation(server1Url, clientInfo1, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
storage.saveClientInformation(server2Url, clientInfo2, {
|
await storage.saveClientInformation(server2Url, clientInfo2, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
|
||||||
{ client_id: "dyn" },
|
{ client_id: "dyn" },
|
||||||
{ registrationKind: "dcr" },
|
{ registrationKind: "dcr" },
|
||||||
);
|
);
|
||||||
storage.clearClientInformation(serverUrl);
|
await storage.clearClientInformation(serverUrl);
|
||||||
expect(await storage.getClientInformation(serverUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(serverUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
|
||||||
await storage.savePreregisteredClientInformation(serverUrl, {
|
await storage.savePreregisteredClientInformation(serverUrl, {
|
||||||
client_id: "pre",
|
client_id: "pre",
|
||||||
});
|
});
|
||||||
storage.clearClientInformation(serverUrl, true);
|
await storage.clearClientInformation(serverUrl, true);
|
||||||
expect(await storage.getClientInformation(serverUrl, true)).toBeUndefined();
|
expect(await storage.getClientInformation(serverUrl, true)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -66,21 +66,21 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
|
||||||
const tokens = { access_token: "t", token_type: "Bearer" };
|
const tokens = { access_token: "t", token_type: "Bearer" };
|
||||||
await storage.saveTokens(serverUrl, tokens);
|
await storage.saveTokens(serverUrl, tokens);
|
||||||
expect(await storage.getTokens(serverUrl)).toEqual(tokens);
|
expect(await storage.getTokens(serverUrl)).toEqual(tokens);
|
||||||
storage.clearTokens(serverUrl);
|
await storage.clearTokens(serverUrl);
|
||||||
expect(await storage.getTokens(serverUrl)).toBeUndefined();
|
expect(await storage.getTokens(serverUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("codeVerifier round-trip and clearCodeVerifier", async () => {
|
it("codeVerifier round-trip and clearCodeVerifier", async () => {
|
||||||
await storage.saveCodeVerifier(serverUrl, "verifier");
|
await storage.saveCodeVerifier(serverUrl, "verifier");
|
||||||
expect(storage.getCodeVerifier(serverUrl)).toBe("verifier");
|
expect(storage.getCodeVerifier(serverUrl)).toBe("verifier");
|
||||||
storage.clearCodeVerifier(serverUrl);
|
await storage.clearCodeVerifier(serverUrl);
|
||||||
expect(storage.getCodeVerifier(serverUrl)).toBeUndefined();
|
expect(storage.getCodeVerifier(serverUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("scope round-trip and clearScope", async () => {
|
it("scope round-trip and clearScope", async () => {
|
||||||
await storage.saveScope(serverUrl, "read write");
|
await storage.saveScope(serverUrl, "read write");
|
||||||
expect(storage.getScope(serverUrl)).toBe("read write");
|
expect(storage.getScope(serverUrl)).toBe("read write");
|
||||||
storage.clearScope(serverUrl);
|
await storage.clearScope(serverUrl);
|
||||||
expect(storage.getScope(serverUrl)).toBeUndefined();
|
expect(storage.getScope(serverUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
|
||||||
};
|
};
|
||||||
await storage.saveServerMetadata(serverUrl, md);
|
await storage.saveServerMetadata(serverUrl, md);
|
||||||
expect(storage.getServerMetadata(serverUrl)).toEqual(md);
|
expect(storage.getServerMetadata(serverUrl)).toEqual(md);
|
||||||
storage.clearServerMetadata(serverUrl);
|
await storage.clearServerMetadata(serverUrl);
|
||||||
expect(storage.getServerMetadata(serverUrl)).toBeNull();
|
expect(storage.getServerMetadata(serverUrl)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
|
||||||
access_token: "t",
|
access_token: "t",
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
});
|
});
|
||||||
storage.clear(serverUrl);
|
await storage.clear(serverUrl);
|
||||||
expect(await storage.getClientInformation(serverUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(serverUrl)).toBeUndefined();
|
||||||
expect(await storage.getTokens(serverUrl)).toBeUndefined();
|
expect(await storage.getTokens(serverUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
@ -120,4 +120,39 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
|
||||||
// No public accessor; constructing without throwing covers the default-branch.
|
// No public accessor; constructing without throwing covers the default-branch.
|
||||||
expect(s).toBeInstanceOf(RemoteOAuthStorage);
|
expect(s).toBeInstanceOf(RemoteOAuthStorage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("load() waits for remote GET before sync reads see persisted state", async () => {
|
||||||
|
const persisted = {
|
||||||
|
state: {
|
||||||
|
servers: {
|
||||||
|
[serverUrl]: { codeVerifier: "persisted-verifier" },
|
||||||
|
},
|
||||||
|
idpSessions: {},
|
||||||
|
},
|
||||||
|
version: 0,
|
||||||
|
};
|
||||||
|
let resolveFetch!: (value: Response) => void;
|
||||||
|
const delayedFetch = vi.fn(
|
||||||
|
() =>
|
||||||
|
new Promise<Response>((resolve) => {
|
||||||
|
resolveFetch = resolve;
|
||||||
|
}),
|
||||||
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
|
const delayedStorage = new RemoteOAuthStorage({
|
||||||
|
baseUrl: "http://remote.example",
|
||||||
|
storeId: `delayed-${Math.random().toString(36).slice(2)}`,
|
||||||
|
fetchFn: delayedFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadPromise = delayedStorage.load();
|
||||||
|
expect(delayedStorage.getCodeVerifier(serverUrl)).toBeUndefined();
|
||||||
|
|
||||||
|
resolveFetch(new Response(JSON.stringify(persisted), { status: 200 }));
|
||||||
|
await loadPromise;
|
||||||
|
|
||||||
|
expect(delayedStorage.getCodeVerifier(serverUrl)).toBe(
|
||||||
|
"persisted-verifier",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ function createMockParams(
|
||||||
const dispatchOAuthError = vi.fn();
|
const dispatchOAuthError = vi.fn();
|
||||||
|
|
||||||
const storage = {
|
const storage = {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getScope: vi.fn().mockReturnValue(undefined),
|
getScope: vi.fn().mockReturnValue(undefined),
|
||||||
getClientInformation: vi.fn().mockResolvedValue(undefined),
|
getClientInformation: vi.fn().mockResolvedValue(undefined),
|
||||||
getClientRegistrationKind: vi.fn().mockReturnValue(undefined),
|
getClientRegistrationKind: vi.fn().mockReturnValue(undefined),
|
||||||
|
|
@ -45,7 +46,7 @@ function createMockParams(
|
||||||
saveTokens: vi.fn().mockResolvedValue(undefined),
|
saveTokens: vi.fn().mockResolvedValue(undefined),
|
||||||
getCodeVerifier: vi.fn().mockReturnValue("verifier"),
|
getCodeVerifier: vi.fn().mockReturnValue("verifier"),
|
||||||
saveCodeVerifier: vi.fn().mockResolvedValue(undefined),
|
saveCodeVerifier: vi.fn().mockResolvedValue(undefined),
|
||||||
clear: vi.fn(),
|
clear: vi.fn().mockResolvedValue(undefined),
|
||||||
clearClientInformation: vi.fn(),
|
clearClientInformation: vi.fn(),
|
||||||
clearTokens: vi.fn(),
|
clearTokens: vi.fn(),
|
||||||
clearCodeVerifier: vi.fn(),
|
clearCodeVerifier: vi.fn(),
|
||||||
|
|
@ -126,10 +127,10 @@ describe("OAuthManager", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("clearOAuthTokens", () => {
|
describe("clearOAuthTokens", () => {
|
||||||
it("calls storage.clear(serverUrl) when storage is configured", () => {
|
it("calls storage.clear(serverUrl) when storage is configured", async () => {
|
||||||
const params = createMockParams();
|
const params = createMockParams();
|
||||||
const manager = new OAuthManager(params);
|
const manager = new OAuthManager(params);
|
||||||
manager.clearOAuthTokens();
|
await manager.clearOAuthTokens();
|
||||||
expect(params.initialConfig.storage!.clear).toHaveBeenCalledWith(
|
expect(params.initialConfig.storage!.clear).toHaveBeenCalledWith(
|
||||||
SERVER_URL,
|
SERVER_URL,
|
||||||
);
|
);
|
||||||
|
|
@ -137,7 +138,7 @@ describe("OAuthManager", () => {
|
||||||
expect(manager.getOAuthFlowStep()).toBeUndefined();
|
expect(manager.getOAuthFlowStep()).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no-ops when storage is not configured", () => {
|
it("no-ops when storage is not configured", async () => {
|
||||||
const params = createMockParams({
|
const params = createMockParams({
|
||||||
initialConfig: {
|
initialConfig: {
|
||||||
redirectUrlProvider: {
|
redirectUrlProvider: {
|
||||||
|
|
@ -147,7 +148,7 @@ describe("OAuthManager", () => {
|
||||||
} as OAuthManagerConfig,
|
} as OAuthManagerConfig,
|
||||||
});
|
});
|
||||||
const manager = new OAuthManager(params);
|
const manager = new OAuthManager(params);
|
||||||
manager.clearOAuthTokens();
|
await manager.clearOAuthTokens();
|
||||||
expect(params.getServerUrl).not.toHaveBeenCalled();
|
expect(params.getServerUrl).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,13 @@ describe("useEmaIdpLoginState", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
storage = {
|
storage = {
|
||||||
|
load: vi.fn().mockResolvedValue(undefined),
|
||||||
getIdpSession: vi.fn().mockResolvedValue(undefined),
|
getIdpSession: vi.fn().mockResolvedValue(undefined),
|
||||||
clearIdpSession: vi.fn(),
|
clearIdpSession: vi.fn().mockResolvedValue(undefined),
|
||||||
clear: vi.fn(),
|
clear: vi.fn().mockResolvedValue(undefined),
|
||||||
clearEnterpriseManagedResourceServers: vi.fn(),
|
clearEnterpriseManagedResourceServers: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(undefined),
|
||||||
} as unknown as OAuthStorage;
|
} as unknown as OAuthStorage;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,10 +59,12 @@ describe("useEmaIdpLoginState", () => {
|
||||||
result.current.logout();
|
result.current.logout();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(storage.clearIdpSession).toHaveBeenCalledWith("https://idp.test");
|
await waitFor(() => {
|
||||||
expect(storage.clear).toHaveBeenCalledWith("ema-idp:https://idp.test");
|
expect(storage.clearIdpSession).toHaveBeenCalledWith("https://idp.test");
|
||||||
expect(storage.clearEnterpriseManagedResourceServers).toHaveBeenCalled();
|
expect(storage.clear).toHaveBeenCalledWith("ema-idp:https://idp.test");
|
||||||
expect(result.current.loginState).toBe("none");
|
expect(storage.clearEnterpriseManagedResourceServers).toHaveBeenCalled();
|
||||||
|
expect(result.current.loginState).toBe("none");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports 'expired' for an expired token with no refresh token", async () => {
|
it("reports 'expired' for an expired token with no refresh token", async () => {
|
||||||
|
|
|
||||||
|
|
@ -126,10 +126,10 @@ describe("NodeOAuthStorage", () => {
|
||||||
client_id: "second-id",
|
client_id: "second-id",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(testServerUrl, firstInfo, {
|
await storage.saveClientInformation(testServerUrl, firstInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
storage.saveClientInformation(testServerUrl, secondInfo, {
|
await storage.saveClientInformation(testServerUrl, secondInfo, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
const result = await storage.getClientInformation(testServerUrl);
|
const result = await storage.getClientInformation(testServerUrl);
|
||||||
|
|
@ -310,7 +310,7 @@ describe("NodeOAuthStorage", () => {
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toEqual({
|
expect(await storage.getClientInformation(testServerUrl)).toEqual({
|
||||||
client_id: "dyn",
|
client_id: "dyn",
|
||||||
});
|
});
|
||||||
storage.clearClientInformation(testServerUrl);
|
await storage.clearClientInformation(testServerUrl);
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -321,7 +321,7 @@ describe("NodeOAuthStorage", () => {
|
||||||
expect(await storage.getClientInformation(testServerUrl, true)).toEqual({
|
expect(await storage.getClientInformation(testServerUrl, true)).toEqual({
|
||||||
client_id: "pre",
|
client_id: "pre",
|
||||||
});
|
});
|
||||||
storage.clearClientInformation(testServerUrl, true);
|
await storage.clearClientInformation(testServerUrl, true);
|
||||||
expect(
|
expect(
|
||||||
await storage.getClientInformation(testServerUrl, true),
|
await storage.getClientInformation(testServerUrl, true),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
|
|
@ -335,21 +335,21 @@ describe("NodeOAuthStorage", () => {
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
});
|
});
|
||||||
expect(await storage.getTokens(testServerUrl)).toBeDefined();
|
expect(await storage.getTokens(testServerUrl)).toBeDefined();
|
||||||
storage.clearTokens(testServerUrl);
|
await storage.clearTokens(testServerUrl);
|
||||||
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearCodeVerifier removes only the PKCE verifier", async () => {
|
it("clearCodeVerifier removes only the PKCE verifier", async () => {
|
||||||
await storage.saveCodeVerifier(testServerUrl, "verifier");
|
await storage.saveCodeVerifier(testServerUrl, "verifier");
|
||||||
expect(storage.getCodeVerifier(testServerUrl)).toBe("verifier");
|
expect(storage.getCodeVerifier(testServerUrl)).toBe("verifier");
|
||||||
storage.clearCodeVerifier(testServerUrl);
|
await storage.clearCodeVerifier(testServerUrl);
|
||||||
expect(storage.getCodeVerifier(testServerUrl)).toBeUndefined();
|
expect(storage.getCodeVerifier(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearScope removes only the scope", async () => {
|
it("clearScope removes only the scope", async () => {
|
||||||
await storage.saveScope(testServerUrl, "read write");
|
await storage.saveScope(testServerUrl, "read write");
|
||||||
expect(storage.getScope(testServerUrl)).toBe("read write");
|
expect(storage.getScope(testServerUrl)).toBe("read write");
|
||||||
storage.clearScope(testServerUrl);
|
await storage.clearScope(testServerUrl);
|
||||||
expect(storage.getScope(testServerUrl)).toBeUndefined();
|
expect(storage.getScope(testServerUrl)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -362,7 +362,7 @@ describe("NodeOAuthStorage", () => {
|
||||||
};
|
};
|
||||||
await storage.saveServerMetadata(testServerUrl, metadata);
|
await storage.saveServerMetadata(testServerUrl, metadata);
|
||||||
expect(storage.getServerMetadata(testServerUrl)).toEqual(metadata);
|
expect(storage.getServerMetadata(testServerUrl)).toEqual(metadata);
|
||||||
storage.clearServerMetadata(testServerUrl);
|
await storage.clearServerMetadata(testServerUrl);
|
||||||
expect(storage.getServerMetadata(testServerUrl)).toBeNull();
|
expect(storage.getServerMetadata(testServerUrl)).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -382,7 +382,7 @@ describe("NodeOAuthStorage", () => {
|
||||||
});
|
});
|
||||||
await storage.saveTokens(testServerUrl, tokens);
|
await storage.saveTokens(testServerUrl, tokens);
|
||||||
|
|
||||||
storage.clear(testServerUrl);
|
await storage.clear(testServerUrl);
|
||||||
|
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
||||||
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
expect(await storage.getTokens(testServerUrl)).toBeUndefined();
|
||||||
|
|
@ -401,7 +401,7 @@ describe("NodeOAuthStorage", () => {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
|
|
||||||
storage.clear(testServerUrl);
|
await storage.clear(testServerUrl);
|
||||||
|
|
||||||
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
expect(await storage.getClientInformation(testServerUrl)).toBeUndefined();
|
||||||
const otherResult = await storage.getClientInformation(otherServerUrl);
|
const otherResult = await storage.getClientInformation(otherServerUrl);
|
||||||
|
|
@ -424,10 +424,10 @@ describe("NodeOAuthStorage", () => {
|
||||||
client_id: "client-2",
|
client_id: "client-2",
|
||||||
};
|
};
|
||||||
|
|
||||||
storage.saveClientInformation(server1Url, clientInfo1, {
|
await storage.saveClientInformation(server1Url, clientInfo1, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
storage.saveClientInformation(server2Url, clientInfo2, {
|
await storage.saveClientInformation(server2Url, clientInfo2, {
|
||||||
registrationKind: "dcr",
|
registrationKind: "dcr",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -547,7 +547,7 @@ describe("NodeOAuthStorage idpSessions (EMA)", () => {
|
||||||
expect(session?.idToken).toBe("eyJ.id.token");
|
expect(session?.idToken).toBe("eyJ.id.token");
|
||||||
expect(session?.refreshToken).toBe("rt-1");
|
expect(session?.refreshToken).toBe("rt-1");
|
||||||
|
|
||||||
storage.clearIdpSession(issuer);
|
await storage.clearIdpSession(issuer);
|
||||||
await flushStoreFileWrites(testStatePath);
|
await flushStoreFileWrites(testStatePath);
|
||||||
expect(await storage.getIdpSession(issuer)).toBeUndefined();
|
expect(await storage.getIdpSession(issuer)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
@ -635,12 +635,10 @@ describe("NodeOAuthStorage with custom storagePath", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const defaultStore = getOAuthStore();
|
const defaultStorage = new NodeOAuthStorage();
|
||||||
defaultStore.getState().setServerState(testServerUrl, {
|
await defaultStorage.saveTokens(testServerUrl, {
|
||||||
tokens: {
|
access_token: "default-token",
|
||||||
access_token: "default-token",
|
token_type: "Bearer",
|
||||||
token_type: "Bearer",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const customStorage = new NodeOAuthStorage(customPath);
|
const customStorage = new NodeOAuthStorage(customPath);
|
||||||
|
|
@ -652,11 +650,10 @@ describe("NodeOAuthStorage with custom storagePath", () => {
|
||||||
const fromCustom = await customStorage.getTokens(testServerUrl);
|
const fromCustom = await customStorage.getTokens(testServerUrl);
|
||||||
expect(fromCustom?.access_token).toBe("custom-token");
|
expect(fromCustom?.access_token).toBe("custom-token");
|
||||||
|
|
||||||
const defaultStorage = new NodeOAuthStorage();
|
|
||||||
const fromDefault = await defaultStorage.getTokens(testServerUrl);
|
const fromDefault = await defaultStorage.getTokens(testServerUrl);
|
||||||
expect(fromDefault?.access_token).toBe("default-token");
|
expect(fromDefault?.access_token).toBe("default-token");
|
||||||
|
|
||||||
defaultStore.getState().clearServerState(testServerUrl);
|
getOAuthStore().getState().clearServerState(testServerUrl);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await fs.unlink(customPath);
|
await fs.unlink(customPath);
|
||||||
|
|
|
||||||
|
|
@ -630,7 +630,7 @@ describe("InspectorClient OAuth E2E", () => {
|
||||||
expect(client.getStatus()).toBe("connected");
|
expect(client.getStatus()).toBe("connected");
|
||||||
|
|
||||||
await client.disconnect();
|
await client.disconnect();
|
||||||
client.clearOAuthTokens();
|
await client.clearOAuthTokens();
|
||||||
|
|
||||||
const authUrlSecond = await client.authenticate();
|
const authUrlSecond = await client.authenticate();
|
||||||
if (!authUrlSecond) throw new Error("Expected authorization URL");
|
if (!authUrlSecond) throw new Error("Expected authorization URL");
|
||||||
|
|
@ -995,7 +995,7 @@ describe("InspectorClient OAuth E2E", () => {
|
||||||
expect(tokens?.access_token).toBeDefined();
|
expect(tokens?.access_token).toBeDefined();
|
||||||
expect(await client.isOAuthAuthorized()).toBe(true);
|
expect(await client.isOAuthAuthorized()).toBe(true);
|
||||||
|
|
||||||
client.clearOAuthTokens();
|
await client.clearOAuthTokens();
|
||||||
expect(await client.isOAuthAuthorized()).toBe(false);
|
expect(await client.isOAuthAuthorized()).toBe(false);
|
||||||
expect(await client.getOAuthTokens()).toBeUndefined();
|
expect(await client.getOAuthTokens()).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -173,8 +173,8 @@ describe("InspectorClient OAuth", () => {
|
||||||
expect(tokens).toBeUndefined();
|
expect(tokens).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should clear OAuth tokens", () => {
|
it("should clear OAuth tokens", async () => {
|
||||||
client.clearOAuthTokens();
|
await client.clearOAuthTokens();
|
||||||
// Should not throw
|
// Should not throw
|
||||||
expect(client).toBeDefined();
|
expect(client).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4949,7 +4949,7 @@ describe("InspectorClient", () => {
|
||||||
expect(c.getOAuthFlowState()).toBeUndefined();
|
expect(c.getOAuthFlowState()).toBeUndefined();
|
||||||
await expect(c.getOAuthState()).resolves.toBeUndefined();
|
await expect(c.getOAuthState()).resolves.toBeUndefined();
|
||||||
// clearOAuthTokens is a no-op when there is no manager
|
// clearOAuthTokens is a no-op when there is no manager
|
||||||
expect(() => c.clearOAuthTokens()).not.toThrow();
|
await expect(c.clearOAuthTokens()).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("setOAuthConfig throws when oauthManager is unset", () => {
|
it("setOAuthConfig throws when oauthManager is unset", () => {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||||
import { BrowserOAuthStorage } from "@inspector/core/auth/browser/storage.js";
|
import { BrowserOAuthStorage } from "@inspector/core/auth/browser/storage.js";
|
||||||
|
import type { InspectorClient } from "@inspector/core/mcp/inspectorClient.js";
|
||||||
import { clearServerOAuthState } from "./clearServerOAuthState";
|
import { clearServerOAuthState } from "./clearServerOAuthState";
|
||||||
|
|
||||||
describe("clearServerOAuthState", () => {
|
describe("clearServerOAuthState", () => {
|
||||||
let storage: BrowserOAuthStorage;
|
let storage: BrowserOAuthStorage;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
storage = new BrowserOAuthStorage();
|
storage = new BrowserOAuthStorage();
|
||||||
storage.clear("https://mcp.example.com/mcp");
|
await storage.clear("https://mcp.example.com/mcp");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clears storage by server URL when not the active connection", async () => {
|
it("clears storage by server URL when not the active connection", async () => {
|
||||||
|
|
@ -16,7 +17,7 @@ describe("clearServerOAuthState", () => {
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
});
|
});
|
||||||
|
|
||||||
const cleared = clearServerOAuthState({
|
const cleared = await clearServerOAuthState({
|
||||||
config: { type: "streamable-http", url: "https://mcp.example.com/mcp" },
|
config: { type: "streamable-http", url: "https://mcp.example.com/mcp" },
|
||||||
isActiveConnection: false,
|
isActiveConnection: false,
|
||||||
oauthStorage: storage,
|
oauthStorage: storage,
|
||||||
|
|
@ -28,11 +29,11 @@ describe("clearServerOAuthState", () => {
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses the live client when clearing the active connection", () => {
|
it("uses the live client when clearing the active connection", async () => {
|
||||||
const clearOAuthTokens = vi.fn<() => void>();
|
const clearOAuthTokens = vi.fn<InspectorClient["clearOAuthTokens"]>();
|
||||||
const inspectorClient = { clearOAuthTokens };
|
const inspectorClient = { clearOAuthTokens };
|
||||||
|
|
||||||
const cleared = clearServerOAuthState({
|
const cleared = await clearServerOAuthState({
|
||||||
config: { type: "streamable-http", url: "https://mcp.example.com/mcp" },
|
config: { type: "streamable-http", url: "https://mcp.example.com/mcp" },
|
||||||
inspectorClient,
|
inspectorClient,
|
||||||
isActiveConnection: true,
|
isActiveConnection: true,
|
||||||
|
|
@ -43,13 +44,13 @@ describe("clearServerOAuthState", () => {
|
||||||
expect(clearOAuthTokens).toHaveBeenCalledTimes(1);
|
expect(clearOAuthTokens).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false for stdio servers", () => {
|
it("returns false for stdio servers", async () => {
|
||||||
expect(
|
await expect(
|
||||||
clearServerOAuthState({
|
clearServerOAuthState({
|
||||||
config: { type: "stdio", command: "node", args: [] },
|
config: { type: "stdio", command: "node", args: [] },
|
||||||
isActiveConnection: false,
|
isActiveConnection: false,
|
||||||
oauthStorage: storage,
|
oauthStorage: storage,
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).resolves.toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,18 @@ export interface ClearServerOAuthStateParams {
|
||||||
* HTTP MCP server. When clearing the active connection, uses the live client so
|
* HTTP MCP server. When clearing the active connection, uses the live client so
|
||||||
* in-memory flow state is reset too.
|
* in-memory flow state is reset too.
|
||||||
*/
|
*/
|
||||||
export function clearServerOAuthState(
|
export async function clearServerOAuthState(
|
||||||
params: ClearServerOAuthStateParams,
|
params: ClearServerOAuthStateParams,
|
||||||
): boolean {
|
): Promise<boolean> {
|
||||||
const serverUrl = getOAuthServerUrl(params.config);
|
const serverUrl = getOAuthServerUrl(params.config);
|
||||||
if (!serverUrl) {
|
if (!serverUrl) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.isActiveConnection && params.inspectorClient) {
|
if (params.isActiveConnection && params.inspectorClient) {
|
||||||
params.inspectorClient.clearOAuthTokens();
|
await params.inspectorClient.clearOAuthTokens();
|
||||||
} else {
|
} else {
|
||||||
params.oauthStorage.clear(serverUrl);
|
await params.oauthStorage.clear(serverUrl);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ async function resolveIdpMetadata(
|
||||||
storage: OAuthStorage,
|
storage: OAuthStorage,
|
||||||
fetchFn?: typeof fetch,
|
fetchFn?: typeof fetch,
|
||||||
): Promise<OAuthMetadata> {
|
): Promise<OAuthMetadata> {
|
||||||
|
await storage.load();
|
||||||
const storageKey = idpOAuthStorageKey(issuer);
|
const storageKey = idpOAuthStorageKey(issuer);
|
||||||
const cached = storage.getServerMetadata(storageKey);
|
const cached = storage.getServerMetadata(storageKey);
|
||||||
if (cached?.token_endpoint) {
|
if (cached?.token_endpoint) {
|
||||||
|
|
@ -63,7 +64,7 @@ export async function startIdpOidcAuthorization(params: {
|
||||||
fetchFn?: typeof fetch;
|
fetchFn?: typeof fetch;
|
||||||
}): Promise<{ authorizationUrl: URL }> {
|
}): Promise<{ authorizationUrl: URL }> {
|
||||||
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
||||||
const metadata = await discoverIdpMetadata(issuer, params.fetchFn);
|
const metadata = await resolveIdpMetadata(issuer, params.storage, params.fetchFn);
|
||||||
const clientInformation = idpClientInformation(params.idp);
|
const clientInformation = idpClientInformation(params.idp);
|
||||||
const storageKey = idpOAuthStorageKey(issuer);
|
const storageKey = idpOAuthStorageKey(issuer);
|
||||||
const state = generateOAuthState();
|
const state = generateOAuthState();
|
||||||
|
|
@ -101,6 +102,7 @@ export async function completeIdpOidcAuthorization(params: {
|
||||||
}> {
|
}> {
|
||||||
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
||||||
const storageKey = idpOAuthStorageKey(issuer);
|
const storageKey = idpOAuthStorageKey(issuer);
|
||||||
|
await params.storage.load();
|
||||||
const metadata = params.storage.getServerMetadata(storageKey);
|
const metadata = params.storage.getServerMetadata(storageKey);
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
throw new Error("IdP OAuth metadata not found — restart EMA IdP login");
|
throw new Error("IdP OAuth metadata not found — restart EMA IdP login");
|
||||||
|
|
@ -131,7 +133,7 @@ export async function completeIdpOidcAuthorization(params: {
|
||||||
throw new Error("IdP token response did not include an ID Token");
|
throw new Error("IdP token response did not include an ID Token");
|
||||||
}
|
}
|
||||||
|
|
||||||
params.storage.clearCodeVerifier(storageKey);
|
await params.storage.clearCodeVerifier(storageKey);
|
||||||
const idTokenExpiresAt = jwtExpiresAtMs(idToken);
|
const idTokenExpiresAt = jwtExpiresAtMs(idToken);
|
||||||
await params.storage.saveIdpSession(issuer, {
|
await params.storage.saveIdpSession(issuer, {
|
||||||
idToken,
|
idToken,
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,13 @@ export async function getEmaIdpLoginState(
|
||||||
return "expired";
|
return "expired";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearEmaIdpSession(storage: OAuthStorage, issuer: string): void {
|
export async function clearEmaIdpSession(
|
||||||
|
storage: OAuthStorage,
|
||||||
|
issuer: string,
|
||||||
|
): Promise<void> {
|
||||||
const normalized = normalizeIdpIssuer(issuer);
|
const normalized = normalizeIdpIssuer(issuer);
|
||||||
if (!normalized) return;
|
if (!normalized) return;
|
||||||
storage.clearIdpSession(normalized);
|
await storage.clearIdpSession(normalized);
|
||||||
storage.clear(idpOAuthStorageKey(normalized));
|
await storage.clear(idpOAuthStorageKey(normalized));
|
||||||
storage.clearEnterpriseManagedResourceServers();
|
await storage.clearEnterpriseManagedResourceServers();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,48 @@ import type {
|
||||||
SaveTokensOptions,
|
SaveTokensOptions,
|
||||||
} from "./storage.js";
|
} from "./storage.js";
|
||||||
|
|
||||||
|
type OAuthStoreWithPersist = ReturnType<typeof createOAuthStore> & {
|
||||||
|
persist: {
|
||||||
|
hasHydrated: () => boolean;
|
||||||
|
onFinishHydration: (fn: () => void) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concrete OAuthStorage implementation parameterized on a Zustand store.
|
* Concrete OAuthStorage implementation parameterized on a Zustand store.
|
||||||
* The store carries the storage adapter (sessionStorage, file, remote HTTP, …),
|
* The store carries the storage adapter (sessionStorage, file, remote HTTP, …),
|
||||||
* so the same body works for browser, Node, and remote environments.
|
* so the same body works for browser, Node, and remote environments.
|
||||||
*/
|
*/
|
||||||
export class OAuthStorageBase implements OAuthStorage {
|
export class OAuthStorageBase implements OAuthStorage {
|
||||||
private readonly store: ReturnType<typeof createOAuthStore>;
|
private readonly store: OAuthStoreWithPersist;
|
||||||
|
private loadedPromise: Promise<void> | undefined;
|
||||||
|
|
||||||
constructor(store: ReturnType<typeof createOAuthStore>) {
|
constructor(store: ReturnType<typeof createOAuthStore>) {
|
||||||
this.store = store;
|
this.store = store as OAuthStoreWithPersist;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): Promise<void> {
|
||||||
|
if (!this.loadedPromise) {
|
||||||
|
this.loadedPromise = new Promise((resolve) => {
|
||||||
|
if (this.store.persist.hasHydrated()) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.store.persist.onFinishHydration(() => resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.loadedPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureLoaded(): Promise<void> {
|
||||||
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getClientInformation(
|
async getClientInformation(
|
||||||
serverUrl: string,
|
serverUrl: string,
|
||||||
isPreregistered?: boolean,
|
isPreregistered?: boolean,
|
||||||
): Promise<OAuthClientInformation | undefined> {
|
): Promise<OAuthClientInformation | undefined> {
|
||||||
|
await this.ensureLoaded();
|
||||||
const state = this.store.getState().getServerState(serverUrl);
|
const state = this.store.getState().getServerState(serverUrl);
|
||||||
const clientInfo = isPreregistered
|
const clientInfo = isPreregistered
|
||||||
? state.preregisteredClientInformation
|
? state.preregisteredClientInformation
|
||||||
|
|
@ -56,6 +82,7 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
clientInformation: OAuthClientInformation,
|
clientInformation: OAuthClientInformation,
|
||||||
options: SaveClientInformationOptions,
|
options: SaveClientInformationOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, {
|
this.store.getState().setServerState(serverUrl, {
|
||||||
clientInformation,
|
clientInformation,
|
||||||
clientRegistrationKind: options.registrationKind,
|
clientRegistrationKind: options.registrationKind,
|
||||||
|
|
@ -66,13 +93,18 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
serverUrl: string,
|
serverUrl: string,
|
||||||
clientInformation: OAuthClientInformation,
|
clientInformation: OAuthClientInformation,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, {
|
this.store.getState().setServerState(serverUrl, {
|
||||||
preregisteredClientInformation: clientInformation,
|
preregisteredClientInformation: clientInformation,
|
||||||
clientRegistrationKind: "static",
|
clientRegistrationKind: "static",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clearClientInformation(serverUrl: string, isPreregistered?: boolean): void {
|
async clearClientInformation(
|
||||||
|
serverUrl: string,
|
||||||
|
isPreregistered?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
const updates: Partial<ServerOAuthState> = {};
|
const updates: Partial<ServerOAuthState> = {};
|
||||||
|
|
||||||
if (isPreregistered) {
|
if (isPreregistered) {
|
||||||
|
|
@ -86,6 +118,7 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTokens(serverUrl: string): Promise<OAuthTokens | undefined> {
|
async getTokens(serverUrl: string): Promise<OAuthTokens | undefined> {
|
||||||
|
await this.ensureLoaded();
|
||||||
const state = this.store.getState().getServerState(serverUrl);
|
const state = this.store.getState().getServerState(serverUrl);
|
||||||
if (!state.tokens) {
|
if (!state.tokens) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
@ -99,13 +132,15 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
tokens: OAuthTokens,
|
tokens: OAuthTokens,
|
||||||
options?: SaveTokensOptions,
|
options?: SaveTokensOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, {
|
this.store.getState().setServerState(serverUrl, {
|
||||||
tokens,
|
tokens,
|
||||||
...(options?.enterpriseManaged === true && { enterpriseManaged: true }),
|
...(options?.enterpriseManaged === true && { enterpriseManaged: true }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTokens(serverUrl: string): void {
|
async clearTokens(serverUrl: string): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, { tokens: undefined });
|
this.store.getState().setServerState(serverUrl, { tokens: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,10 +153,12 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
serverUrl: string,
|
serverUrl: string,
|
||||||
codeVerifier: string,
|
codeVerifier: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, { codeVerifier });
|
this.store.getState().setServerState(serverUrl, { codeVerifier });
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCodeVerifier(serverUrl: string): void {
|
async clearCodeVerifier(serverUrl: string): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store
|
this.store
|
||||||
.getState()
|
.getState()
|
||||||
.setServerState(serverUrl, { codeVerifier: undefined });
|
.setServerState(serverUrl, { codeVerifier: undefined });
|
||||||
|
|
@ -133,10 +170,12 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveScope(serverUrl: string, scope: string | undefined): Promise<void> {
|
async saveScope(serverUrl: string, scope: string | undefined): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, { scope });
|
this.store.getState().setServerState(serverUrl, { scope });
|
||||||
}
|
}
|
||||||
|
|
||||||
clearScope(serverUrl: string): void {
|
async clearScope(serverUrl: string): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setServerState(serverUrl, { scope: undefined });
|
this.store.getState().setServerState(serverUrl, { scope: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,22 +188,26 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
serverUrl: string,
|
serverUrl: string,
|
||||||
metadata: OAuthMetadata,
|
metadata: OAuthMetadata,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store
|
this.store
|
||||||
.getState()
|
.getState()
|
||||||
.setServerState(serverUrl, { serverMetadata: metadata });
|
.setServerState(serverUrl, { serverMetadata: metadata });
|
||||||
}
|
}
|
||||||
|
|
||||||
clearServerMetadata(serverUrl: string): void {
|
async clearServerMetadata(serverUrl: string): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store
|
this.store
|
||||||
.getState()
|
.getState()
|
||||||
.setServerState(serverUrl, { serverMetadata: undefined });
|
.setServerState(serverUrl, { serverMetadata: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(serverUrl: string): void {
|
async clear(serverUrl: string): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().clearServerState(serverUrl);
|
this.store.getState().clearServerState(serverUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIdpSession(issuer: string): Promise<IdpSessionState | undefined> {
|
async getIdpSession(issuer: string): Promise<IdpSessionState | undefined> {
|
||||||
|
await this.ensureLoaded();
|
||||||
const session = this.store.getState().getIdpSession(issuer);
|
const session = this.store.getState().getIdpSession(issuer);
|
||||||
if (
|
if (
|
||||||
!session.idToken &&
|
!session.idToken &&
|
||||||
|
|
@ -180,14 +223,17 @@ export class OAuthStorageBase implements OAuthStorage {
|
||||||
issuer: string,
|
issuer: string,
|
||||||
session: Partial<IdpSessionState>,
|
session: Partial<IdpSessionState>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().setIdpSession(issuer, session);
|
this.store.getState().setIdpSession(issuer, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearIdpSession(issuer: string): void {
|
async clearIdpSession(issuer: string): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().clearIdpSession(issuer);
|
this.store.getState().clearIdpSession(issuer);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearEnterpriseManagedResourceServers(): void {
|
async clearEnterpriseManagedResourceServers(): Promise<void> {
|
||||||
|
await this.ensureLoaded();
|
||||||
this.store.getState().clearEnterpriseManagedResourceServers();
|
this.store.getState().clearEnterpriseManagedResourceServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue