mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat: unraid single sign on with account app
This commit is contained in:
@@ -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) {
|
||||
|
||||
18
api/src/core/sso/sso-remove.ts
Normal file
18
api/src/core/sso/sso-remove.ts
Normal 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
62
api/src/core/sso/sso-setup.ts
Executable 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.');
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
22
api/src/unraid-api/cli/sso.command.ts
Normal file
22
api/src/unraid-api/cli/sso.command.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: '',
|
||||
};
|
||||
85
web/components/SsoButton.ce.vue
Normal file
85
web/components/SsoButton.ce.vue
Normal 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>
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -117,6 +117,7 @@ export interface Server {
|
||||
username?: string;
|
||||
wanFQDN?: string;
|
||||
wanIp?: string;
|
||||
ssoSubIds?: string;
|
||||
}
|
||||
|
||||
export interface ServerAccountCallbackSendPayload {
|
||||
|
||||
Reference in New Issue
Block a user