mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: auth logout page (#27831)
* feat: auth logout page * feat: skip login if already logged in
This commit is contained in:
@@ -14,12 +14,11 @@
|
|||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import UserAvatar from '../user-avatar.svelte';
|
import UserAvatar from '../user-avatar.svelte';
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
onLogout: () => void;
|
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
let { onLogout, onClose = () => {} }: Props = $props();
|
let { onClose }: Props = $props();
|
||||||
|
|
||||||
let info: ServerAboutResponseDto | undefined = $state();
|
let info: ServerAboutResponseDto | undefined = $state();
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
shape="round"
|
shape="round"
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
onClose();
|
onClose?.();
|
||||||
await modalManager.show(AvatarEditModal);
|
await modalManager.show(AvatarEditModal);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -99,7 +98,7 @@
|
|||||||
<div class="mb-4 flex flex-col">
|
<div class="mb-4 flex flex-col">
|
||||||
<Button
|
<Button
|
||||||
class="m-1 mx-4 rounded-none rounded-b-3xl bg-white p-3 dark:bg-immich-dark-primary/10"
|
class="m-1 mx-4 rounded-none rounded-b-3xl bg-white p-3 dark:bg-immich-dark-primary/10"
|
||||||
onclick={onLogout}
|
href={Route.logout()}
|
||||||
leadingIcon={mdiLogout}
|
leadingIcon={mdiLogout}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="secondary">{$t('sign_out')}</Button
|
color="secondary">{$t('sign_out')}</Button
|
||||||
@@ -109,7 +108,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="text-center mt-4 underline text-xs text-primary"
|
class="text-center mt-4 underline text-xs text-primary"
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
onClose();
|
onClose?.();
|
||||||
if (info) {
|
if (info) {
|
||||||
await modalManager.show(HelpAndFeedbackModal, { info });
|
await modalManager.show(HelpAndFeedbackModal, { info });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,10 +178,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if shouldShowAccountInfoPanel}
|
{#if shouldShowAccountInfoPanel}
|
||||||
<AccountInfoPanel
|
<AccountInfoPanel onClose={() => (shouldShowAccountInfoPanel = false)} />
|
||||||
onLogout={() => authManager.logout()}
|
|
||||||
onClose={() => (shouldShowAccountInfoPanel = false)}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ class AuthManager {
|
|||||||
return this.#preferences;
|
return this.#preferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
eventManager.on({
|
||||||
|
SessionDelete: () => goto(Route.logout()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
if (authManager.authenticated) {
|
if (authManager.authenticated) {
|
||||||
return;
|
return;
|
||||||
@@ -84,30 +90,26 @@ class AuthManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
let redirectUri;
|
let redirectUri = Route.login();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await logout();
|
const response = await logout();
|
||||||
if (response.redirectUri) {
|
if (response.redirectUri) {
|
||||||
redirectUri = response.redirectUri;
|
redirectUri = response.redirectUri;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.log('Error logging out:', error);
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri = redirectUri ?? Route.login();
|
if (redirectUri.startsWith('/')) {
|
||||||
|
|
||||||
try {
|
|
||||||
if (redirectUri.startsWith('/')) {
|
|
||||||
await goto(redirectUri);
|
|
||||||
} else {
|
|
||||||
globalThis.location.href = redirectUri;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.isPurchased = false;
|
this.isPurchased = false;
|
||||||
|
|
||||||
this.reset();
|
this.reset();
|
||||||
eventManager.emit('AuthLogout');
|
eventManager.emit('AuthLogout');
|
||||||
|
|
||||||
|
await goto(redirectUri);
|
||||||
|
} else {
|
||||||
|
globalThis.location.href = redirectUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export type Events = {
|
|||||||
UserAdminDeleted: [{ id: string }];
|
UserAdminDeleted: [{ id: string }];
|
||||||
|
|
||||||
SessionLocked: [];
|
SessionLocked: [];
|
||||||
|
SessionDelete: [];
|
||||||
|
|
||||||
SystemConfigUpdate: [SystemConfigDto];
|
SystemConfigUpdate: [SystemConfigDto];
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const Docs = {
|
|||||||
export const Route = {
|
export const Route = {
|
||||||
// auth
|
// auth
|
||||||
login: (params?: { continue?: string; autoLaunch?: 0 | 1 }) => '/auth/login' + asQueryString(params),
|
login: (params?: { continue?: string; autoLaunch?: 0 | 1 }) => '/auth/login' + asQueryString(params),
|
||||||
|
logout: (params?: { continue?: string }) => '/auth/logout' + asQueryString(params),
|
||||||
register: () => '/auth/register',
|
register: () => '/auth/register',
|
||||||
changePassword: () => '/auth/change-password',
|
changePassword: () => '/auth/change-password',
|
||||||
onboarding: (params?: { step?: string }) => '/auth/onboarding' + asQueryString(params),
|
onboarding: (params?: { step?: string }) => '/auth/onboarding' + asQueryString(params),
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ websocket
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event))
|
.on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event))
|
||||||
.on('on_session_delete', () => authManager.logout())
|
.on('on_session_delete', () => eventManager.emit('SessionDelete'))
|
||||||
.on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id }))
|
.on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id }))
|
||||||
.on('on_asset_update', (asset) => eventManager.emit('AssetUpdate', asset))
|
.on('on_asset_update', (asset) => eventManager.emit('AssetUpdate', asset))
|
||||||
.on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id }))
|
.on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id }))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
import { initLanguage } from '$lib/utils';
|
import { initLanguage } from '$lib/utils';
|
||||||
@@ -13,6 +14,7 @@ async function _init(fetch: Fetch) {
|
|||||||
defaults.fetch = fetch;
|
defaults.fetch = fetch;
|
||||||
await initLanguage();
|
await initLanguage();
|
||||||
await serverConfigManager.init();
|
await serverConfigManager.init();
|
||||||
|
await authManager.load();
|
||||||
|
|
||||||
if (!serverConfigManager.value.maintenanceMode) {
|
if (!serverConfigManager.value.maintenanceMode) {
|
||||||
await featureFlagsManager.init();
|
await featureFlagsManager.init();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { updateMyUser } from '@immich/sdk';
|
import { updateMyUser } from '@immich/sdk';
|
||||||
import { Alert, Button, Field, HelperText, PasswordInput, Stack, Text } from '@immich/ui';
|
import { Alert, Button, Field, HelperText, PasswordInput, Stack, Text } from '@immich/ui';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await updateMyUser({ userUpdateMeDto: { password } });
|
await updateMyUser({ userUpdateMeDto: { password } });
|
||||||
await authManager.logout();
|
await goto(Route.logout());
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
import { Route } from '$lib/route';
|
import { Route } from '$lib/route';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
@@ -7,6 +8,11 @@ import type { PageLoad } from './$types';
|
|||||||
export const load = (async ({ parent, url }) => {
|
export const load = (async ({ parent, url }) => {
|
||||||
await parent();
|
await parent();
|
||||||
|
|
||||||
|
const continueUrl = url.searchParams.get('continue') || Route.photos();
|
||||||
|
if (authManager.authenticated) {
|
||||||
|
redirect(307, continueUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (!serverConfigManager.value.isInitialized) {
|
if (!serverConfigManager.value.isInitialized) {
|
||||||
// Admin not registered
|
// Admin not registered
|
||||||
redirect(307, Route.register());
|
redirect(307, Route.register());
|
||||||
@@ -17,6 +23,6 @@ export const load = (async ({ parent, url }) => {
|
|||||||
meta: {
|
meta: {
|
||||||
title: $t('login'),
|
title: $t('login'),
|
||||||
},
|
},
|
||||||
continueUrl: url.searchParams.get('continue') || Route.photos(),
|
continueUrl,
|
||||||
};
|
};
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
|
import { LoadingSpinner } from '@immich/ui';
|
||||||
|
|
||||||
|
void authManager.logout();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="h-screen w-screen overflow-hidden">
|
||||||
|
<div class="m-auto">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user