mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
refactor: authentication on public routes (#6765)
* refactor: authentication on public routes * fix: remove public user * pr feedback * pr feedback * pr feedback * pr feedback * remove unused method * fix: tests * fix: useless methods * fix: tests * pr feedback * pr feedback * chore: cleanup --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
45ea0bb689
commit
f1e4fdf175
16 changed files with 92 additions and 75 deletions
|
@ -153,9 +153,10 @@ describe(`${AuthController.name} (e2e)`, () => {
|
||||||
expect(token).toBeDefined();
|
expect(token).toBeDefined();
|
||||||
|
|
||||||
const cookies = headers['set-cookie'];
|
const cookies = headers['set-cookie'];
|
||||||
expect(cookies).toHaveLength(2);
|
expect(cookies).toHaveLength(3);
|
||||||
expect(cookies[0]).toEqual(`immich_access_token=${token}; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;`);
|
expect(cookies[0]).toEqual(`immich_access_token=${token}; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;`);
|
||||||
expect(cookies[1]).toEqual('immich_auth_type=password; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;');
|
expect(cookies[1]).toEqual('immich_auth_type=password; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;');
|
||||||
|
expect(cookies[2]).toEqual('immich_is_authenticated=true; Path=/; Max-Age=34560000; SameSite=Lax;');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export const MOBILE_REDIRECT = 'app.immich:/';
|
export const MOBILE_REDIRECT = 'app.immich:/';
|
||||||
export const LOGIN_URL = '/auth/login?autoLaunch=0';
|
export const LOGIN_URL = '/auth/login?autoLaunch=0';
|
||||||
export const IMMICH_ACCESS_COOKIE = 'immich_access_token';
|
export const IMMICH_ACCESS_COOKIE = 'immich_access_token';
|
||||||
|
export const IMMICH_IS_AUTHENTICATED = 'immich_is_authenticated';
|
||||||
export const IMMICH_AUTH_TYPE_COOKIE = 'immich_auth_type';
|
export const IMMICH_AUTH_TYPE_COOKIE = 'immich_auth_type';
|
||||||
export const IMMICH_API_KEY_NAME = 'api_key';
|
export const IMMICH_API_KEY_NAME = 'api_key';
|
||||||
export const IMMICH_API_KEY_HEADER = 'x-api-key';
|
export const IMMICH_API_KEY_HEADER = 'x-api-key';
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
IMMICH_ACCESS_COOKIE,
|
IMMICH_ACCESS_COOKIE,
|
||||||
IMMICH_API_KEY_HEADER,
|
IMMICH_API_KEY_HEADER,
|
||||||
IMMICH_AUTH_TYPE_COOKIE,
|
IMMICH_AUTH_TYPE_COOKIE,
|
||||||
|
IMMICH_IS_AUTHENTICATED,
|
||||||
LOGIN_URL,
|
LOGIN_URL,
|
||||||
MOBILE_REDIRECT,
|
MOBILE_REDIRECT,
|
||||||
} from './auth.constant';
|
} from './auth.constant';
|
||||||
|
@ -429,14 +430,17 @@ export class AuthService {
|
||||||
|
|
||||||
let authTypeCookie = '';
|
let authTypeCookie = '';
|
||||||
let accessTokenCookie = '';
|
let accessTokenCookie = '';
|
||||||
|
let isAuthenticatedCookie = '';
|
||||||
|
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
|
isAuthenticatedCookie = `${IMMICH_IS_AUTHENTICATED}=true; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
} else {
|
} else {
|
||||||
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
|
isAuthenticatedCookie = `${IMMICH_IS_AUTHENTICATED}=true; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
}
|
}
|
||||||
return [accessTokenCookie, authTypeCookie];
|
return [accessTokenCookie, authTypeCookie, isAuthenticatedCookie];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
ChangePasswordDto,
|
ChangePasswordDto,
|
||||||
IMMICH_ACCESS_COOKIE,
|
IMMICH_ACCESS_COOKIE,
|
||||||
IMMICH_AUTH_TYPE_COOKIE,
|
IMMICH_AUTH_TYPE_COOKIE,
|
||||||
|
IMMICH_IS_AUTHENTICATED,
|
||||||
LoginCredentialDto,
|
LoginCredentialDto,
|
||||||
LoginDetails,
|
LoginDetails,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
|
@ -84,6 +85,7 @@ export class AuthController {
|
||||||
): Promise<LogoutResponseDto> {
|
): Promise<LogoutResponseDto> {
|
||||||
res.clearCookie(IMMICH_ACCESS_COOKIE);
|
res.clearCookie(IMMICH_ACCESS_COOKIE);
|
||||||
res.clearCookie(IMMICH_AUTH_TYPE_COOKIE);
|
res.clearCookie(IMMICH_AUTH_TYPE_COOKIE);
|
||||||
|
res.clearCookie(IMMICH_IS_AUTHENTICATED);
|
||||||
|
|
||||||
return this.service.logout(auth, (request.cookies || {})[IMMICH_AUTH_TYPE_COOKIE]);
|
return this.service.logout(auth, (request.cookies || {})[IMMICH_AUTH_TYPE_COOKIE]);
|
||||||
}
|
}
|
||||||
|
|
3
server/test/fixtures/auth.stub.ts
vendored
3
server/test/fixtures/auth.stub.ts
vendored
|
@ -145,6 +145,7 @@ export const loginResponseStub = {
|
||||||
cookie: [
|
cookie: [
|
||||||
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
'immich_auth_type=oauth; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
'immich_auth_type=oauth; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
|
'immich_is_authenticated=true; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
user1password: {
|
user1password: {
|
||||||
|
@ -160,6 +161,7 @@ export const loginResponseStub = {
|
||||||
cookie: [
|
cookie: [
|
||||||
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
'immich_auth_type=password; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
'immich_auth_type=password; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
|
'immich_is_authenticated=true; Secure; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
user1insecure: {
|
user1insecure: {
|
||||||
|
@ -175,6 +177,7 @@ export const loginResponseStub = {
|
||||||
cookie: [
|
cookie: [
|
||||||
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;',
|
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
'immich_auth_type=password; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;',
|
'immich_auth_type=password; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
|
'immich_is_authenticated=true; Path=/; Max-Age=34560000; SameSite=Lax;',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,7 +76,6 @@
|
||||||
dispatch('firstLogin');
|
dispatch('firstLogin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch('success');
|
dispatch('success');
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { api, UserAvatarColor, type UserResponseDto } from '@api';
|
import { api, UserAvatarColor } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
@ -10,9 +10,7 @@
|
||||||
import { notificationController, NotificationType } from '../notification/notification';
|
import { notificationController, NotificationType } from '../notification/notification';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import AvatarSelector from './avatar-selector.svelte';
|
import AvatarSelector from './avatar-selector.svelte';
|
||||||
import { setUser } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
|
||||||
export let user: UserResponseDto;
|
|
||||||
|
|
||||||
let isShowSelectAvatar = false;
|
let isShowSelectAvatar = false;
|
||||||
|
|
||||||
|
@ -23,21 +21,20 @@
|
||||||
|
|
||||||
const handleSaveProfile = async (color: UserAvatarColor) => {
|
const handleSaveProfile = async (color: UserAvatarColor) => {
|
||||||
try {
|
try {
|
||||||
if (user.profileImagePath !== '') {
|
if ($user.profileImagePath !== '') {
|
||||||
await api.userApi.deleteProfileImage();
|
await api.userApi.deleteProfileImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await api.userApi.updateUser({
|
const { data } = await api.userApi.updateUser({
|
||||||
updateUserDto: {
|
updateUserDto: {
|
||||||
id: user.id,
|
id: $user.id,
|
||||||
email: user.email,
|
email: $user.email,
|
||||||
name: user.name,
|
name: $user.name,
|
||||||
avatarColor: color,
|
avatarColor: color,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
user = data;
|
$user = data;
|
||||||
setUser(user);
|
|
||||||
isShowSelectAvatar = false;
|
isShowSelectAvatar = false;
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
|
@ -60,8 +57,8 @@
|
||||||
class="mx-4 mt-4 flex flex-col items-center justify-center gap-4 rounded-3xl bg-white p-4 dark:bg-immich-dark-primary/10"
|
class="mx-4 mt-4 flex flex-col items-center justify-center gap-4 rounded-3xl bg-white p-4 dark:bg-immich-dark-primary/10"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#key user}
|
{#key $user}
|
||||||
<UserAvatar {user} size="xl" />
|
<UserAvatar user={$user} size="xl" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6 border dark:border-immich-dark-primary bg-immich-primary"
|
class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6 border dark:border-immich-dark-primary bg-immich-primary"
|
||||||
|
@ -77,9 +74,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-center text-lg font-medium text-immich-primary dark:text-immich-dark-primary">
|
<p class="text-center text-lg font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||||
{user.name}
|
{$user.name}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
|
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href={AppRoute.USER_SETTINGS} on:click={() => dispatch('close')}>
|
<a href={AppRoute.USER_SETTINGS} on:click={() => dispatch('close')}>
|
||||||
|
@ -104,7 +101,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if isShowSelectAvatar}
|
{#if isShowSelectAvatar}
|
||||||
<AvatarSelector
|
<AvatarSelector
|
||||||
{user}
|
user={$user}
|
||||||
on:close={() => (isShowSelectAvatar = false)}
|
on:close={() => (isShowSelectAvatar = false)}
|
||||||
on:choose={({ detail: color }) => handleSaveProfile(color)}
|
on:choose={({ detail: color }) => handleSaveProfile(color)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -146,7 +146,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if shouldShowAccountInfoPanel}
|
{#if shouldShowAccountInfoPanel}
|
||||||
<AccountInfoPanel user={$user} on:logout={logOut} />
|
<AccountInfoPanel on:logout={logOut} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -3,27 +3,28 @@
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { api, type UserResponseDto } from '@api';
|
import { api } from '@api';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import SettingInputField, { SettingInputFieldType } from '../admin-page/settings/setting-input-field.svelte';
|
import SettingInputField, { SettingInputFieldType } from '../admin-page/settings/setting-input-field.svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import { setUser } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
export let user: UserResponseDto;
|
let editedUser = cloneDeep($user);
|
||||||
|
|
||||||
const handleSaveProfile = async () => {
|
const handleSaveProfile = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.userApi.updateUser({
|
const { data } = await api.userApi.updateUser({
|
||||||
updateUserDto: {
|
updateUserDto: {
|
||||||
id: user.id,
|
id: editedUser.id,
|
||||||
email: user.email,
|
email: editedUser.email,
|
||||||
name: user.name,
|
name: editedUser.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(user, data);
|
Object.assign(editedUser, data);
|
||||||
setUser(data);
|
$user = data;
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: 'Saved profile',
|
message: 'Saved profile',
|
||||||
|
@ -42,19 +43,24 @@
|
||||||
<SettingInputField
|
<SettingInputField
|
||||||
inputType={SettingInputFieldType.TEXT}
|
inputType={SettingInputFieldType.TEXT}
|
||||||
label="USER ID"
|
label="USER ID"
|
||||||
bind:value={user.id}
|
bind:value={editedUser.id}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={user.email} />
|
<SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={editedUser.email} />
|
||||||
|
|
||||||
<SettingInputField inputType={SettingInputFieldType.TEXT} label="NAME" bind:value={user.name} required={true} />
|
<SettingInputField
|
||||||
|
inputType={SettingInputFieldType.TEXT}
|
||||||
|
label="NAME"
|
||||||
|
bind:value={editedUser.name}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
|
||||||
<SettingInputField
|
<SettingInputField
|
||||||
inputType={SettingInputFieldType.TEXT}
|
inputType={SettingInputFieldType.TEXT}
|
||||||
label="STORAGE LABEL"
|
label="STORAGE LABEL"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
value={user.storageLabel || ''}
|
value={editedUser.storageLabel || ''}
|
||||||
required={false}
|
required={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -62,7 +68,7 @@
|
||||||
inputType={SettingInputFieldType.TEXT}
|
inputType={SettingInputFieldType.TEXT}
|
||||||
label="EXTERNAL PATH"
|
label="EXTERNAL PATH"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
value={user.externalPath || ''}
|
value={editedUser.externalPath || ''}
|
||||||
required={false}
|
required={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion key="account" title="Account" subtitle="Manage your account">
|
<SettingAccordion key="account" title="Account" subtitle="Manage your account">
|
||||||
<UserProfileSettings user={$user} />
|
<UserProfileSettings />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion key="api-keys" title="API Keys" subtitle="Manage your API keys">
|
<SettingAccordion key="api-keys" title="API Keys" subtitle="Manage your API keys">
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
import { get, writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { UserResponseDto } from '@api';
|
import type { UserResponseDto } from '@api';
|
||||||
|
|
||||||
export let user = writable<UserResponseDto>();
|
export let user = writable<UserResponseDto>();
|
||||||
|
|
||||||
export const setUser = (value: UserResponseDto) => {
|
|
||||||
user.set(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSavedUser = () => {
|
|
||||||
return get(user);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetSavedUser = () => {
|
export const resetSavedUser = () => {
|
||||||
user = writable<UserResponseDto>();
|
user = writable<UserResponseDto>();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import type { AssetResponseDto, ServerVersionResponseDto } from '@api';
|
import type { AssetResponseDto, ServerVersionResponseDto } from '@api';
|
||||||
import { type Socket, io } from 'socket.io-client';
|
import { type Socket, io } from 'socket.io-client';
|
||||||
import { writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import { loadConfig } from './server-config.store';
|
import { loadConfig } from './server-config.store';
|
||||||
import { getAuthUser } from '$lib/utils/auth';
|
import { user } from './user.store';
|
||||||
|
|
||||||
export interface ReleaseEvent {
|
export interface ReleaseEvent {
|
||||||
isAvailable: boolean;
|
isAvailable: boolean;
|
||||||
|
@ -30,8 +30,7 @@ export const openWebsocketConnection = async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await getAuthUser();
|
if (!get(user)) {
|
||||||
if (!user) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,53 +1,65 @@
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { AppRoute } from '../constants';
|
import { AppRoute } from '../constants';
|
||||||
import { getSavedUser, setUser } from '$lib/stores/user.store';
|
import { get } from 'svelte/store';
|
||||||
import { serverInfo } from '$lib/stores/server-info.store';
|
import { serverInfo } from '$lib/stores/server-info.store';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
|
||||||
export interface AuthOptions {
|
export interface AuthOptions {
|
||||||
admin?: true;
|
admin?: true;
|
||||||
|
public?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAuthUser = async () => {
|
export const loadUser = async () => {
|
||||||
try {
|
try {
|
||||||
const { data: user } = await api.userApi.getMyUserInfo();
|
let loaded = get(user);
|
||||||
return user;
|
if (!loaded && hasAuthCookie()) {
|
||||||
|
const { data } = await api.userApi.getMyUserInfo();
|
||||||
|
loaded = data;
|
||||||
|
user.set(loaded);
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authenticate = async (options?: AuthOptions) => {
|
const hasAuthCookie = (): boolean => {
|
||||||
options = options || {};
|
if (!browser) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const savedUser = getSavedUser();
|
for (const cookie of document.cookie.split('; ')) {
|
||||||
const user = savedUser || (await getAuthUser());
|
const [name] = cookie.split('=');
|
||||||
|
if (name === 'immich_is_authenticated') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authenticate = async (options?: AuthOptions) => {
|
||||||
|
const { public: publicRoute, admin: adminRoute } = options || {};
|
||||||
|
const user = await loadUser();
|
||||||
|
|
||||||
|
if (publicRoute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(302, AppRoute.AUTH_LOGIN);
|
redirect(302, AppRoute.AUTH_LOGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.admin && !user.isAdmin) {
|
if (adminRoute && !user.isAdmin) {
|
||||||
redirect(302, AppRoute.PHOTOS);
|
redirect(302, AppRoute.PHOTOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!savedUser) {
|
|
||||||
setUser(user);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestServerInfo = async () => {
|
export const requestServerInfo = async () => {
|
||||||
if (getSavedUser()) {
|
if (get(user)) {
|
||||||
const { data } = await api.serverInfoApi.getServerInfo();
|
const { data } = await api.serverInfoApi.getServerInfo();
|
||||||
serverInfo.set(data);
|
serverInfo.set(data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isLoggedIn = async () => {
|
|
||||||
const savedUser = getSavedUser();
|
|
||||||
const user = savedUser || (await getAuthUser());
|
|
||||||
if (!savedUser) {
|
|
||||||
setUser(user);
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getAuthUser } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api, ThumbnailFormat } from '@api';
|
import { api, ThumbnailFormat } from '@api';
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError } from 'axios';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
@ -6,7 +6,7 @@ import { error as throwError } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load = (async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
const { key } = params;
|
const { key } = params;
|
||||||
await getAuthUser();
|
await authenticate({ public: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
|
const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { api } from '../api';
|
import { api } from '../api';
|
||||||
import { isLoggedIn } from '../lib/utils/auth';
|
import { loadUser } from '../lib/utils/auth';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
export const csr = true;
|
export const csr = true;
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = (async () => {
|
||||||
const authenticated = await isLoggedIn();
|
const authenticated = await loadUser();
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
redirect(302, AppRoute.PHOTOS);
|
redirect(302, AppRoute.PHOTOS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ import { AppRoute } from '$lib/constants';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
import { getSavedUser } from '$lib/stores/user.store';
|
import { get } from 'svelte/store';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = (async () => {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
if (!getSavedUser().shouldChangePassword) {
|
if (!get(user).shouldChangePassword) {
|
||||||
redirect(302, AppRoute.PHOTOS);
|
redirect(302, AppRoute.PHOTOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue