diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/sso-login.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/sso-login.php index a964e066c..cfe8d5833 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/sso-login.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/sso-login.php @@ -18,5 +18,5 @@ echo $wcExtractor->getScriptTagHtml(); ?> - + \ No newline at end of file diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php index fed4f5854..8fe37f6a9 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php @@ -8,6 +8,7 @@ * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. */ + /** * @todo refactor globals – currently if you try to use $GLOBALS the class will break. */ @@ -56,7 +57,7 @@ class ServerState /** * SSO Sub IDs from the my servers config file. */ - public $ssoSubIds = ''; + public $ssoEnabled = false; private $osVersion; private $osVersionBranch; private $rebootDetails; @@ -71,7 +72,7 @@ class ServerState public $myServersMemoryCfg = []; public $host = 'unknown'; public $combinedKnownOrigins = []; - + public $nginxCfg = []; public $flashbackupStatus = []; public $registered = false; @@ -90,7 +91,7 @@ class ServerState * @see - getWebguiGlobal() for usage * */ global $webguiGlobals; - $this->webguiGlobals =& $webguiGlobals; + $this->webguiGlobals = &$webguiGlobals; // echo "
" . json_encode($this->webguiGlobals, JSON_PRETTY_PRINT) . "
"; $this->var = (array)parse_ini_file('state/var.ini'); @@ -123,7 +124,8 @@ class ServerState /** * Retrieve the value of a webgui global setting. */ - public function getWebguiGlobal(string $key, string $subkey = null) { + public function getWebguiGlobal(string $key, string $subkey = null) + { if (!$subkey) { return _var($this->webguiGlobals, $key, ''); } @@ -131,7 +133,8 @@ class ServerState return _var($keyArray, $subkey, ''); } - private function setConnectValues() { + private function setConnectValues() + { if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net')) { $this->connectPluginInstalled = 'dynamix.unraid.net.plg'; } @@ -158,13 +161,15 @@ class ServerState $this->getFlashBackupStatus(); } - private function getFlashBackupStatus() { + private function getFlashBackupStatus() + { $flashbackupCfg = '/var/local/emhttp/flashbackup.ini'; $this->flashbackupStatus = (file_exists($flashbackupCfg)) ? @parse_ini_file($flashbackupCfg) : []; $this->flashBackupActivated = empty($this->flashbackupStatus['activated']) ? '' : 'true'; } - private function getMyServersCfgValues() { + private function getMyServersCfgValues() + { /** * @todo can we read this from somewhere other than the flash? Connect page uses this path and /boot/config/plugins/dynamix.my.servers/myservers.cfg… * - $myservers_memory_cfg_path ='/var/local/emhttp/myservers.cfg'; @@ -197,10 +202,11 @@ class ServerState $this->registered = !empty($this->myServersFlashCfg['remote']['apikey']) && $this->connectPluginInstalled; $this->registeredTime = $this->myServersFlashCfg['remote']['regWizTime'] ?? ''; $this->username = $this->myServersFlashCfg['remote']['username'] ?? ''; - $this->ssoSubIds = $this->myServersFlashCfg['remote']['ssoSubIds'] ?? ''; + $this->ssoEnabled = $this->myServersFlashCfg['remote']['ssoSubIds'] !== ''; } - private function getConnectKnownOrigins() { + private function getConnectKnownOrigins() + { /** * Allowed origins warning displayed when the current webGUI URL is NOT included in the known lists of allowed origins. * Include localhost in the test, but only display HTTP(S) URLs that do not include localhost. @@ -208,7 +214,7 @@ class ServerState $this->host = $_SERVER['HTTP_HOST'] ?? "unknown"; $memoryCfgPath = '/var/local/emhttp/myservers.cfg'; $this->myServersMemoryCfg = (file_exists($memoryCfgPath)) ? @parse_ini_file($memoryCfgPath) : []; - $this->myServersMiniGraphConnected = (($this->myServersMemoryCfg['minigraph']??'') === 'CONNECTED'); + $this->myServersMiniGraphConnected = (($this->myServersMemoryCfg['minigraph'] ?? '') === 'CONNECTED'); $allowedOrigins = $this->myServersMemoryCfg['allowedOrigins'] ?? ""; $extraOrigins = $this->myServersFlashCfg['api']['extraOrigins'] ?? ""; @@ -224,8 +230,8 @@ class ServerState $this->combinedKnownOrigins = explode(",", $combinedOrigins); if ($this->combinedKnownOrigins) { - foreach($this->combinedKnownOrigins as $key => $origin) { - if ( (strpos($origin, "http") === false) || (strpos($origin, "localhost") !== false) ) { + foreach ($this->combinedKnownOrigins as $key => $origin) { + if ((strpos($origin, "http") === false) || (strpos($origin, "localhost") !== false)) { // clean up $this->combinedKnownOrigins, only display warning if origins still remain to display unset($this->combinedKnownOrigins[$key]); } @@ -238,7 +244,8 @@ class ServerState } } - private function detectActivationCode() { + private function detectActivationCode() + { // Fresh server and we're not loading with a callback param to install if ($this->state !== 'ENOKEYFILE' || !empty($_GET['c'])) { return; @@ -312,6 +319,7 @@ class ServerState "registered" => $this->registered, "registeredTime" => $this->registeredTime, "site" => _var($_SERVER, 'REQUEST_SCHEME') . "://" . _var($_SERVER, 'HTTP_HOST'), + "ssoEnabled" => $this->ssoEnabled, "state" => $this->state, "theme" => [ "banner" => !empty($this->getWebguiGlobal('display', 'banner')), @@ -326,7 +334,6 @@ class ServerState "uptime" => 1000 * (time() - round(strtok(exec("cat /proc/uptime"), ' '))), "username" => $this->username, "wanFQDN" => @$this->nginxCfg['NGINX_WANFQDN'] ?? '', - "ssoSubIds" => $this->ssoSubIds ]; if ($this->combinedKnownOrigins) { @@ -357,7 +364,8 @@ class ServerState * * @return string */ - public function getServerStateJson() { + public function getServerStateJson() + { return json_encode($this->getServerState()); } @@ -366,7 +374,8 @@ class ServerState * * @return string */ - public function getServerStateJsonForHtmlAttr() { + public function getServerStateJsonForHtmlAttr() + { $json = json_encode($this->getServerState()); return htmlspecialchars($json, ENT_QUOTES, 'UTF-8'); } diff --git a/web/_data/serverState.ts b/web/_data/serverState.ts index 05abb341a..a1c4e91f6 100644 --- a/web/_data/serverState.ts +++ b/web/_data/serverState.ts @@ -181,7 +181,7 @@ export const serverState: Server = { regExp, regGuid, site: 'http://localhost:4321', - ssoSubIds: '1234567890,0987654321,297294e2-b31c-4bcc-a441-88aee0ad609f', + ssoEnabled: true, state, theme: { banner: false, diff --git a/web/components/SsoButton.ce.vue b/web/components/SsoButton.ce.vue index 8be9d48ef..a0e939689 100644 --- a/web/components/SsoButton.ce.vue +++ b/web/components/SsoButton.ce.vue @@ -3,7 +3,7 @@ import Button from '~/components/Brand/Button.vue'; import { ACCOUNT } from '~/helpers/urls'; export interface Props { - subids?: string; + ssoenabled?: boolean; } const props = defineProps(); @@ -34,25 +34,39 @@ const generateStateToken = (): string => { return state; }; -onMounted(() => { - const search = new URLSearchParams(window.location.search); - const token = search.get('token') ?? ''; - const state = search.get('state') ?? ''; - const sessionState = getStateToken(); - if (token && state === sessionState) { - enterCallbackTokenIntoField(token); - // Clear the token from the URL - window.history.replaceState({}, document.title, window.location.pathname); - window.location.search = ''; +onMounted(async () => { + try { + const search = new URLSearchParams(window.location.search); + const code = search.get('code') ?? ''; + const state = search.get('state') ?? ''; + const sessionState = getStateToken(); + + if (code && state === sessionState) { + const token = await fetch(new URL('token', ACCOUNT), { + method: 'POST', + body: new URLSearchParams({ + code, + clientId: 'CONNECT_SERVER_SSO', + grant_type: 'authorization_code', + }), + }); + if (token.ok) { + const tokenBody = await token.json(); + enterCallbackTokenIntoField(tokenBody.access_token); + if (window.location.search) { + window.history.replaceState({}, document.title, window.location.pathname); + window.location.search = ''; + } + } + } + } catch (err) { + console.error('Error fetching token', err); + } finally { } }); const externalSSOUrl = computed(() => { - if (props.subids === undefined) { - return ''; - } const url = new URL('sso', ACCOUNT); - url.searchParams.append('uids', props.subids); const callbackUrlLogin = new URL('login', window.location.origin); const state = generateStateToken(); callbackUrlLogin.searchParams.append('state', state); @@ -63,7 +77,7 @@ const externalSSOUrl = computed(() => { diff --git a/web/store/server.ts b/web/store/server.ts index b4dde9b4b..6bf83f896 100644 --- a/web/store/server.ts +++ b/web/store/server.ts @@ -155,7 +155,7 @@ export const useServerStore = defineStore("server", () => { return today.isAfter(parsedUpdateExpirationDate, "day"); }); const site = ref(""); - const ssoSubIds = ref(""); + const ssoEnabled = ref(false); const state = ref(); const theme = ref(); watch(theme, (newVal) => { @@ -1209,8 +1209,8 @@ export const useServerStore = defineStore("server", () => { if (typeof data?.regTo !== "undefined") { regTo.value = data.regTo; } - if (typeof data?.ssoSubIds !== "undefined") { - ssoSubIds.value = data.ssoSubIds; + if (typeof data?.ssoEnabled !== "undefined") { + ssoEnabled.value = Boolean(data.ssoEnabled); } if (typeof data.activationCodeData !== "undefined") { @@ -1478,7 +1478,7 @@ export const useServerStore = defineStore("server", () => { parsedRegExp, regUpdatesExpired, site, - ssoSubIds, + ssoEnabled, state, theme, updateOsIgnoredReleases, diff --git a/web/types/server.ts b/web/types/server.ts index e9c28d392..374094949 100644 --- a/web/types/server.ts +++ b/web/types/server.ts @@ -1,7 +1,8 @@ import type { Config, PartialCloudFragment } from '~/composables/gql/graphql'; +import type { ActivationCodeData } from '~/store/activationCode'; import type { Theme } from '~/store/theme'; import type { UserProfileLink } from '~/types/userProfile'; -import type { ActivationCodeData } from '~/store/activationCode'; + export type ServerState = 'BASIC' | 'PLUS' @@ -108,6 +109,7 @@ export interface Server { regExp?: number; regUpdatesExpired?: boolean; site?: string; + ssoEnabled?: boolean; state?: ServerState; theme?: Theme | undefined; updateOsIgnoredReleases?: string[]; @@ -117,7 +119,6 @@ export interface Server { username?: string; wanFQDN?: string; wanIp?: string; - ssoSubIds?: string; } export interface ServerAccountCallbackSendPayload { @@ -196,4 +197,4 @@ export interface ServerStateData { message: string; error?: ServerStateDataError | boolean; withKey?: boolean; // @todo potentially remove -} +} \ No newline at end of file