mirror of
https://github.com/unraid/api.git
synced 2026-01-01 22:20:05 -06:00
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Style** - Updated CSS file paths across multiple components to reference assets absolutely, ensuring consistent and reliable styling across the user interface. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
157 lines
4.5 KiB
Vue
157 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
import { BrandButton } from '@unraid/ui';
|
|
import { ACCOUNT } from '~/helpers/urls';
|
|
|
|
export interface Props {
|
|
ssoenabled?: boolean | string;
|
|
ssoEnabled?: boolean;
|
|
}
|
|
const props = defineProps<Props>();
|
|
|
|
type CurrentState = 'loading' | 'idle' | 'error';
|
|
|
|
const currentState = ref<CurrentState>('idle');
|
|
const error = ref<string | null>(null);
|
|
|
|
const isSsoEnabled = computed<boolean>(
|
|
() => props['ssoenabled'] === true || props['ssoenabled'] === 'true' || props.ssoEnabled
|
|
);
|
|
|
|
const getInputFields = (): {
|
|
form: HTMLFormElement;
|
|
passwordField: HTMLInputElement;
|
|
usernameField: HTMLInputElement;
|
|
} => {
|
|
const form = document.querySelector('form[action="/login"]') as HTMLFormElement;
|
|
const passwordField = document.querySelector('input[name=password]') as HTMLInputElement;
|
|
const usernameField = document.querySelector('input[name=username]') as HTMLInputElement;
|
|
if (!form || !passwordField || !usernameField) {
|
|
console.warn('Could not find form, username, or password field');
|
|
}
|
|
return { form, passwordField, usernameField };
|
|
};
|
|
|
|
const enterCallbackTokenIntoField = (token: string) => {
|
|
const { form, passwordField, usernameField } = getInputFields();
|
|
if (!passwordField || !usernameField || !form) {
|
|
console.warn('Could not find form, username, or password field');
|
|
} else {
|
|
usernameField.value = 'root';
|
|
passwordField.value = token;
|
|
// Submit the form
|
|
form.requestSubmit();
|
|
}
|
|
};
|
|
|
|
const getStateToken = (): string | null => {
|
|
const state = sessionStorage.getItem('sso_state');
|
|
return state ?? null;
|
|
};
|
|
|
|
const generateStateToken = (): string => {
|
|
const array = new Uint8Array(32);
|
|
window.crypto.getRandomValues(array);
|
|
const state = Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
sessionStorage.setItem('sso_state', state);
|
|
return state;
|
|
};
|
|
|
|
const disableFormOnSubmit = () => {
|
|
const { form } = getInputFields();
|
|
if (form) {
|
|
form.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
const reEnableFormOnError = () => {
|
|
const { form } = getInputFields();
|
|
if (form) {
|
|
form.style.display = 'block';
|
|
}
|
|
};
|
|
|
|
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) {
|
|
disableFormOnSubmit();
|
|
currentState.value = 'loading';
|
|
const token = await fetch(new URL('/api/oauth2/token', ACCOUNT), {
|
|
method: 'POST',
|
|
body: new URLSearchParams({
|
|
code,
|
|
client_id: 'CONNECT_SERVER_SSO',
|
|
grant_type: 'authorization_code',
|
|
}),
|
|
});
|
|
if (token.ok) {
|
|
const tokenBody = await token.json();
|
|
if (!tokenBody.access_token) {
|
|
throw new Error('Token body did not contain access_token');
|
|
}
|
|
enterCallbackTokenIntoField(tokenBody.access_token);
|
|
if (window.location.search) {
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
}
|
|
} else {
|
|
throw new Error('Failed to fetch token');
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching token', err);
|
|
|
|
currentState.value = 'error';
|
|
error.value = 'Error fetching token';
|
|
reEnableFormOnError();
|
|
}
|
|
});
|
|
|
|
const buttonText = computed<string>(() => {
|
|
switch (currentState.value) {
|
|
case 'loading':
|
|
return 'Signing you in...';
|
|
case 'error':
|
|
return 'Error';
|
|
default:
|
|
return 'Log In With Unraid.net';
|
|
}
|
|
});
|
|
|
|
const navigateToExternalSSOUrl = () => {
|
|
const url = new URL('sso', ACCOUNT);
|
|
const callbackUrlLogin = new URL('login', window.location.origin);
|
|
const state = generateStateToken();
|
|
|
|
url.searchParams.append('callbackUrl', callbackUrlLogin.toString());
|
|
url.searchParams.append('state', state);
|
|
|
|
window.location.href = url.toString();
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<template v-if="isSsoEnabled">
|
|
<div class="w-full flex flex-col gap-1 my-1">
|
|
<p v-if="currentState === 'idle' || currentState === 'error'" class="text-center">or</p>
|
|
<p v-if="currentState === 'error'" class="text-red-500 text-center">{{ error }}</p>
|
|
<BrandButton
|
|
:disabled="currentState === 'loading'"
|
|
variant="outline"
|
|
class="rounded-none uppercase tracking-widest"
|
|
@click="navigateToExternalSSOUrl"
|
|
>{{ buttonText }}</BrandButton
|
|
>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
|
|
<style lang="postcss">
|
|
/* Import unraid-ui globals first */
|
|
@import '@unraid/ui/styles';
|
|
@import '~/assets/main.css';
|
|
</style>
|