mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	fix(web): login error handling (#1322)
This commit is contained in:
		
							
								
								
									
										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} /> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user