refactor: auth manager (#27638)

This commit is contained in:
Jason Rasmussen
2026-04-14 08:49:24 -04:00
committed by GitHub
parent daed3f0966
commit 1ba0989e15
77 changed files with 387 additions and 379 deletions
+2 -5
View File
@@ -3,7 +3,6 @@ import { authManager } from '$lib/managers/auth-manager.svelte';
import { downloadManager } from '$lib/managers/download-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { preferences } from '$lib/stores/user.store';
import { downloadRequest, withError } from '$lib/utils';
import { getByteUnitString } from '$lib/utils/byte-units';
import { getFormatter } from '$lib/utils/i18n';
@@ -26,7 +25,6 @@ import {
type DownloadInfoDto,
type ExifResponseDto,
type StackResponseDto,
type UserPreferencesResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { toastManager } from '@immich/ui';
@@ -102,9 +100,8 @@ export const downloadUrl = (url: string, filename: string) => {
};
export const downloadArchive = async (fileName: string, options: Omit<DownloadInfoDto, 'archiveSize'>) => {
const $preferences = get<UserPreferencesResponseDto | undefined>(preferences);
const dto = { ...options, archiveSize: $preferences?.download.archiveSize };
const archiveSize = authManager.authenticated ? authManager.preferences.download.archiveSize : undefined;
const dto = { ...options, archiveSize };
const [error, downloadInfo] = await withError(() => getDownloadInfo({ ...authManager.params, downloadInfoDto: dto }));
if (error) {
const $t = get(t);
+8 -45
View File
@@ -1,82 +1,45 @@
import { browser } from '$app/environment';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { Route } from '$lib/route';
import { preferences as preferences$, user as user$ } from '$lib/stores/user.store';
import { userInteraction } from '$lib/stores/user.svelte';
import { getMyPreferences, getMyUser, getStorage } from '@immich/sdk';
import { getStorage } from '@immich/sdk';
import { redirect } from '@sveltejs/kit';
import { DateTime } from 'luxon';
import { get } from 'svelte/store';
export interface AuthOptions {
admin?: true;
public?: boolean;
}
export const loadUser = async () => {
try {
let user = get(user$);
let preferences = get(preferences$);
if ((!user || !preferences) && hasAuthCookie()) {
[user, preferences] = await Promise.all([getMyUser(), getMyPreferences()]);
user$.set(user);
preferences$.set(preferences);
eventManager.emit('AuthUserLoaded', user);
}
return user;
} catch {
return null;
}
};
const hasAuthCookie = (): boolean => {
if (!browser) {
return false;
}
for (const cookie of document.cookie.split('; ')) {
const [name] = cookie.split('=');
if (name === 'immich_is_authenticated') {
return true;
}
}
return false;
};
export const authenticate = async (url: URL, options?: AuthOptions) => {
const { public: publicRoute, admin: adminRoute } = options || {};
const user = await loadUser();
await authManager.load();
if (publicRoute) {
return;
}
if (!user) {
if (!authManager.authenticated) {
redirect(307, Route.login({ continue: url.pathname + url.search }));
}
if (adminRoute && !user.isAdmin) {
if (adminRoute && !authManager.user.isAdmin) {
redirect(307, Route.photos());
}
};
export const requestServerInfo = async () => {
if (get(user$)) {
if (authManager.authenticated) {
const data = await getStorage();
userInteraction.serverInfo = data;
}
};
export const getAccountAge = (): number => {
const user = get(user$);
if (!user) {
if (!authManager.authenticated) {
return 0;
}
const createdDate = DateTime.fromISO(user.createdAt);
const createdDate = DateTime.fromISO(authManager.user.createdAt);
const now = DateTime.now();
const accountAge = now.diff(createdDate, 'days').days.toFixed(0);
@@ -1,8 +1,7 @@
import { authManager } from '$lib/managers/auth-manager.svelte';
import { CastDestinationType, CastState, type ICastDestination } from '$lib/managers/cast-manager.svelte';
import { preferences } from '$lib/stores/user.store';
import 'chromecast-caf-sender';
import { Duration } from 'luxon';
import { get } from 'svelte/store';
const FRAMEWORK_LINK = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
@@ -26,8 +25,7 @@ export class GCastDestination implements ICastDestination {
private currentUrl: string | null = null;
async initialize(): Promise<boolean> {
const preferencesStore = get(preferences);
if (!preferencesStore || !preferencesStore.cast.gCastEnabled) {
if (!authManager.authenticated || authManager.preferences.cast.gCastEnabled) {
this.isAvailable = false;
return false;
}
+7 -5
View File
@@ -1,9 +1,10 @@
import { authManager } from '$lib/managers/auth-manager.svelte';
import { uploadManager } from '$lib/managers/upload-manager.svelte';
import { uploadAssetsStore } from '$lib/stores/upload';
import { resetSavedUser, user } from '$lib/stores/user.store';
import { UploadState } from '$lib/types';
import * as utils from '$lib/utils';
import { AssetMediaStatus, type AssetMediaResponseDto, type UserAdminResponseDto } from '@immich/sdk';
import { preferencesFactory } from '@test-data/factories/preferences-factory';
import { get } from 'svelte/store';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fileUploadHandler } from './file-uploader';
@@ -18,7 +19,7 @@ describe('fileUploader error handling', () => {
vi.clearAllMocks();
vi.spyOn(uploadManager, 'getExtensions').mockReturnValue(['.jpg']);
uploadAssetsStore.reset();
resetSavedUser();
authManager.reset();
// Stub out crypto to avoid that branch
vi.stubGlobal('crypto', undefined);
@@ -31,7 +32,7 @@ describe('fileUploader error handling', () => {
describe(`for ${name}`, () => {
beforeEach(() => {
if (mockUser) {
user.set(mockUserObject);
authManager.setUser(mockUserObject);
}
});
@@ -58,9 +59,10 @@ describe('fileUploader error handling', () => {
}
it('should suppress errors on logout', async () => {
user.set(mockUserObject);
authManager.setUser(mockUserObject);
authManager.setPreferences(preferencesFactory.build());
vi.spyOn(utils, 'uploadRequest').mockImplementationOnce(() => {
resetSavedUser();
authManager.reset();
return Promise.reject(mockError);
});
+2 -3
View File
@@ -2,7 +2,6 @@ import { authManager } from '$lib/managers/auth-manager.svelte';
import { uploadManager } from '$lib/managers/upload-manager.svelte';
import { addAssetsToAlbums } from '$lib/services/album.service';
import { uploadAssetsStore } from '$lib/stores/upload';
import { user } from '$lib/stores/user.store';
import { UploadState } from '$lib/types';
import { uploadRequest } from '$lib/utils';
import { ExecutorQueue } from '$lib/utils/executor-queue';
@@ -145,7 +144,7 @@ async function fileUploader({
}: FileUploaderParams): Promise<string | undefined> {
const fileCreatedAt = new Date(assetFile.lastModified).toISOString();
const $t = get(t);
const wasInitiallyLoggedIn = !!get(user);
const wasInitiallyLoggedIn = !!authManager.authenticated;
uploadAssetsStore.markStarted(deviceAssetId);
@@ -238,7 +237,7 @@ async function fileUploader({
} catch (error) {
// If the user store no longer holds a user, it means they have logged out
// In this case don't bother reporting any errors.
if (wasInitiallyLoggedIn && !get(user)) {
if (wasInitiallyLoggedIn && !authManager.authenticated) {
return;
}
+6 -4
View File
@@ -1,14 +1,16 @@
import { PUBLIC_IMMICH_BUY_HOST, PUBLIC_IMMICH_PAY_HOST } from '$env/static/public';
import type { ImmichProduct } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { setServerLicense, setUserLicense, type LicenseResponseDto } from '@immich/sdk';
import { loadUser } from './auth';
export const activateProduct = async (licenseKey: string, activationKey: string): Promise<LicenseResponseDto> => {
// Send server key to user activation if user is not admin
const user = await loadUser();
const isServerActivation = user?.isAdmin && licenseKey.search('IMSV') !== -1;
// TODO is this needed?
await authManager.load();
const isServerActivation = authManager.user.isAdmin && licenseKey.search('IMSV') !== -1;
const licenseKeyDto = { licenseKey, activationKey };
// Send server key to user activation if user is not admin
return isServerActivation ? setServerLicense({ licenseKeyDto }) : setUserLicense({ licenseKeyDto });
};
+5 -18
View File
@@ -1,32 +1,19 @@
import { preferences } from '$lib/stores/user.store';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { updateMyPreferences } from '@immich/sdk';
import { DateTime } from 'luxon';
import { get } from 'svelte/store';
export const getButtonVisibility = (): boolean => {
const myPreferences = get(preferences);
if (!myPreferences) {
if (!authManager.authenticated) {
return true;
}
const { purchase } = myPreferences;
const now = DateTime.now();
const hideUntilDate = DateTime.fromISO(purchase.hideBuyButtonUntil);
const hideUntilDate = DateTime.fromISO(authManager.preferences.purchase.hideBuyButtonUntil);
const dayLeft = Number(now.diff(hideUntilDate, 'days').days.toFixed(0));
return dayLeft > 0;
};
export const setSupportBadgeVisibility = async (value: boolean) => {
const response = await updateMyPreferences({
userPreferencesUpdateDto: {
purchase: {
showSupportBadge: value,
},
},
});
preferences.set(response);
const response = await updateMyPreferences({ userPreferencesUpdateDto: { purchase: { showSupportBadge: value } } });
authManager.setPreferences(response);
};