feat: unraid single sign on with account app

This commit is contained in:
Eli Bosley
2025-01-20 12:04:26 -05:00
parent 2d3892deb8
commit 2b25537e26
16 changed files with 422 additions and 175 deletions

View File

@@ -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) {

View File

@@ -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');
};

62
api/src/core/sso/sso-setup.ts Executable file
View File

@@ -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 = '<?php include "$docroot/plugins/dynamix.my.servers/include/sso-login.php"; ?>';
// 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 (<?php)
fileContent = fileContent.replace(/<\?php\s*(\r?\n|\r)*/, `<?php\n\n${newFunction}\n`);
// Replace the old function call with the new function name
const functionCallPattern = /!verifyUsernamePassword\(\$username, \$password\)/g;
fileContent = fileContent.replace(
functionCallPattern,
'!verifyUsernamePasswordAndSSO($username, $password)'
);
// Inject the PHP include tag before the closing </body> tag
fileContent = fileContent.replace(/<\/body>/i, `${tagToInject}\n</body>`);
// Write the updated content back to the file
await writeFile(path, fileContent);
console.log('Function replaced successfully.');
};

View File

@@ -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);

View File

@@ -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 {}

View File

@@ -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<void> {
this.logger.info('Please provide a subcommand or use --help for more information');
}
}

View File

@@ -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: '<token>',
})
@@ -33,11 +34,13 @@ export class ValidateTokenCommand extends CommandRunner {
async run(passedParams: string[]): Promise<void> {
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;

View File

@@ -0,0 +1,22 @@
<?
/**
* Caveats to get the SSO login button to display
*
* /usr/local/emhttp/auth-request.php must be updated to include the exact URLs of anything that is being loaded.
* Otherwise, the request for the asset will be blocked and redirected to /login.
*
* The modification of these files should be done via the plugin's install script.
*/
require_once("$docroot/plugins/dynamix.my.servers/include/state.php");
require_once("$docroot/plugins/dynamix.my.servers/include/web-components-extractor.php");
$serverState = new ServerState();
$wcExtractor = new WebComponentsExtractor();
echo $wcExtractor->getScriptTagHtml();
?>
<unraid-i18n-host>
<unraid-sso-button server="<?= $serverState->getServerStateJsonForHtmlAttr() ?>"></unraid-sso-button>
</unraid-i18n-host>

View File

@@ -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) {

View File

@@ -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<ServerUpdateOsResponse | undefined> => {
@@ -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: '',
};

View File

@@ -0,0 +1,85 @@
<script setup lang="ts">
import Button from '~/components/Brand/Button.vue';
import { ACCOUNT } from '~/helpers/urls';
import { useServerStore } from '~/store/server';
import type { Server } from '~/types/server';
export interface Props {
server?: Server | string;
}
const props = defineProps<Props>();
const serverStore = useServerStore();
const { ssoSubIds } = storeToRefs(serverStore);
onBeforeMount(() => {
if (!props.server) {
throw new Error('Server data not present');
}
console.log('props.server', props.server);
if (typeof props.server === 'object') {
// Handles the testing dev Vue component
serverStore.setServer(props.server);
} else if (typeof props.server === 'string') {
// Handle web component
const parsedServerProp = JSON.parse(props.server);
serverStore.setServer(parsedServerProp);
}
});
const queryParams = useUrlSearchParams<{ token: string }>();
const enterCallbackTokenIntoField = (token: string) => {
const passwordField = document.querySelector('input[name=password]') as HTMLInputElement;
const usernameField = document.querySelector('input[name=username]') as HTMLInputElement;
const form = document.querySelector('form[action="/login"]') as HTMLFormElement;
if (!passwordField || !usernameField || !form) {
console.warn('Could not find form, username, or password field');
} else {
usernameField.value = 'root';
passwordField.value = 'password';
// Submit the form
form.requestSubmit();
}
};
const search = new URLSearchParams(window.location.search);
const token = search.get('token') ?? '';
if (token) {
enterCallbackTokenIntoField(token);
// Clear the token from the URL
window.history.replaceState({}, document.title, window.location.pathname);
window.location.search = '';
}
watch(queryParams, (newVal) => {
console.log('newVal', newVal);
if (newVal?.token) {
enterCallbackTokenIntoField(newVal.token);
// Clear the token from the URL
window.history.replaceState({}, document.title, window.location.pathname);
window.location.search = '';
}
});
const externalSSOUrl = computed(() => {
const url = new URL('sso', ACCOUNT);
url.searchParams.append('uids', ssoSubIds.value);
url.searchParams.append('callbackUrl', window.location.href);
return url.toString();
});
</script>
<template>
<template v-if="ssoSubIds">
<Button target="_blank" :href="externalSSOUrl">Sign In With Unraid.net Account</Button>
</template>
</template>
<style lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

View File

@@ -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',
});

View File

@@ -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(() => {
>
</template>
</div>
<div class="bg-background">
<hr class="border-black dark:border-white" />
<h2 class="text-xl font-semibold font-mono">SSO Button Component</h2>
<SsoButtonCe :server="serverState" />
</div>
</div>
</client-only>
</div>

View File

@@ -75,7 +75,12 @@ onBeforeMount(() => {
<h3 class="text-lg font-semibold font-mono">
ModalsCe
</h3>
<unraid-modals />
<!-- uncomment to test modals <unraid-modals />-->
<hr class="border-black dark:border-white">
<h3 class="text-lg font-semibold font-mono">
SSOSignInButtonCe
</h3>
<unraid-sso-button :server="JSON.stringify(serverState)" />
</unraid-i18n-host>
</client-only>
</template>

View File

@@ -155,6 +155,7 @@ export const useServerStore = defineStore("server", () => {
return today.isAfter(parsedUpdateExpirationDate, "day");
});
const site = ref<string>("");
const ssoSubIds = ref<string>("");
const state = ref<ServerState>();
const theme = ref<Theme>();
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,

View File

@@ -117,6 +117,7 @@ export interface Server {
username?: string;
wanFQDN?: string;
wanIp?: string;
ssoSubIds?: string;
}
export interface ServerAccountCallbackSendPayload {