feat: sync pictureFile with oidc if it isn't set already (#17397)

* feat: sync pictureFile with oidc if it isn't set already

fix: move picture writer to get userId

fix: move await promise to the top of the setPicure function before checking its value and automatically create the user folder

chore: code cleanup

* fix: extension double dot

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Etienne 2025-04-11 20:00:39 +02:00 committed by GitHub
parent 08b5952c87
commit d7a782da34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 133 additions and 5 deletions

View file

@ -3,7 +3,9 @@ import { isString } from 'class-validator';
import { parse } from 'cookie';
import { DateTime } from 'luxon';
import { IncomingHttpHeaders } from 'node:http';
import { join } from 'node:path';
import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { UserAdmin } from 'src/database';
import { OnEvent } from 'src/decorators';
import {
@ -18,12 +20,12 @@ import {
mapLoginResponse,
} from 'src/dtos/auth.dto';
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, JobName, Permission, StorageFolder } from 'src/enum';
import { OAuthProfile } from 'src/repositories/oauth.repository';
import { BaseService } from 'src/services/base.service';
import { isGranted } from 'src/utils/access';
import { HumanReadableSize } from 'src/utils/bytes';
import { mimeTypes } from 'src/utils/mime-types';
export interface LoginDetails {
isSecure: boolean;
clientIp: string;
@ -239,9 +241,36 @@ export class AuthService extends BaseService {
});
}
if (!user.profileImagePath && profile.picture) {
await this.syncProfilePicture(user, profile.picture);
}
return this.createLoginResponse(user, loginDetails);
}
private async syncProfilePicture(user: UserAdmin, url: string) {
try {
const oldPath = user.profileImagePath;
const { contentType, data } = await this.oauthRepository.getProfilePicture(url);
const extensionWithDot = mimeTypes.toExtension(contentType || 'image/jpeg') ?? 'jpg';
const profileImagePath = join(
StorageCore.getFolderLocation(StorageFolder.PROFILE, user.id),
`${this.cryptoRepository.randomUUID()}${extensionWithDot}`,
);
this.storageCore.ensureFolders(profileImagePath);
await this.storageRepository.createFile(profileImagePath, Buffer.from(data));
await this.userRepository.update(user.id, { profileImagePath, profileChangedAt: new Date() });
if (oldPath) {
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [oldPath] } });
}
} catch (error: Error | any) {
this.logger.warn(`Unable to sync oauth profile picture: ${error}`, error?.stack);
}
}
async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserAdminResponseDto> {
const { oauth } = await this.getConfig({ withCache: false });
const { sub: oauthId } = await this.oauthRepository.getProfile(