[PM-7693] Remove cookie from Duo connector (#9699)
* utilizing locale service in duo * refactor launchDuoUri method * Add cookie information back in ext. and desktop to support backwards compatibility * Update duo-redirect.ts fixing comment
This commit is contained in:
parent
4f5c0de039
commit
705a02086e
|
@ -573,18 +573,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"masterPassDoesntMatch": {
|
|
||||||
"message": "Master password confirmation does not match."
|
|
||||||
},
|
|
||||||
"newAccountCreated": {
|
|
||||||
"message": "Your new account has been created! You may now log in."
|
|
||||||
},
|
|
||||||
"youSuccessfullyLoggedIn": {
|
"youSuccessfullyLoggedIn": {
|
||||||
"message": "You successfully logged in"
|
"message": "You successfully logged in"
|
||||||
},
|
},
|
||||||
"youMayCloseThisWindow": {
|
"youMayCloseThisWindow": {
|
||||||
"message": "You may close this window"
|
"message": "You may close this window"
|
||||||
},
|
},
|
||||||
|
"masterPassDoesntMatch": {
|
||||||
|
"message": "Master password confirmation does not match."
|
||||||
|
},
|
||||||
|
"newAccountCreated": {
|
||||||
|
"message": "Your new account has been created! You may now log in."
|
||||||
|
},
|
||||||
"masterPassSent": {
|
"masterPassSent": {
|
||||||
"message": "We've sent you an email with your master password hint."
|
"message": "We've sent you an email with your master password hint."
|
||||||
},
|
},
|
||||||
|
|
|
@ -149,17 +149,6 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
|
||||||
await this.submit();
|
await this.submit();
|
||||||
};
|
};
|
||||||
|
|
||||||
override async launchDuoFrameless() {
|
|
||||||
const duoHandOffMessage = {
|
|
||||||
title: this.i18nService.t("youSuccessfullyLoggedIn"),
|
|
||||||
message: this.i18nService.t("thisWindowWillCloseIn5Seconds"),
|
|
||||||
buttonText: this.i18nService.t("close"),
|
|
||||||
isCountdown: true,
|
|
||||||
};
|
|
||||||
document.cookie = `duoHandOffMessage=${JSON.stringify(duoHandOffMessage)}; SameSite=strict;`;
|
|
||||||
this.platformUtilsService.launchUri(this.duoFramelessUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
super.ngOnDestroy();
|
super.ngOnDestroy();
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { getQsParam } from "./common";
|
import { getQsParam } from "./common";
|
||||||
|
import { TranslationService } from "./translation.service";
|
||||||
|
|
||||||
require("./duo-redirect.scss");
|
require("./duo-redirect.scss");
|
||||||
|
|
||||||
const mobileDesktopCallback = "bitwarden://duo-callback";
|
const mobileDesktopCallback = "bitwarden://duo-callback";
|
||||||
|
let localeService: TranslationService = null;
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", async () => {
|
||||||
const redirectUrl = getQsParam("duoFramelessUrl");
|
const redirectUrl = getQsParam("duoFramelessUrl");
|
||||||
const handOffMessage = getQsParam("handOffMessage");
|
|
||||||
|
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
redirectToDuoFrameless(redirectUrl, handOffMessage);
|
redirectToDuoFrameless(redirectUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,19 +18,22 @@ window.addEventListener("load", () => {
|
||||||
const code = getQsParam("code");
|
const code = getQsParam("code");
|
||||||
const state = getQsParam("state");
|
const state = getQsParam("state");
|
||||||
|
|
||||||
|
localeService = new TranslationService(navigator.language, "locales");
|
||||||
|
await localeService.init();
|
||||||
|
|
||||||
if (client === "web") {
|
if (client === "web") {
|
||||||
const channel = new BroadcastChannel("duoResult");
|
const channel = new BroadcastChannel("duoResult");
|
||||||
|
|
||||||
channel.postMessage({ code: code, state: state });
|
channel.postMessage({ code: code, state: state });
|
||||||
channel.close();
|
channel.close();
|
||||||
|
|
||||||
processAndDisplayHandoffMessage();
|
displayHandoffMessage(client);
|
||||||
} else if (client === "browser") {
|
} else if (client === "browser") {
|
||||||
window.postMessage({ command: "duoResult", code: code, state: state }, "*");
|
window.postMessage({ command: "duoResult", code: code, state: state }, "*");
|
||||||
processAndDisplayHandoffMessage();
|
displayHandoffMessage(client);
|
||||||
} else if (client === "mobile" || client === "desktop") {
|
} else if (client === "mobile" || client === "desktop") {
|
||||||
if (client === "desktop") {
|
if (client === "desktop") {
|
||||||
processAndDisplayHandoffMessage();
|
displayHandoffMessage(client);
|
||||||
}
|
}
|
||||||
document.location.replace(
|
document.location.replace(
|
||||||
mobileDesktopCallback +
|
mobileDesktopCallback +
|
||||||
|
@ -42,103 +46,55 @@ window.addEventListener("load", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order to set a cookie with the hand off message, some clients need to use
|
* validate the Duo AuthUrl and redirect to it.
|
||||||
* this connector as a middleman to set the cookie before continuing to the duo url
|
|
||||||
* @param redirectUrl the duo auth url
|
* @param redirectUrl the duo auth url
|
||||||
* @param handOffMessage message to save as cookie
|
|
||||||
*/
|
*/
|
||||||
function redirectToDuoFrameless(redirectUrl: string, handOffMessage: string) {
|
function redirectToDuoFrameless(redirectUrl: string) {
|
||||||
const validateUrl = new URL(redirectUrl);
|
const validateUrl = new URL(redirectUrl);
|
||||||
|
|
||||||
if (validateUrl.protocol !== "https:" || !validateUrl.hostname.endsWith("duosecurity.com")) {
|
if (validateUrl.protocol !== "https:" || !validateUrl.hostname.endsWith("duosecurity.com")) {
|
||||||
throw new Error("Invalid redirect URL");
|
throw new Error("Invalid redirect URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
document.cookie = `duoHandOffMessage=${handOffMessage}; SameSite=strict;`;
|
|
||||||
window.location.href = decodeURIComponent(redirectUrl);
|
window.location.href = decodeURIComponent(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `duoHandOffMessage` must be set in the client via a cookie. This is so
|
* Note: browsers won't let javascript close a tab (button or otherwise) that wasn't opened by javascript,
|
||||||
* we can make use of i18n translations.
|
* so browser, desktop, and mobile are not able to take advantage of the countdown timer or close button.
|
||||||
*
|
|
||||||
* Format the message as an object and set is as a cookie. The following gives an
|
|
||||||
* example (be sure to replace strings with i18n translated text):
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* const duoHandOffMessage = {
|
|
||||||
* title: "You successfully logged in",
|
|
||||||
* message: "This window will automatically close in 5 seconds",
|
|
||||||
* buttonText: "Close",
|
|
||||||
* isCountdown: true
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
* document.cookie = `duoHandOffMessage=${encodeURIComponent(JSON.stringify(duoHandOffMessage))};SameSite=strict`;
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The `title`, `message`, and `buttonText` properties will be used to create the
|
|
||||||
* relevant DOM elements.
|
|
||||||
*
|
|
||||||
* Countdown timer:
|
|
||||||
* The `isCountdown` signifies that you want to start a countdown timer that will
|
|
||||||
* automatically close the tab when finished. The starting point for the timer will
|
|
||||||
* be based upon the first number that can be parsed from the `message` property
|
|
||||||
* (so be sure to add exactly one number to the `message`).
|
|
||||||
*
|
|
||||||
* This implementation makes it so the client does not have to split up the `message` into
|
|
||||||
* three translations, such as:
|
|
||||||
* ['This window will automatically close in', '5', 'seconds']
|
|
||||||
* ...which would cause bad translations in languages that swap the order of words.
|
|
||||||
*
|
|
||||||
* If `isCountdown` is undefined/false, there will be no countdown timer and the user
|
|
||||||
* will simply have to close the tab manually.
|
|
||||||
*
|
|
||||||
* If `buttonText` is undefined, there will be no close button.
|
|
||||||
*
|
|
||||||
* Note: browsers won't let javascript close a tab that wasn't opened by javascript,
|
|
||||||
* so some clients may not be able to take advantage of the countdown timer/close button.
|
|
||||||
*/
|
*/
|
||||||
function processAndDisplayHandoffMessage() {
|
function displayHandoffMessage(client: string) {
|
||||||
const handOffMessageCookie = ("; " + document.cookie)
|
|
||||||
|
|
||||||
.split("; duoHandOffMessage=")
|
|
||||||
.pop()
|
|
||||||
.split(";")
|
|
||||||
.shift();
|
|
||||||
const handOffMessage = JSON.parse(decodeURIComponent(handOffMessageCookie));
|
|
||||||
|
|
||||||
// Clear the cookie
|
|
||||||
document.cookie = "duoHandOffMessage=;SameSite=strict;max-age=0";
|
|
||||||
|
|
||||||
const content = document.getElementById("content");
|
const content = document.getElementById("content");
|
||||||
content.className = "text-center";
|
content.className = "text-center";
|
||||||
content.innerHTML = "";
|
content.innerHTML = "";
|
||||||
|
|
||||||
const h1 = document.createElement("h1");
|
const h1 = document.createElement("h1");
|
||||||
const p = document.createElement("p");
|
const p = document.createElement("p");
|
||||||
const button = document.createElement("button");
|
|
||||||
|
|
||||||
h1.textContent = handOffMessage.title;
|
h1.textContent = localeService.t("youSuccessfullyLoggedIn");
|
||||||
p.textContent = handOffMessage.message;
|
p.textContent =
|
||||||
button.textContent = handOffMessage.buttonText;
|
client == "web"
|
||||||
|
? (p.textContent = localeService.t("thisWindowWillCloseIn5Seconds"))
|
||||||
|
: localeService.t("youMayCloseThisWindow");
|
||||||
|
|
||||||
h1.className = "font-weight-semibold";
|
h1.className = "font-weight-semibold";
|
||||||
p.className = "mb-4";
|
p.className = "mb-4";
|
||||||
|
|
||||||
|
content.appendChild(h1);
|
||||||
|
content.appendChild(p);
|
||||||
|
|
||||||
|
// Web client will have a close button as well as an auto close timer
|
||||||
|
if (client == "web") {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.textContent = localeService.t("close");
|
||||||
button.className = "bg-primary text-white border-0 rounded py-2 px-3";
|
button.className = "bg-primary text-white border-0 rounded py-2 px-3";
|
||||||
|
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
content.appendChild(h1);
|
|
||||||
content.appendChild(p);
|
|
||||||
if (handOffMessage.buttonText) {
|
|
||||||
content.appendChild(button);
|
content.appendChild(button);
|
||||||
}
|
|
||||||
|
|
||||||
// Countdown timer (closes tab upon completion)
|
// Countdown timer (closes tab upon completion)
|
||||||
if (handOffMessage.isCountdown) {
|
|
||||||
let num = Number(p.textContent.match(/\d+/)[0]);
|
let num = Number(p.textContent.match(/\d+/)[0]);
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
|
|
@ -4151,6 +4151,9 @@
|
||||||
"thisWindowWillCloseIn5Seconds": {
|
"thisWindowWillCloseIn5Seconds": {
|
||||||
"message": "This window will automatically close in 5 seconds"
|
"message": "This window will automatically close in 5 seconds"
|
||||||
},
|
},
|
||||||
|
"youMayCloseThisWindow": {
|
||||||
|
"message": "You may close this window"
|
||||||
|
},
|
||||||
"includeAllTeamsFeatures": {
|
"includeAllTeamsFeatures": {
|
||||||
"message": "All Teams features, plus:"
|
"message": "All Teams features, plus:"
|
||||||
},
|
},
|
||||||
|
|
|
@ -506,6 +506,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||||
return authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey;
|
return authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// implemented in clients
|
async launchDuoFrameless() {
|
||||||
async launchDuoFrameless() {}
|
this.platformUtilsService.launchUri(this.duoFramelessUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue