diff --git a/api/src/cli.ts b/api/src/cli.ts index 2903f8403..800263450 100644 --- a/api/src/cli.ts +++ b/api/src/cli.ts @@ -9,14 +9,14 @@ import { cliLogger, internalLogger } from '@app/core/log'; import { CliModule } from '@app/unraid-api/cli/cli.module'; try { - const shellToUse = execSync('which bash'); + const shellToUse = execSync('which bash').toString().trim(); await CommandFactory.run(CliModule, { cliName: 'unraid-api', logger: false, completion: { fig: true, cmd: 'unraid-api', - nativeShell: { executablePath: shellToUse.toString('utf-8') }, + nativeShell: { executablePath: shellToUse }, }, }); } catch (error) { diff --git a/api/src/core/sso/sso-remove.ts b/api/src/core/sso/sso-remove.ts new file mode 100644 index 000000000..c3e73323e --- /dev/null +++ b/api/src/core/sso/sso-remove.ts @@ -0,0 +1,18 @@ +import { existsSync, renameSync, unlinkSync } from 'node:fs'; + +export const removeSso = () => { + const path = '/usr/local/emhttp/plugins/dynamix/include/.login.php'; + const backupPath = path + '.bak'; + + // Remove the SSO login inject file if it exists + if (existsSync(path)) { + unlinkSync(path); + } + + // Move the backup file to the original location + if (existsSync(backupPath)) { + renameSync(backupPath, path); + } + + console.log('Restored .login php file'); +}; diff --git a/api/src/core/sso/sso-setup.ts b/api/src/core/sso/sso-setup.ts new file mode 100755 index 000000000..5eb8dcdd7 --- /dev/null +++ b/api/src/core/sso/sso-setup.ts @@ -0,0 +1,62 @@ +import { existsSync } from 'node:fs'; +import { copyFile, readFile, rename, unlink, writeFile } from 'node:fs/promises'; + +export const setupSso = async () => { + const path = '/usr/local/emhttp/plugins/dynamix/include/.login.php'; + + // Define the new PHP function to insert + const newFunction = ` +function verifyUsernamePasswordAndSSO(string $username, string $password): bool { + if ($username != "root") return false; + + $output = exec("/usr/bin/getent shadow $username"); + if ($output === false) return false; + $credentials = explode(":", $output); + $valid = password_verify($password, $credentials[1]); + if ($valid) { + return true; + } + // We may have an SSO token, attempt validation + if (strlen($password) > 800) { + $safePassword = escapeshellarg($password); + $response = exec("/usr/local/bin/unraid-api sso validate-token $safePassword", $output, $code); + my_logger("SSO Login Response: $response"); + if ($code === 0 && $response && strpos($response, '"valid":true') !== false) { + return true; + } + } + return false; +}`; + + const tagToInject = ''; + + // Backup the original file if exists + if (existsSync(path + '.bak')) { + await copyFile(path + '.bak', path); + await unlink(path + '.bak'); + } + + // Read the file content + let fileContent = await readFile(path, 'utf-8'); + + // Backup the original content + await writeFile(path + '.bak', fileContent); + + // Add new function after the opening PHP tag ( tag + fileContent = fileContent.replace(/<\/body>/i, `${tagToInject}\n`); + + // Write the updated content back to the file + await writeFile(path, fileContent); + + console.log('Function replaced successfully.'); +}; diff --git a/api/src/index.ts b/api/src/index.ts index 1bb1c9c65..b420f9ac8 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -14,6 +14,7 @@ import { WebSocket } from 'ws'; import { logger } from '@app/core/log'; import { setupLogRotation } from '@app/core/logrotate/setup-logrotate'; +import { setupSso } from '@app/core/sso/sso-setup'; import { fileExistsSync } from '@app/core/utils/files/file-exists'; import { environment, PORT } from '@app/environment'; import * as envVars from '@app/environment'; @@ -97,6 +98,11 @@ try { startMiddlewareListeners(); + // If the config contains SSO IDs, enable SSO + if (store.getState().config.remote.ssoSubIds) { + await setupSso(); + } + // On process exit stop HTTP server exitHook((signal) => { console.log('exithook', signal); diff --git a/api/src/unraid-api/cli/cli.module.ts b/api/src/unraid-api/cli/cli.module.ts index 0f61d082e..71a34cdb3 100644 --- a/api/src/unraid-api/cli/cli.module.ts +++ b/api/src/unraid-api/cli/cli.module.ts @@ -1,17 +1,18 @@ import { Module } from '@nestjs/common'; +import { ConfigCommand } from '@app/unraid-api/cli/config.command'; import { KeyCommand } from '@app/unraid-api/cli/key.command'; import { LogService } from '@app/unraid-api/cli/log.service'; +import { LogsCommand } from '@app/unraid-api/cli/logs.command'; import { ReportCommand } from '@app/unraid-api/cli/report.command'; import { RestartCommand } from '@app/unraid-api/cli/restart.command'; +import { SSOCommand } from '@app/unraid-api/cli/sso.command'; import { StartCommand } from '@app/unraid-api/cli/start.command'; +import { StatusCommand } from '@app/unraid-api/cli/status.command'; import { StopCommand } from '@app/unraid-api/cli/stop.command'; import { SwitchEnvCommand } from '@app/unraid-api/cli/switch-env.command'; import { VersionCommand } from '@app/unraid-api/cli/version.command'; -import { StatusCommand } from '@app/unraid-api/cli/status.command'; import { ValidateTokenCommand } from '@app/unraid-api/cli/validate-token.command'; -import { LogsCommand } from '@app/unraid-api/cli/logs.command'; -import { ConfigCommand } from '@app/unraid-api/cli/config.command'; @Module({ providers: [ @@ -24,9 +25,10 @@ import { ConfigCommand } from '@app/unraid-api/cli/config.command'; SwitchEnvCommand, VersionCommand, StatusCommand, + SSOCommand, ValidateTokenCommand, LogsCommand, - ConfigCommand + ConfigCommand, ], }) export class CliModule {} diff --git a/api/src/unraid-api/cli/sso.command.ts b/api/src/unraid-api/cli/sso.command.ts new file mode 100644 index 000000000..ce4593c03 --- /dev/null +++ b/api/src/unraid-api/cli/sso.command.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; + +import { Command, CommandRunner } from 'nest-commander'; + +import { LogService } from '@app/unraid-api/cli/log.service'; +import { ValidateTokenCommand } from '@app/unraid-api/cli/validate-token.command'; + +@Injectable() +@Command({ + name: 'sso', + description: 'Main Command to Configure / Validate SSO Tokens', + subCommands: [ValidateTokenCommand], +}) +export class SSOCommand extends CommandRunner { + constructor(private readonly logger: LogService) { + super(); + } + + async run(): Promise { + this.logger.info('Please provide a subcommand or use --help for more information'); + } +} diff --git a/api/src/unraid-api/cli/validate-token.command.ts b/api/src/unraid-api/cli/validate-token.command.ts index cf5f25153..902fa28da 100644 --- a/api/src/unraid-api/cli/validate-token.command.ts +++ b/api/src/unraid-api/cli/validate-token.command.ts @@ -1,14 +1,15 @@ import type { JWTPayload } from 'jose'; import { createLocalJWKSet, createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose'; -import { Command, CommandRunner } from 'nest-commander'; +import { CommandRunner, SubCommand } from 'nest-commander'; import { JWKS_LOCAL_PAYLOAD, JWKS_REMOTE_LINK } from '@app/consts'; import { store } from '@app/store'; import { loadConfigFile } from '@app/store/modules/config'; import { LogService } from '@app/unraid-api/cli/log.service'; -@Command({ +@SubCommand({ name: 'validate-token', + aliases: ['validate', 'v'], description: 'Returns JSON: { error: string | null, valid: boolean }', arguments: '', }) @@ -33,11 +34,13 @@ export class ValidateTokenCommand extends CommandRunner { async run(passedParams: string[]): Promise { if (passedParams.length !== 1) { - this.logger.error('Please pass token argument only'); - process.exit(1); + this.createErrorAndExit('Please pass token argument only'); } const token = passedParams[0]; + if (typeof token !== 'string' || token.trim() === '') { + this.createErrorAndExit('Invalid token provided'); + } let caughtError: null | unknown = null; let tokenPayload: null | JWTPayload = null; 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 new file mode 100644 index 000000000..414c1d07c --- /dev/null +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/sso-login.php @@ -0,0 +1,22 @@ +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 e09f6909d..f66c07e10 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 @@ -53,6 +53,10 @@ class ServerState "nokeyserver" => 'NO_KEY_SERVER', "withdrawn" => 'WITHDRAWN', ]; + /** + * SSO Sub IDs from the my servers config file. + */ + private $ssoSubIds = ''; private $osVersion; private $osVersionBranch; private $rebootDetails; @@ -193,6 +197,7 @@ 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'] ?? ''; } private function getConnectKnownOrigins() { @@ -321,6 +326,7 @@ 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) { diff --git a/web/_data/serverState.ts b/web/_data/serverState.ts index 945532889..05abb341a 100644 --- a/web/_data/serverState.ts +++ b/web/_data/serverState.ts @@ -1,3 +1,4 @@ +; // import dayjs, { extend } from 'dayjs'; // import customParseFormat from 'dayjs/plugin/customParseFormat'; // import relativeTime from 'dayjs/plugin/relativeTime'; @@ -6,11 +7,10 @@ // import QueryStringAddon from 'wretch/addons/queryString'; // import { OS_RELEASES } from '~/helpers/urls'; -import type { - Server, - ServerState, - // ServerUpdateOsResponse, -} from "~/types/server"; +import type { Server, ServerState +// ServerUpdateOsResponse, +} from '~/types/server'; + // dayjs plugins // extend(customParseFormat); @@ -44,10 +44,10 @@ import type { // EBLACKLISTED2 // ENOCONN -const state: ServerState = "ENOKEYFILE" as ServerState; -const currentFlashGuid = "1111-1111-YIJD-ZACK1234TEST"; // this is the flash drive that's been booted from -const regGuid = "1111-1111-YIJD-ZACK1234TEST"; // this guid is registered in key server -const keyfileBase64 = ""; +const state: ServerState = 'ENOKEYFILE' as ServerState; +const currentFlashGuid = '1111-1111-YIJD-ZACK1234TEST'; // this is the flash drive that's been booted from +const regGuid = '1111-1111-YIJD-ZACK1234TEST'; // this guid is registered in key server +const keyfileBase64 = ''; // const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server // const newGuid = `1234-1234-${makeid(4)}-123412341234`; // this is a new USB, not registered @@ -65,50 +65,50 @@ let expireTime = 0; let regExp: number | undefined; let regDevs = 0; -let regTy = ""; +let regTy = ''; switch (state) { - case "EEXPIRED": + case 'EEXPIRED': expireTime = uptime; // 1 hour ago break; - case "ENOCONN": + case 'ENOCONN': break; - case "TRIAL": + case 'TRIAL': expireTime = oneHourFromNow; // in 1 hour - regTy = "Trial"; + regTy = 'Trial'; break; - case "BASIC": + case 'BASIC': regDevs = 6; - regTy = "Basic"; + regTy = 'Basic'; break; - case "PLUS": + case 'PLUS': regDevs = 12; - regTy = "Plus"; + regTy = 'Plus'; break; - case "PRO": + case 'PRO': regDevs = -1; - regTy = "Pro"; + regTy = 'Pro'; break; - case "STARTER": + case 'STARTER': regDevs = 6; regExp = ninetyDaysAgo; - regTy = "Starter"; + regTy = 'Starter'; break; - case "UNLEASHED": + case 'UNLEASHED': regDevs = -1; regExp = ninetyDaysAgo; - regTy = "Unleashed"; + regTy = 'Unleashed'; break; - case "LIFETIME": + case 'LIFETIME': regDevs = -1; - regTy = "Lifetime"; + regTy = 'Lifetime'; break; } // const connectPluginInstalled = 'dynamix.unraid.net.staging.plg'; -const connectPluginInstalled = "dynamix.unraid.net.staging.plg"; +const connectPluginInstalled = 'dynamix.unraid.net.staging.plg'; -const osVersion = "7.0.0-beta.2.10"; -const osVersionBranch = "stable"; +const osVersion = '7.0.0-beta.2.10'; +const osVersionBranch = 'stable'; // const parsedRegExp = regExp ? dayjs(regExp).format('YYYY-MM-DD') : undefined; // const mimicWebguiUnraidCheck = async (): Promise => { @@ -134,62 +134,63 @@ const osVersionBranch = "stable"; export const serverState: Server = { activationCodeData: { - "code": "CC2KP3TDRF", - "partnerName": "OEM Partner", - "partnerUrl": "https://unraid.net/OEM+Partner", - "sysModel": "OEM Partner v1", - "comment": "OEM Partner NAS", - "caseIcon": "case-model.png", - "header": "#ffffff", - "headermetacolor": "#eeeeee", - "background": "#000000", - "showBannerGradient": "yes", - "partnerLogo": true, + code: 'CC2KP3TDRF', + partnerName: 'OEM Partner', + partnerUrl: 'https://unraid.net/OEM+Partner', + sysModel: 'OEM Partner v1', + comment: 'OEM Partner NAS', + caseIcon: 'case-model.png', + header: '#ffffff', + headermetacolor: '#eeeeee', + background: '#000000', + showBannerGradient: 'yes', + partnerLogo: true, }, - apiKey: "unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810", - avatar: "https://source.unsplash.com/300x300/?portrait", + apiKey: 'unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810', + avatar: 'https://source.unsplash.com/300x300/?portrait', config: { id: 'config-id', error: null, valid: false, }, connectPluginInstalled, - description: "DevServer9000", + description: 'DevServer9000', deviceCount: 3, expireTime, flashBackupActivated: !!connectPluginInstalled, - flashProduct: "SanDisk_3.2Gen1", - flashVendor: "USB", + flashProduct: 'SanDisk_3.2Gen1', + flashVendor: 'USB', guid: currentFlashGuid, // "guid": "0781-5583-8355-81071A2B0211", inIframe: false, // keyfile: 'DUMMY_KEYFILE', keyfile: keyfileBase64, - lanIp: "192.168.254.36", - license: "", - locale: "en_US", // en_US, ja - name: "dev-static", + lanIp: '192.168.254.36', + license: '', + locale: 'en_US', // en_US, ja + name: 'dev-static', osVersion, osVersionBranch, registered: connectPluginInstalled ? true : false, // registered: false, regGen: 0, regTm: twoDaysAgo, - regTo: "Zack Spear", + regTo: 'Zack Spear', regTy, regDevs, regExp, regGuid, - site: "http://localhost:4321", + site: 'http://localhost:4321', + ssoSubIds: '1234567890,0987654321,297294e2-b31c-4bcc-a441-88aee0ad609f', state, theme: { banner: false, bannerGradient: false, - bgColor: "", + bgColor: '', descriptionShow: true, - metaColor: "", - name: "white", - textColor: "", + metaColor: '', + name: 'white', + textColor: '', }, // updateOsResponse: { // version: '6.12.6', @@ -201,6 +202,6 @@ export const serverState: Server = { // sha256: '2f5debaf80549029cf6dfab0db59180e7e3391c059e6521aace7971419c9c4bf', // }, uptime, - username: "zspearmint", - wanFQDN: "", -}; + username: 'zspearmint', + wanFQDN: '', +}; \ No newline at end of file diff --git a/web/components/SsoButton.ce.vue b/web/components/SsoButton.ce.vue new file mode 100644 index 000000000..abb3f1898 --- /dev/null +++ b/web/components/SsoButton.ce.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/web/nuxt.config.ts b/web/nuxt.config.ts index b4a78b4fc..83170261f 100644 --- a/web/nuxt.config.ts +++ b/web/nuxt.config.ts @@ -40,119 +40,122 @@ const charsToReserve = '_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01 // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ - ssr: false, + ssr: false, - devServer: { - port: 4321, - }, + devServer: { + port: 4321, + }, - devtools: { - enabled: true, - }, + devtools: { + enabled: true, + }, - modules: [ - "@vueuse/nuxt", - "@pinia/nuxt", - "@nuxtjs/tailwindcss", - "nuxt-custom-elements", - "@nuxt/eslint", - "shadcn-nuxt", - ], + modules: [ + '@vueuse/nuxt', + '@pinia/nuxt', + '@nuxtjs/tailwindcss', + 'nuxt-custom-elements', + '@nuxt/eslint', + 'shadcn-nuxt', + ], - ignore: ['/webGui/images'], + ignore: ['/webGui/images'], - components: [ - { path: "~/components/Brand", prefix: "Brand" }, - { path: "~/components/ConnectSettings", prefix: "ConnectSettings" }, - { path: "~/components/Ui", prefix: "Ui" }, - { path: "~/components/UserProfile", prefix: "Upc" }, - { path: "~/components/UpdateOs", prefix: "UpdateOs" }, - "~/components", - ], + components: [ + { path: '~/components/Brand', prefix: 'Brand' }, + { path: '~/components/ConnectSettings', prefix: 'ConnectSettings' }, + { path: '~/components/Ui', prefix: 'Ui' }, + { path: '~/components/UserProfile', prefix: 'Upc' }, + { path: '~/components/UpdateOs', prefix: 'UpdateOs' }, + '~/components', + ], - // typescript: { - // typeCheck: true - // }, - shadcn: { - prefix: "", - componentDir: "./components/shadcn", - }, + // typescript: { + // typeCheck: true + // }, + shadcn: { + prefix: '', + componentDir: './components/shadcn', + }, - vite: { - plugins: [ - !process.env.VITE_ALLOW_CONSOLE_LOGS && - removeConsole({ - includes: ["log", "warn", "error", "info", "debug"], - }), - ], - build: { - minify: "terser", - terserOptions: { - mangle: { - reserved: terserReservations(charsToReserve), - toplevel: true, - }, - }, - }, - }, + vite: { + plugins: [ + !process.env.VITE_ALLOW_CONSOLE_LOGS && + removeConsole({ + includes: ['log', 'warn', 'error', 'info', 'debug'], + }), + ], + build: { + minify: 'terser', + terserOptions: { + mangle: { + reserved: terserReservations(charsToReserve), + toplevel: true, + }, + }, + }, + }, - customElements: { - entries: [ - { - name: "UnraidComponents", - tags: [ - { - name: "UnraidI18nHost", - path: "@/components/I18nHost.ce", - }, - { - name: "UnraidAuth", - path: "@/components/Auth.ce", - }, - { - name: "UnraidConnectSettings", - path: "@/components/ConnectSettings/ConnectSettings.ce", - }, - { - name: "UnraidDownloadApiLogs", - path: "@/components/DownloadApiLogs.ce", - }, - { - name: "UnraidHeaderOsVersion", - path: "@/components/HeaderOsVersion.ce", - }, - { - name: "UnraidModals", - path: "@/components/Modals.ce", - }, - { - name: "UnraidUserProfile", - path: "@/components/UserProfile.ce", - }, - { - name: "UnraidUpdateOs", - path: "@/components/UpdateOs.ce", - }, - { - name: "UnraidDowngradeOs", - path: "@/components/DowngradeOs.ce", - }, - { - name: "UnraidRegistration", - path: "@/components/Registration.ce", - }, - { - name: "UnraidWanIpCheck", - path: "@/components/WanIpCheck.ce", - }, - { - name: "UnraidWelcomeModal", - path: "@/components/WelcomeModal.ce", - }, - ], - }, - ], - }, + customElements: { + entries: [ + { + name: 'UnraidComponents', + tags: [ + { + name: 'UnraidI18nHost', + path: '@/components/I18nHost.ce', + }, + { + name: 'UnraidAuth', + path: '@/components/Auth.ce', + }, + { + name: 'UnraidConnectSettings', + path: '@/components/ConnectSettings/ConnectSettings.ce', + }, + { + name: 'UnraidDownloadApiLogs', + path: '@/components/DownloadApiLogs.ce', + }, + { + name: 'UnraidHeaderOsVersion', + path: '@/components/HeaderOsVersion.ce', + }, + { + name: 'UnraidModals', + path: '@/components/Modals.ce', + }, + { + name: 'UnraidUserProfile', + path: '@/components/UserProfile.ce', + }, + { + name: 'UnraidUpdateOs', + path: '@/components/UpdateOs.ce', + }, + { + name: 'UnraidDowngradeOs', + path: '@/components/DowngradeOs.ce', + }, + { + name: 'UnraidRegistration', + path: '@/components/Registration.ce', + }, + { + name: 'UnraidWanIpCheck', + path: '@/components/WanIpCheck.ce', + }, + { + name: 'UnraidWelcomeModal', + path: '@/components/WelcomeModal.ce', + }, + { name: 'UnraidSsoButton', + path: '@/components/SsoButton.ce' + }, + ], + }, + ], + }, - compatibilityDate: "2024-12-05" + compatibilityDate: '2024-12-05', }); diff --git a/web/pages/index.vue b/web/pages/index.vue index e3c52db23..05ca38bfa 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -4,6 +4,7 @@ import { BrandButton, BrandLogo } from '@unraid/ui'; import { serverState } from '~/_data/serverState'; import type { SendPayloads } from '~/store/callback'; import AES from 'crypto-js/aes'; +import SsoButtonCe from '~/components/SsoButton.ce.vue'; const { registerEntry } = useCustomElements(); onBeforeMount(() => { @@ -152,6 +153,11 @@ onMounted(() => { > +
+
+

SSO Button Component

+ +
diff --git a/web/pages/webComponents.vue b/web/pages/webComponents.vue index 0b03597cb..4ceaa3f99 100644 --- a/web/pages/webComponents.vue +++ b/web/pages/webComponents.vue @@ -75,7 +75,12 @@ onBeforeMount(() => {

ModalsCe

- + +
+

+ SSOSignInButtonCe +

+ diff --git a/web/store/server.ts b/web/store/server.ts index 8de3ee403..b4dde9b4b 100644 --- a/web/store/server.ts +++ b/web/store/server.ts @@ -155,6 +155,7 @@ export const useServerStore = defineStore("server", () => { return today.isAfter(parsedUpdateExpirationDate, "day"); }); const site = ref(""); + const ssoSubIds = ref(""); const state = ref(); const theme = ref(); watch(theme, (newVal) => { @@ -1208,6 +1209,9 @@ 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.activationCodeData !== "undefined") { const activationCodeStore = useActivationCodeStore(); @@ -1474,6 +1478,7 @@ export const useServerStore = defineStore("server", () => { parsedRegExp, regUpdatesExpired, site, + ssoSubIds, state, theme, updateOsIgnoredReleases, diff --git a/web/types/server.ts b/web/types/server.ts index 70200b8aa..e9c28d392 100644 --- a/web/types/server.ts +++ b/web/types/server.ts @@ -117,6 +117,7 @@ export interface Server { username?: string; wanFQDN?: string; wanIp?: string; + ssoSubIds?: string; } export interface ServerAccountCallbackSendPayload {