mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	fix(web): login error handling (#1322)
This commit is contained in:
		
							parent
							
								
									ba04b753de
								
							
						
					
					
						commit
						5fb3ea465f
					
				
							
								
								
									
										6
									
								
								web/src/app.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								web/src/app.d.ts
									
									
									
									
										vendored
									
									
								
							@ -8,6 +8,12 @@ declare namespace App {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// interface Platform {}
 | 
			
		||||
 | 
			
		||||
	interface Error {
 | 
			
		||||
		message: string;
 | 
			
		||||
		stack?: string;
 | 
			
		||||
		code?: string;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Source: https://stackoverflow.com/questions/63814432/typescript-typing-of-non-standard-window-event-in-svelte
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,15 @@
 | 
			
		||||
import type { Handle } from '@sveltejs/kit';
 | 
			
		||||
import type { Handle, HandleServerError } from '@sveltejs/kit';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
 | 
			
		||||
export const handle: Handle = async ({ event, resolve }) => {
 | 
			
		||||
	return await resolve(event);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const handleError: HandleServerError = async ({ error }) => {
 | 
			
		||||
	const httpError = error as AxiosError;
 | 
			
		||||
	return {
 | 
			
		||||
		message: httpError?.message || 'Hmm, not sure about that. Check the logs or open a ticket?',
 | 
			
		||||
		stack: httpError?.stack,
 | 
			
		||||
		code: httpError.code || '500'
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
 | 
			
		||||
	import { loginPageMessage } from '$lib/constants';
 | 
			
		||||
	import { handleError } from '$lib/utils/handle-error';
 | 
			
		||||
	import { api, oauth, OAuthConfigResponseDto } from '@api';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
 | 
			
		||||
@ -9,7 +10,7 @@
 | 
			
		||||
	let email = '';
 | 
			
		||||
	let password = '';
 | 
			
		||||
	let oauthError: string;
 | 
			
		||||
	let oauthConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
 | 
			
		||||
	let authConfig: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: false };
 | 
			
		||||
	let loading = true;
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
@ -30,17 +31,18 @@
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			const { data } = await oauth.getConfig(window.location);
 | 
			
		||||
			oauthConfig = data;
 | 
			
		||||
			authConfig = data;
 | 
			
		||||
 | 
			
		||||
			const { enabled, url, autoLaunch } = oauthConfig;
 | 
			
		||||
			const { enabled, url, autoLaunch } = authConfig;
 | 
			
		||||
 | 
			
		||||
			if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) {
 | 
			
		||||
				await goto('/auth/login?autoLaunch=0', { replaceState: true });
 | 
			
		||||
				await goto(url);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			console.error('Error [login-form] [oauth.generateConfig]', e);
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			authConfig.passwordLoginEnabled = true;
 | 
			
		||||
			handleError(error, 'Unable to connect!');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		loading = false;
 | 
			
		||||
@ -92,7 +94,7 @@
 | 
			
		||||
			<LoadingSpinner />
 | 
			
		||||
		</div>
 | 
			
		||||
	{:else}
 | 
			
		||||
		{#if oauthConfig.passwordLoginEnabled}
 | 
			
		||||
		{#if authConfig.passwordLoginEnabled}
 | 
			
		||||
			<form on:submit|preventDefault={login} autocomplete="off">
 | 
			
		||||
				<div class="m-4 flex flex-col gap-2">
 | 
			
		||||
					<label class="immich-form-label" for="email">Email</label>
 | 
			
		||||
@ -133,26 +135,26 @@
 | 
			
		||||
			</form>
 | 
			
		||||
		{/if}
 | 
			
		||||
 | 
			
		||||
		{#if oauthConfig.enabled}
 | 
			
		||||
		{#if authConfig.enabled}
 | 
			
		||||
			<div class="flex flex-col gap-4 px-4">
 | 
			
		||||
				{#if oauthConfig.passwordLoginEnabled}
 | 
			
		||||
				{#if authConfig.passwordLoginEnabled}
 | 
			
		||||
					<hr />
 | 
			
		||||
				{/if}
 | 
			
		||||
				{#if oauthError}
 | 
			
		||||
					<p class="text-red-400">{oauthError}</p>
 | 
			
		||||
				{/if}
 | 
			
		||||
				<a href={oauthConfig.url} class="flex w-full">
 | 
			
		||||
				<a href={authConfig.url} class="flex w-full">
 | 
			
		||||
					<button
 | 
			
		||||
						type="button"
 | 
			
		||||
						disabled={loading}
 | 
			
		||||
						class="bg-immich-primary dark:bg-immich-dark-primary dark:text-immich-dark-gray dark:hover:bg-immich-dark-primary/80 hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full font-semibold"
 | 
			
		||||
						>{oauthConfig.buttonText || 'Login with OAuth'}</button
 | 
			
		||||
						>{authConfig.buttonText || 'Login with OAuth'}</button
 | 
			
		||||
					>
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
 | 
			
		||||
		{#if !oauthConfig.enabled && !oauthConfig.passwordLoginEnabled}
 | 
			
		||||
		{#if !authConfig.enabled && !authConfig.passwordLoginEnabled}
 | 
			
		||||
			<p class="text-center dark:text-immich-dark-fg p-4">Login has been disabled.</p>
 | 
			
		||||
		{/if}
 | 
			
		||||
	{/if}
 | 
			
		||||
 | 
			
		||||
@ -90,5 +90,7 @@
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<p class="text-sm pl-[28px] pr-[16px]" data-testid="message">{@html notificationInfo.message}</p>
 | 
			
		||||
	<p class="whitespace-pre text-sm pl-[28px] pr-[16px]" data-testid="message">
 | 
			
		||||
		{@html notificationInfo.message}
 | 
			
		||||
	</p>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,14 @@ import {
 | 
			
		||||
 | 
			
		||||
export function handleError(error: unknown, message: string) {
 | 
			
		||||
	console.error(`[handleError]: ${message}`, error);
 | 
			
		||||
 | 
			
		||||
	let serverMessage = (error as ApiError)?.response?.data?.message;
 | 
			
		||||
	if (serverMessage) {
 | 
			
		||||
		serverMessage = `${String(serverMessage).slice(0, 50)}\n<i>(Immich Server Error)<i>`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notificationController.show({
 | 
			
		||||
		message: (error as ApiError)?.response?.data?.message || message,
 | 
			
		||||
		message: serverMessage || message,
 | 
			
		||||
		type: NotificationType.Error
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,122 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import { page } from '$app/stores';
 | 
			
		||||
	import Message from 'svelte-material-icons/Message.svelte';
 | 
			
		||||
	import PartyPopper from 'svelte-material-icons/PartyPopper.svelte';
 | 
			
		||||
	import CodeTags from 'svelte-material-icons/CodeTags.svelte';
 | 
			
		||||
	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
 | 
			
		||||
	import {
 | 
			
		||||
		notificationController,
 | 
			
		||||
		NotificationType
 | 
			
		||||
	} from '$lib/components/shared-components/notification/notification';
 | 
			
		||||
	import { handleError } from '$lib/utils/handle-error';
 | 
			
		||||
 | 
			
		||||
	const handleCopy = async () => {
 | 
			
		||||
		//
 | 
			
		||||
		const error = $page.error || null;
 | 
			
		||||
		if (!error) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			await navigator.clipboard.writeText(`${error.message} - ${error.code}\n${error.stack}`);
 | 
			
		||||
			notificationController.show({
 | 
			
		||||
				type: NotificationType.Info,
 | 
			
		||||
				message: 'Copied error to clipboard'
 | 
			
		||||
			});
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			handleError(error, 'Unable to copy to clipboard');
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="h-screen w-screen  flex place-items-center place-content-center flex-col">
 | 
			
		||||
	<div class="min-w-[500px] max-w-[95vw]  bg-gray-300 rounded-2xl my-4 p-4">
 | 
			
		||||
		<code class="text-xs text-red-500">Error code {$page.status}</code>
 | 
			
		||||
		<br />
 | 
			
		||||
		<code class="text-sm">
 | 
			
		||||
			{$page.error?.message}
 | 
			
		||||
		</code>
 | 
			
		||||
		<br />
 | 
			
		||||
		<div class="mt-5">
 | 
			
		||||
			<p class="text-sm font-medium">Verbose</p>
 | 
			
		||||
			<pre class="text-xs">{JSON.stringify($page.error)}</pre>
 | 
			
		||||
<div class="h-screen w-screen">
 | 
			
		||||
	<section class="bg-immich-bg dark:bg-immich-dark-bg">
 | 
			
		||||
		<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
 | 
			
		||||
			<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
 | 
			
		||||
				<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" />
 | 
			
		||||
				<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
 | 
			
		||||
					IMMICH
 | 
			
		||||
				</h1>
 | 
			
		||||
			</a>
 | 
			
		||||
		</div>
 | 
			
		||||
	</section>
 | 
			
		||||
 | 
			
		||||
		<a
 | 
			
		||||
			href="https://github.com/immich-app/immich/issues/new/choose"
 | 
			
		||||
			target="_blank"
 | 
			
		||||
			rel="noopener noreferrer"
 | 
			
		||||
		>
 | 
			
		||||
			<button
 | 
			
		||||
				class="px-5 py-2 rounded-lg text-sm mt-6 bg-immich-primary text-white hover:bg-immich-primary/75"
 | 
			
		||||
				>Get help</button
 | 
			
		||||
	<div
 | 
			
		||||
		class="fixed top-0 w-full h-full  bg-black/50 flex place-items-center place-content-center overflow-hidden"
 | 
			
		||||
	>
 | 
			
		||||
		<div>
 | 
			
		||||
			<div
 | 
			
		||||
				class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray shadow-sm w-[500px] max-w-[95vw] rounded-3xl dark:text-immich-dark-fg"
 | 
			
		||||
			>
 | 
			
		||||
		</a>
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class="flex items-center justify-between gap-4 px-4 py-4">
 | 
			
		||||
						<h1 class="text-immich-primary dark:text-immich-dark-primary font-medium">
 | 
			
		||||
							🚨 Error - Something went wrong
 | 
			
		||||
						</h1>
 | 
			
		||||
						<div class="flex justify-end">
 | 
			
		||||
							<button
 | 
			
		||||
								on:click={() => handleCopy()}
 | 
			
		||||
								class="transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-3 py-2 text-white rounded-full shadow-md text-sm"
 | 
			
		||||
							>
 | 
			
		||||
								<ContentCopy size={24} />
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<hr />
 | 
			
		||||
 | 
			
		||||
					<div class="p-4 max-h-[75vh] min-h-[300px] overflow-y-auto immich-scrollbar pb-4 gap-4">
 | 
			
		||||
						<div class="flex flex-col w-full gap-2">
 | 
			
		||||
							<p class="text-red-500">{$page.error?.message} - {$page.error?.code}</p>
 | 
			
		||||
							{#if $page.error?.stack}
 | 
			
		||||
								<label for="stacktrace">Stacktrace</label>
 | 
			
		||||
								<pre id="stacktrace" class="text-xs">{$page.error?.stack || 'No stack'}</pre>
 | 
			
		||||
							{/if}
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<hr />
 | 
			
		||||
 | 
			
		||||
					<div class="flex justify-around place-items-center place-content-center">
 | 
			
		||||
						<!-- href="https://github.com/immich-app/immich/issues/new" -->
 | 
			
		||||
						<a
 | 
			
		||||
							href="https://discord.com/invite/D8JsnBEuKb"
 | 
			
		||||
							target="_blank"
 | 
			
		||||
							rel="noopener noreferrer"
 | 
			
		||||
							class="flex justify-center grow basis-0 p-4"
 | 
			
		||||
						>
 | 
			
		||||
							<button class="flex flex-col gap-2 place-items-center place-content-center">
 | 
			
		||||
								<Message size={24} />
 | 
			
		||||
								<p class="text-sm">Get Help</p>
 | 
			
		||||
							</button>
 | 
			
		||||
						</a>
 | 
			
		||||
 | 
			
		||||
						<a
 | 
			
		||||
							href="https://github.com/immich-app/immich/releases"
 | 
			
		||||
							target="_blank"
 | 
			
		||||
							rel="noopener noreferrer"
 | 
			
		||||
							class="flex justify-center grow basis-0 p-4"
 | 
			
		||||
						>
 | 
			
		||||
							<button class="flex flex-col gap-2 place-items-center place-content-center">
 | 
			
		||||
								<PartyPopper size={24} />
 | 
			
		||||
								<p class="text-sm">Read Changelog</p>
 | 
			
		||||
							</button>
 | 
			
		||||
						</a>
 | 
			
		||||
 | 
			
		||||
						<a
 | 
			
		||||
							href="https://immich.app/docs/guides/docker-help"
 | 
			
		||||
							target="_blank"
 | 
			
		||||
							rel="noopener noreferrer"
 | 
			
		||||
							class="flex justify-center grow basis-0 p-4"
 | 
			
		||||
						>
 | 
			
		||||
							<button class="flex flex-col gap-2 place-items-center place-content-center">
 | 
			
		||||
								<CodeTags size={24} />
 | 
			
		||||
								<p class="text-sm">Check Logs</p>
 | 
			
		||||
							</button>
 | 
			
		||||
						</a>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>{$page.data.meta?.title} - Immich</title>
 | 
			
		||||
	<title>{$page.data.meta?.title || 'Web'} - Immich</title>
 | 
			
		||||
	{#if $page.data.meta}
 | 
			
		||||
		<meta name="description" content={$page.data.meta.description} />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user