mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
feat(auth): [PM-8978] migrate SSO connector to Tailwind
- Convert Bootstrap styles to Tailwind - Remove deprecated sso.scss - Add test coverage for SSO connector [PM-8978]
This commit is contained in:
@@ -15,19 +15,17 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="layout_frontend">
|
<body class="layout_frontend">
|
||||||
<div class="mt-5 d-flex justify-content-center">
|
<div class="tw-p-8 tw-flex tw-flex-col tw-items-center">
|
||||||
<div>
|
<img class="new-logo-themed tw-mb-4" alt="Bitwarden" />
|
||||||
<img src="../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" />
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<p class="text-center">
|
<p class="tw-text-center">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
class="bwi bwi-spinner bwi-spin bwi-2x tw-text-muted"
|
||||||
title="Loading"
|
title="Loading"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
@import "../scss/styles.scss";
|
|
||||||
119
apps/web/src/connectors/sso.spec.ts
Normal file
119
apps/web/src/connectors/sso.spec.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { initiateWebAppSso, initiateBrowserSso } from "./sso";
|
||||||
|
|
||||||
|
describe("sso", () => {
|
||||||
|
let originalLocation: Location;
|
||||||
|
let originalPostMessage: any;
|
||||||
|
let postMessageSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Save original window methods
|
||||||
|
originalLocation = window.location;
|
||||||
|
originalPostMessage = window.postMessage;
|
||||||
|
|
||||||
|
// Mock location
|
||||||
|
Object.defineProperty(window, "location", {
|
||||||
|
value: {
|
||||||
|
href: "",
|
||||||
|
origin: "https://test.bitwarden.com",
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock postMessage
|
||||||
|
postMessageSpy = jest.spyOn(window, "postMessage");
|
||||||
|
|
||||||
|
// Set up document
|
||||||
|
document.cookie = "ssoHandOffMessage=SSO login successful;SameSite=strict";
|
||||||
|
const contentElement = document.createElement("div");
|
||||||
|
contentElement.id = "content";
|
||||||
|
document.body.appendChild(contentElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original window methods
|
||||||
|
Object.defineProperty(window, "location", { value: originalLocation });
|
||||||
|
window.postMessage = originalPostMessage;
|
||||||
|
|
||||||
|
// Clean up document
|
||||||
|
const contentElement = document.getElementById("content");
|
||||||
|
if (contentElement) {
|
||||||
|
document.body.removeChild(contentElement);
|
||||||
|
}
|
||||||
|
document.cookie = "ssoHandOffMessage=;SameSite=strict;max-age=0";
|
||||||
|
|
||||||
|
// Clear mocks
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initiateWebAppSso", () => {
|
||||||
|
it("redirects to the SSO component with code and state", () => {
|
||||||
|
const code = "testcode";
|
||||||
|
const state = "teststate";
|
||||||
|
|
||||||
|
initiateWebAppSso(code, state);
|
||||||
|
|
||||||
|
expect(window.location.href).toBe(
|
||||||
|
"https://test.bitwarden.com/#/sso?code=testcode&state=teststate",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("redirects to the return URI when included in state", () => {
|
||||||
|
const code = "testcode";
|
||||||
|
const state = "teststate_returnUri='/organizations'";
|
||||||
|
|
||||||
|
initiateWebAppSso(code, state);
|
||||||
|
|
||||||
|
expect(window.location.href).toBe("https://test.bitwarden.com/#/organizations");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty code parameter", () => {
|
||||||
|
initiateWebAppSso("", "teststate");
|
||||||
|
expect(window.location.href).toBe("https://test.bitwarden.com/#/sso?code=&state=teststate");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty state parameter", () => {
|
||||||
|
initiateWebAppSso("testcode", "");
|
||||||
|
expect(window.location.href).toBe("https://test.bitwarden.com/#/sso?code=testcode&state=");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initiateBrowserSso", () => {
|
||||||
|
it("posts message with code and state", () => {
|
||||||
|
const code = "testcode";
|
||||||
|
const state = "teststate";
|
||||||
|
const lastpass = false;
|
||||||
|
|
||||||
|
initiateBrowserSso(code, state, lastpass);
|
||||||
|
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
{ command: "authResult", code, state, lastpass },
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates content with message from cookie", () => {
|
||||||
|
const code = "testcode";
|
||||||
|
const state = "teststate";
|
||||||
|
const lastpass = false;
|
||||||
|
|
||||||
|
initiateBrowserSso(code, state, lastpass);
|
||||||
|
|
||||||
|
const contentElement = document.getElementById("content");
|
||||||
|
const paragraphElement = contentElement?.querySelector("p");
|
||||||
|
expect(paragraphElement?.innerText).toBe("SSO login successful");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles lastpass flag correctly", () => {
|
||||||
|
const code = "testcode";
|
||||||
|
const state = "teststate";
|
||||||
|
const lastpass = true;
|
||||||
|
|
||||||
|
initiateBrowserSso(code, state, lastpass);
|
||||||
|
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
{ command: "authResult", code, state, lastpass },
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,10 +2,6 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { getQsParam } from "./common";
|
import { getQsParam } from "./common";
|
||||||
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
require("./sso.scss");
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
const code = getQsParam("code");
|
const code = getQsParam("code");
|
||||||
const state = getQsParam("state");
|
const state = getQsParam("state");
|
||||||
@@ -20,7 +16,7 @@ window.addEventListener("load", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function initiateWebAppSso(code: string, state: string) {
|
export function initiateWebAppSso(code: string, state: string) {
|
||||||
// If we've initiated SSO from somewhere other than the SSO component on the web app, the SSO component will add
|
// If we've initiated SSO from somewhere other than the SSO component on the web app, the SSO component will add
|
||||||
// a _returnUri to the state variable. Here we're extracting that URI and sending the user there instead of to the SSO component.
|
// a _returnUri to the state variable. Here we're extracting that URI and sending the user there instead of to the SSO component.
|
||||||
const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')");
|
const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')");
|
||||||
@@ -31,7 +27,7 @@ function initiateWebAppSso(code: string, state: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initiateBrowserSso(code: string, state: string, lastpass: boolean) {
|
export function initiateBrowserSso(code: string, state: string, lastpass: boolean) {
|
||||||
window.postMessage({ command: "authResult", code, state, lastpass }, window.location.origin);
|
window.postMessage({ command: "authResult", code, state, lastpass }, window.location.origin);
|
||||||
const handOffMessage = ("; " + document.cookie)
|
const handOffMessage = ("; " + document.cookie)
|
||||||
.split("; ssoHandOffMessage=")
|
.split("; ssoHandOffMessage=")
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ const plugins = [
|
|||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: "./src/connectors/sso.html",
|
template: "./src/connectors/sso.html",
|
||||||
filename: "sso-connector.html",
|
filename: "sso-connector.html",
|
||||||
chunks: ["connectors/sso"],
|
chunks: ["connectors/sso", "styles"],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: "./src/connectors/redirect.html",
|
template: "./src/connectors/redirect.html",
|
||||||
|
|||||||
Reference in New Issue
Block a user