From 1ba6effe6b8f9fdd59adbbe012fca16fff994089 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Mon, 19 Sep 2022 14:49:25 +1000 Subject: [PATCH] fix(web): broadcastchannel not supported on old safari (#4014) This utilizes a wrapper around the BroadcastChannel API to handle browsers which don't properly support the API such as Safari prior to 10.4 and iOS prior to 10.4. Where possible it uses the native API. Co-authored-by: Amir Zarrinkafsh --- web/package.json | 1 + web/pnpm-lock.yaml | 79 +++++++++++++++---- .../FirstFactor/FirstFactorForm.tsx | 12 +-- web/src/views/LoginPortal/LoginPortal.tsx | 11 ++- 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/web/package.json b/web/package.json index 37d69d9b3..e672a7f4e 100644 --- a/web/package.json +++ b/web/package.json @@ -29,6 +29,7 @@ "@mui/material": "5.10.5", "@mui/styles": "5.10.3", "axios": "0.27.2", + "broadcast-channel": "4.10.0", "classnames": "2.3.2", "i18next": "21.9.2", "i18next-browser-languagedetector": "6.1.5", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b7dcd0610..d148bde15 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -25,6 +25,7 @@ specifiers: '@typescript-eslint/parser': 5.37.0 '@vitejs/plugin-react': 2.1.0 axios: 0.27.2 + broadcast-channel: 4.10.0 classnames: 2.3.2 esbuild: 0.15.8 esbuild-jest: 0.5.0 @@ -76,6 +77,7 @@ dependencies: '@mui/material': 5.10.5_af5ln35zuaotaffazii6n6bke4 '@mui/styles': 5.10.3_w5j4k42lgipnm43s3brx6h3c34 axios: 0.27.2 + broadcast-channel: 4.10.0 classnames: 2.3.2 i18next: 21.9.2 i18next-browser-languagedetector: 6.1.5 @@ -4182,7 +4184,6 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /base/0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} @@ -4197,12 +4198,16 @@ packages: pascalcase: 0.1.1 dev: true + /big-integer/1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: false + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true /braces/2.3.2: resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} @@ -4229,6 +4234,19 @@ packages: fill-range: 7.0.1 dev: true + /broadcast-channel/4.10.0: + resolution: {integrity: sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ==} + dependencies: + '@babel/runtime': 7.18.9 + detect-node: 2.1.0 + microseconds: 0.2.0 + nano-time: 1.0.0 + oblivious-set: 1.0.0 + p-queue: 6.6.2 + rimraf: 3.0.2 + unload: 2.3.1 + dev: false + /browser-process-hrtime/1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: true @@ -4437,8 +4455,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} /confusing-browser-globals/1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} @@ -4712,6 +4729,10 @@ packages: engines: {node: '>=8'} dev: true + /detect-node/2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + dev: false + /diff-sequences/29.0.0: resolution: {integrity: sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5508,6 +5529,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /eventemitter3/4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + /exec-sh/0.3.6: resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==} dev: true @@ -5750,7 +5775,6 @@ packages: /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true /fsevents/2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -5872,7 +5896,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /global-dirs/0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -6138,11 +6161,9 @@ packages: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true /ini/1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -7381,6 +7402,10 @@ packages: picomatch: 2.3.1 dev: true + /microseconds/0.2.0: + resolution: {integrity: sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==} + dev: false + /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -7405,7 +7430,6 @@ packages: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - dev: true /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -7440,6 +7464,12 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /nano-time/1.0.0: + resolution: {integrity: sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==} + dependencies: + big-integer: 1.6.51 + dev: false + /nanoid/3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -7622,11 +7652,14 @@ packages: es-abstract: 1.20.1 dev: true + /oblivious-set/1.0.0: + resolution: {integrity: sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==} + dev: false + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - dev: true /onetime/5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -7671,7 +7704,6 @@ packages: /p-finally/1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} - dev: true /p-limit/1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} @@ -7715,6 +7747,21 @@ packages: p-limit: 3.1.0 dev: true + /p-queue/6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + dev: false + + /p-timeout/3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + dependencies: + p-finally: 1.0.0 + dev: false + /p-try/1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} @@ -7764,7 +7811,6 @@ packages: /path-is-absolute/1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - dev: true /path-key/2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} @@ -8250,7 +8296,6 @@ packages: hasBin: true dependencies: glob: 7.2.3 - dev: true /rollup/2.78.0: resolution: {integrity: sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==} @@ -8997,6 +9042,13 @@ packages: engines: {node: '>= 10.0.0'} dev: true + /unload/2.3.1: + resolution: {integrity: sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA==} + dependencies: + '@babel/runtime': 7.18.9 + detect-node: 2.1.0 + dev: false + /unset-value/1.0.0: resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} engines: {node: '>=0.10.0'} @@ -9234,7 +9286,6 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true /write-file-atomic/3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx index a9cc4fd2f..5c03eec52 100644 --- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx +++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx @@ -2,6 +2,7 @@ import React, { MutableRefObject, useEffect, useMemo, useRef, useState } from "r import { Button, Checkbox, FormControlLabel, Grid, Link, Theme } from "@mui/material"; import makeStyles from "@mui/styles/makeStyles"; +import { BroadcastChannel } from "broadcast-channel"; import classnames from "classnames"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -25,6 +26,7 @@ export interface Props { onAuthenticationStart: () => void; onAuthenticationFailure: () => void; onAuthenticationSuccess: (redirectURL: string | undefined) => void; + onChannelStateChange: () => void; } const FirstFactorForm = function (props: Props) { @@ -34,7 +36,7 @@ const FirstFactorForm = function (props: Props) { const requestMethod = useRequestMethod(); const workflow = useWorkflow(); - const loginChannel = useMemo(() => new BroadcastChannel("login"), []); + const loginChannel = useMemo(() => new BroadcastChannel("login"), []); const [rememberMe, setRememberMe] = useState(false); const [username, setUsername] = useState(""); const [usernameError, setUsernameError] = useState(false); @@ -52,9 +54,9 @@ const FirstFactorForm = function (props: Props) { }, [usernameRef]); useEffect(() => { - loginChannel.addEventListener("message", (ev) => { - if (ev.data) { - props.onAuthenticationSuccess(redirectionURL); + loginChannel.addEventListener("message", (authenticated) => { + if (authenticated) { + props.onChannelStateChange(); } }); }, [loginChannel, redirectionURL, props]); @@ -80,7 +82,7 @@ const FirstFactorForm = function (props: Props) { props.onAuthenticationStart(); try { const res = await postFirstFactor(username, password, rememberMe, redirectionURL, requestMethod, workflow); - loginChannel.postMessage(true); + await loginChannel.postMessage(true); props.onAuthenticationSuccess(res ? res.redirect : undefined); } catch (err) { console.error(err); diff --git a/web/src/views/LoginPortal/LoginPortal.tsx b/web/src/views/LoginPortal/LoginPortal.tsx index ac85220dd..bb1d7daf0 100644 --- a/web/src/views/LoginPortal/LoginPortal.tsx +++ b/web/src/views/LoginPortal/LoginPortal.tsx @@ -45,6 +45,7 @@ const LoginPortal = function (props: Props) { const workflow = useWorkflow(); const { createErrorNotification } = useNotifications(); const [firstFactorDisabled, setFirstFactorDisabled] = useState(true); + const [broadcastRedirect, setBroadcastRedirect] = useState(false); const redirector = useRedirector(); const [state, fetchState, , fetchStateError] = useAutheliaState(); @@ -115,7 +116,8 @@ const LoginPortal = function (props: Props) { ((configuration && configuration.available_methods.size === 0 && state.authentication_level >= AuthenticationLevel.OneFactor) || - state.authentication_level === AuthenticationLevel.TwoFactor) + state.authentication_level === AuthenticationLevel.TwoFactor || + broadcastRedirect) ) { try { const res = await checkSafeRedirection(redirectionURL); @@ -164,8 +166,14 @@ const LoginPortal = function (props: Props) { configuration, createErrorNotification, redirector, + broadcastRedirect, ]); + const handleChannelStateChange = async () => { + setBroadcastRedirect(true); + fetchState(); + }; + const handleAuthSuccess = async (redirectionURL: string | undefined) => { if (redirectionURL) { // Do an external redirection pushed by the server. @@ -195,6 +203,7 @@ const LoginPortal = function (props: Props) { onAuthenticationStart={() => setFirstFactorDisabled(true)} onAuthenticationFailure={() => setFirstFactorDisabled(false)} onAuthenticationSuccess={handleAuthSuccess} + onChannelStateChange={handleChannelStateChange} /> }