feat(web): combine auth settings (#9427)

This commit is contained in:
Jason Rasmussen 2024-05-13 16:40:33 -04:00 committed by GitHub
parent 844f5a16a1
commit a05c990718
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 256 additions and 328 deletions

View File

@ -0,0 +1,242 @@
<script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
import SettingInputField, {
SettingInputFieldType,
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { type SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import type { SettingsEventType } from '../admin-settings';
export let savedConfig: SystemConfigDto;
export let defaultConfig: SystemConfigDto;
export let config: SystemConfigDto; // this is the config that is being edited
export let disabled = false;
const dispatch = createEventDispatcher<SettingsEventType>();
let isConfirmOpen = false;
const handleToggleOverride = () => {
// click runs before bind
const previouslyEnabled = config.oauth.mobileOverrideEnabled;
if (!previouslyEnabled && !config.oauth.mobileRedirectUri) {
config.oauth.mobileRedirectUri = window.location.origin + '/api/oauth/mobile-redirect';
}
};
const handleSave = (skipConfirm: boolean) => {
const allMethodsDisabled = !config.oauth.enabled && !config.passwordLogin.enabled;
if (allMethodsDisabled && !skipConfirm) {
isConfirmOpen = true;
return;
}
isConfirmOpen = false;
dispatch('save', { passwordLogin: config.passwordLogin, oauth: config.oauth });
};
</script>
{#if isConfirmOpen}
<ConfirmDialogue
id="disable-login-modal"
title="Disable login"
onClose={() => (isConfirmOpen = false)}
onConfirm={() => handleSave(true)}
>
<svelte:fragment slot="prompt">
<div class="flex flex-col gap-4">
<p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
<p>
To re-enable, use a
<a
href="https://immich.app/docs/administration/server-commands"
rel="noreferrer"
target="_blank"
class="underline"
>
Server Command</a
>.
</p>
</div>
</svelte:fragment>
</ConfirmDialogue>
{/if}
<div>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingAccordion key="oauth" title="OAuth" subtitle="Manage OAuth login settings">
<div class="ml-4 mt-4 flex flex-col gap-4">
<p class="text-sm dark:text-immich-dark-fg">
For more details about this feature, refer to the <a
href="https://immich.app/docs/administration/oauth"
class="underline"
target="_blank"
rel="noreferrer">docs</a
>.
</p>
<SettingSwitch
id="login-with-oauth"
{disabled}
title="ENABLE"
subtitle="Login with OAuth"
bind:checked={config.oauth.enabled}
/>
{#if config.oauth.enabled}
<hr />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="ISSUER URL"
bind:value={config.oauth.issuerUrl}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.issuerUrl == savedConfig.oauth.issuerUrl)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="CLIENT ID"
bind:value={config.oauth.clientId}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.clientId == savedConfig.oauth.clientId)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="CLIENT SECRET"
bind:value={config.oauth.clientSecret}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.clientSecret == savedConfig.oauth.clientSecret)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="SCOPE"
bind:value={config.oauth.scope}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.scope == savedConfig.oauth.scope)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="SIGNING ALGORITHM"
bind:value={config.oauth.signingAlgorithm}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.signingAlgorithm == savedConfig.oauth.signingAlgorithm)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE LABEL CLAIM"
desc="Automatically set the user's storage label to the value of this claim."
bind:value={config.oauth.storageLabelClaim}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.storageLabelClaim == savedConfig.oauth.storageLabelClaim)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE QUOTA CLAIM"
desc="Automatically set the user's storage quota to the value of this claim."
bind:value={config.oauth.storageQuotaClaim}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.storageQuotaClaim == savedConfig.oauth.storageQuotaClaim)}
/>
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="DEFAULT STORAGE QUOTA (GiB)"
desc="Quota in GiB to be used when no claim is provided (Enter 0 for unlimited quota)."
bind:value={config.oauth.defaultStorageQuota}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.defaultStorageQuota == savedConfig.oauth.defaultStorageQuota)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="BUTTON TEXT"
bind:value={config.oauth.buttonText}
required={false}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.buttonText == savedConfig.oauth.buttonText)}
/>
<SettingSwitch
id="auto-register-new-users"
title="AUTO REGISTER"
subtitle="Automatically register new users after signing in with OAuth"
bind:checked={config.oauth.autoRegister}
disabled={disabled || !config.oauth.enabled}
/>
<SettingSwitch
id="auto-launch-oauth"
title="AUTO LAUNCH"
subtitle="Start the OAuth login flow automatically upon navigating to the login page"
disabled={disabled || !config.oauth.enabled}
bind:checked={config.oauth.autoLaunch}
/>
<SettingSwitch
id="mobile-redirect-uri-override"
title="MOBILE REDIRECT URI OVERRIDE"
subtitle="Enable when 'app.immich:/' is an invalid redirect URI."
disabled={disabled || !config.oauth.enabled}
on:click={() => handleToggleOverride()}
bind:checked={config.oauth.mobileOverrideEnabled}
/>
{#if config.oauth.mobileOverrideEnabled}
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="MOBILE REDIRECT URI"
bind:value={config.oauth.mobileRedirectUri}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.mobileRedirectUri == savedConfig.oauth.mobileRedirectUri)}
/>
{/if}
{/if}
</div>
</SettingAccordion>
<SettingAccordion key="password" title="Password" subtitle="Manage password login settings">
<div class="ml-4 mt-4 flex flex-col gap-4">
<div class="ml-4 mt-4 flex flex-col">
<SettingSwitch
id="enable-password-login"
title="ENABLED"
{disabled}
subtitle="Login with email and password"
bind:checked={config.passwordLogin.enabled}
/>
</div>
</div>
</SettingAccordion>
<SettingButtonsRow
showResetToDefault={!isEqual(savedConfig.passwordLogin, defaultConfig.passwordLogin) ||
!isEqual(savedConfig.oauth, defaultConfig.oauth)}
{disabled}
on:reset={({ detail }) => dispatch('reset', { ...detail, configKeys: ['passwordLogin', 'oauth'] })}
on:save={() => handleSave(false)}
/>
</div>
</form>
</div>
</div>

View File

@ -1,25 +0,0 @@
<script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
export let onCancel: () => void;
export let onConfirm: () => void;
</script>
<ConfirmDialogue id="disable-login-modal" title="Disable login" onClose={onCancel} {onConfirm}>
<svelte:fragment slot="prompt">
<div class="flex flex-col gap-4">
<p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
<p>
To re-enable, use a
<a
href="https://immich.app/docs/administration/server-commands"
rel="noreferrer"
target="_blank"
class="underline"
>
Server Command</a
>.
</p>
</div>
</svelte:fragment>
</ConfirmDialogue>

View File

@ -1,213 +0,0 @@
<script lang="ts">
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import type { SettingsEventType } from '../admin-settings';
import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
import SettingInputField, {
SettingInputFieldType,
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
export let savedConfig: SystemConfigDto;
export let defaultConfig: SystemConfigDto;
export let config: SystemConfigDto; // this is the config that is being edited
export let disabled = false;
const dispatch = createEventDispatcher<SettingsEventType>();
const handleToggleOverride = () => {
// click runs before bind
const previouslyEnabled = config.oauth.mobileOverrideEnabled;
if (!previouslyEnabled && !config.oauth.mobileRedirectUri) {
config.oauth.mobileRedirectUri = window.location.origin + '/api/oauth/mobile-redirect';
}
};
let isConfirmOpen = false;
let handleConfirm: (value: boolean) => void;
const openConfirmModal = () => {
return new Promise((resolve) => {
handleConfirm = (value: boolean) => {
isConfirmOpen = false;
resolve(value);
};
isConfirmOpen = true;
});
};
const handleSave = async () => {
if (!savedConfig.passwordLogin.enabled && savedConfig.oauth.enabled && !config.oauth.enabled) {
const confirmed = await openConfirmModal();
if (!confirmed) {
return;
}
}
if (!config.oauth.mobileOverrideEnabled) {
config.oauth.mobileRedirectUri = '';
}
dispatch('save', { oauth: config.oauth });
};
</script>
{#if isConfirmOpen}
<ConfirmDisableLogin onCancel={() => handleConfirm(false)} onConfirm={() => handleConfirm(true)} />
{/if}
<div class="mt-2">
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault class="mx-4 flex flex-col gap-4 py-4">
<p class="text-sm dark:text-immich-dark-fg">
For more details about this feature, refer to the <a
href="https://immich.app/docs/administration/oauth"
class="underline"
target="_blank"
rel="noreferrer">docs</a
>.
</p>
<SettingSwitch
id="login-with-oauth"
{disabled}
title="ENABLE"
subtitle="Login with OAuth"
bind:checked={config.oauth.enabled}
/>
{#if config.oauth.enabled}
<hr />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="ISSUER URL"
bind:value={config.oauth.issuerUrl}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.issuerUrl == savedConfig.oauth.issuerUrl)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="CLIENT ID"
bind:value={config.oauth.clientId}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.clientId == savedConfig.oauth.clientId)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="CLIENT SECRET"
bind:value={config.oauth.clientSecret}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.clientSecret == savedConfig.oauth.clientSecret)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="SCOPE"
bind:value={config.oauth.scope}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.scope == savedConfig.oauth.scope)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="SIGNING ALGORITHM"
bind:value={config.oauth.signingAlgorithm}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.signingAlgorithm == savedConfig.oauth.signingAlgorithm)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE LABEL CLAIM"
desc="Automatically set the user's storage label to the value of this claim."
bind:value={config.oauth.storageLabelClaim}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.storageLabelClaim == savedConfig.oauth.storageLabelClaim)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE QUOTA CLAIM"
desc="Automatically set the user's storage quota to the value of this claim."
bind:value={config.oauth.storageQuotaClaim}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.storageQuotaClaim == savedConfig.oauth.storageQuotaClaim)}
/>
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="DEFAULT STORAGE QUOTA (GiB)"
desc="Quota in GiB to be used when no claim is provided (Enter 0 for unlimited quota)."
bind:value={config.oauth.defaultStorageQuota}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.defaultStorageQuota == savedConfig.oauth.defaultStorageQuota)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="BUTTON TEXT"
bind:value={config.oauth.buttonText}
required={false}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.buttonText == savedConfig.oauth.buttonText)}
/>
<SettingSwitch
id="auto-register-new-users"
title="AUTO REGISTER"
subtitle="Automatically register new users after signing in with OAuth"
bind:checked={config.oauth.autoRegister}
disabled={disabled || !config.oauth.enabled}
/>
<SettingSwitch
id="auto-launch-oauth"
title="AUTO LAUNCH"
subtitle="Start the OAuth login flow automatically upon navigating to the login page"
disabled={disabled || !config.oauth.enabled}
bind:checked={config.oauth.autoLaunch}
/>
<SettingSwitch
id="mobile-redirect-uri-override"
title="MOBILE REDIRECT URI OVERRIDE"
subtitle="Enable when 'app.immich:/' is an invalid redirect URI."
disabled={disabled || !config.oauth.enabled}
on:click={() => handleToggleOverride()}
bind:checked={config.oauth.mobileOverrideEnabled}
/>
{#if config.oauth.mobileOverrideEnabled}
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="MOBILE REDIRECT URI"
bind:value={config.oauth.mobileRedirectUri}
required={true}
disabled={disabled || !config.oauth.enabled}
isEdited={!(config.oauth.mobileRedirectUri == savedConfig.oauth.mobileRedirectUri)}
/>
{/if}
{/if}
<SettingButtonsRow
on:reset={({ detail }) => dispatch('reset', { ...detail, configKeys: ['oauth'] })}
on:save={() => handleSave()}
showResetToDefault={!isEqual(savedConfig.oauth, defaultConfig.oauth)}
{disabled}
/>
</form>
</div>
</div>

View File

@ -1,68 +0,0 @@
<script lang="ts">
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import type { SettingsEventType } from '../admin-settings';
import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
export let savedConfig: SystemConfigDto;
export let defaultConfig: SystemConfigDto;
export let config: SystemConfigDto; // this is the config that is being edited
export let disabled = false;
const dispatch = createEventDispatcher<SettingsEventType>();
let isConfirmOpen = false;
let handleConfirm: (value: boolean) => void;
const openConfirmModal = () => {
return new Promise((resolve) => {
handleConfirm = (value: boolean) => {
isConfirmOpen = false;
resolve(value);
};
isConfirmOpen = true;
});
};
async function handleSave() {
if (!savedConfig.oauth.enabled && savedConfig.passwordLogin.enabled && !config.passwordLogin.enabled) {
const confirmed = await openConfirmModal();
if (!confirmed) {
return;
}
}
dispatch('save', { passwordLogin: config.passwordLogin });
}
</script>
{#if isConfirmOpen}
<ConfirmDisableLogin onCancel={() => handleConfirm(false)} onConfirm={() => handleConfirm(true)} />
{/if}
<div>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col">
<SettingSwitch
id="enable-password-login"
title="ENABLED"
{disabled}
subtitle="Login with email and password"
bind:checked={config.passwordLogin.enabled}
/>
<SettingButtonsRow
on:reset={({ detail }) => dispatch('reset', { ...detail, configKeys: ['passwordLogin'] })}
on:save={() => handleSave()}
showResetToDefault={!isEqual(savedConfig.passwordLogin, defaultConfig.passwordLogin)}
{disabled}
/>
</div>
</form>
</div>
</div>

View File

@ -1,34 +1,33 @@
<script lang="ts">
import AdminSettings from '$lib/components/admin-page/settings/admin-settings.svelte';
import AuthSettings from '$lib/components/admin-page/settings/auth/auth-settings.svelte';
import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
import ImageSettings from '$lib/components/admin-page/settings/image/image-settings.svelte';
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
import LibrarySettings from '$lib/components/admin-page/settings/library-settings/library-settings.svelte';
import LoggingSettings from '$lib/components/admin-page/settings/logging-settings/logging-settings.svelte';
import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte';
import MapSettings from '$lib/components/admin-page/settings/map-settings/map-settings.svelte';
import NewVersionCheckSettings from '$lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte';
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
import ServerSettings from '$lib/components/admin-page/settings/server/server-settings.svelte';
import NotificationSettings from '$lib/components/admin-page/settings/notification-settings/notification-settings.svelte';
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import ServerSettings from '$lib/components/admin-page/settings/server/server-settings.svelte';
import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte';
import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte';
import ImageSettings from '$lib/components/admin-page/settings/image/image-settings.svelte';
import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte';
import UserSettings from '$lib/components/admin-page/settings/user-settings/user-settings.svelte';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import SettingAccordionState from '$lib/components/shared-components/settings/setting-accordion-state.svelte';
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import { QueryParameter } from '$lib/constants';
import { downloadManager } from '$lib/stores/download';
import { featureFlags } from '$lib/stores/server-config.store';
import { copyToClipboard } from '$lib/utils';
import { downloadBlob } from '$lib/utils/asset-utils';
import type { SystemConfigDto } from '@immich/sdk';
import { mdiAlert, mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js';
import type { PageData } from './$types';
import SettingAccordionState from '$lib/components/shared-components/settings/setting-accordion-state.svelte';
import { QueryParameter } from '$lib/constants';
import type { SystemConfigDto } from '@immich/sdk';
export let data: PageData;
@ -36,13 +35,12 @@
let handleSave: (update: Partial<SystemConfigDto>) => Promise<void>;
type Settings =
| typeof AuthSettings
| typeof JobSettings
| typeof LibrarySettings
| typeof LoggingSettings
| typeof MachineLearningSettings
| typeof MapSettings
| typeof OAuthSettings
| typeof PasswordLoginSettings
| typeof ServerSettings
| typeof StorageTemplateSettings
| typeof ThemeSettings
@ -82,6 +80,12 @@
subtitle: string;
key: string;
}> = [
{
item: AuthSettings,
title: 'Authentication Settings',
subtitle: 'Manage password, OAuth, and other authentication settings',
key: 'image',
},
{
item: ImageSettings,
title: 'Image Settings',
@ -124,18 +128,6 @@
subtitle: 'Manage notification settings, including email',
key: 'notifications',
},
{
item: OAuthSettings,
title: 'OAuth Authentication',
subtitle: 'Manage the login with OAuth settings',
key: 'oauth',
},
{
item: PasswordLoginSettings,
title: 'Password Authentication',
subtitle: 'Manage the login with password settings',
key: 'password',
},
{
item: ServerSettings,
title: 'Server Settings',