mirror of
https://github.com/immich-app/immich.git
synced 2026-04-30 04:58:48 -07:00
Compare commits
13 Commits
main
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23f5a54ae7 | ||
|
|
8d3314b949 | ||
|
|
bb06990957 | ||
|
|
b2d7d63a60 | ||
|
|
3cc34043dc | ||
|
|
81b8d896b5 | ||
|
|
87ef6bed4a | ||
|
|
94264dfc1b | ||
|
|
32cc8ca0aa | ||
|
|
fa6ce8cc00 | ||
|
|
8011adadb6 | ||
|
|
c1bd7a116b | ||
|
|
b96421a083 |
@@ -351,7 +351,7 @@ describe(`/oauth`, () => {
|
||||
const callbackParams = await loginWithOAuth('oauth-no-auto-register');
|
||||
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('User does not exist and auto registering is disabled.'));
|
||||
expect(body).toEqual(errorDto.badRequest('OAuth authentication failed'));
|
||||
});
|
||||
|
||||
it('should link to an existing user by email', async () => {
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('/users', () => {
|
||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toMatchObject(errorDto.badRequest('Email already in use by another account'));
|
||||
expect(body).toMatchObject(errorDto.badRequest('Email is not available'));
|
||||
});
|
||||
|
||||
it('should update my email', async () => {
|
||||
|
||||
@@ -42,6 +42,8 @@ export const VECTOR_INDEX_TABLES = {
|
||||
export const VECTORCHORD_LIST_SLACK_FACTOR = 1.2;
|
||||
|
||||
export const SALT_ROUNDS = 10;
|
||||
// Syntactically valid bcrypt hash used in login() preventing timing-based user enumeration.
|
||||
export const LOGIN_DUMMY_HASH = '$2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZabcde';
|
||||
|
||||
export const IWorker = 'IWorker';
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ export class AlbumService extends BaseService {
|
||||
for (const { userId } of albumUsers) {
|
||||
const exists = await this.userRepository.get(userId, {});
|
||||
if (!exists) {
|
||||
throw new BadRequestException('User not found');
|
||||
throw new BadRequestException('Invalid user');
|
||||
}
|
||||
|
||||
if (userId == auth.user.id) {
|
||||
@@ -302,7 +302,7 @@ export class AlbumService extends BaseService {
|
||||
|
||||
const user = await this.userRepository.get(userId, {});
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
throw new BadRequestException('Invalid user');
|
||||
}
|
||||
|
||||
await this.albumUserRepository.create({ userId, albumId: id, role });
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BadRequestException, ForbiddenException, Injectable, UnauthorizedExcept
|
||||
import { parse } from 'cookie';
|
||||
import { DateTime } from 'luxon';
|
||||
import { IncomingHttpHeaders } from 'node:http';
|
||||
import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
|
||||
import { LOGIN_DUMMY_HASH, LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
|
||||
import { AuthSharedLink, AuthUser, UserAdmin } from 'src/database';
|
||||
import {
|
||||
AuthDto,
|
||||
@@ -62,15 +62,12 @@ export class AuthService extends BaseService {
|
||||
throw new UnauthorizedException('Password login has been disabled');
|
||||
}
|
||||
|
||||
let user = await this.userRepository.getByEmail(dto.email, { withPassword: true });
|
||||
if (user) {
|
||||
const isAuthenticated = this.validateSecret(dto.password, user.password);
|
||||
if (!isAuthenticated) {
|
||||
user = undefined;
|
||||
}
|
||||
}
|
||||
const user = await this.userRepository.getByEmail(dto.email, { withPassword: true });
|
||||
// Always run bcrypt so response time is constant regardless of whether the email
|
||||
// is registered, preventing timing-based user enumeration.
|
||||
const authenticated = this.cryptoRepository.compareBcrypt(dto.password, user?.password ?? LOGIN_DUMMY_HASH);
|
||||
|
||||
if (!user) {
|
||||
if (!user || !user.password || !authenticated) {
|
||||
this.logger.warn(`Failed login attempt for user ${dto.email} from ip address ${details.clientIp}`);
|
||||
throw new UnauthorizedException('Incorrect email or password');
|
||||
}
|
||||
@@ -325,7 +322,7 @@ export class AuthService extends BaseService {
|
||||
const emailUser = await this.userRepository.getByEmail(normalizedEmail);
|
||||
if (emailUser) {
|
||||
if (emailUser.oauthId) {
|
||||
throw new BadRequestException('User already exists, but is linked to another account.');
|
||||
throw new BadRequestException('OAuth authentication failed');
|
||||
}
|
||||
user = await this.userRepository.update(emailUser.id, { oauthId: profile.sub });
|
||||
}
|
||||
@@ -337,7 +334,7 @@ export class AuthService extends BaseService {
|
||||
this.logger.warn(
|
||||
`Unable to register ${profile.sub}/${normalizedEmail || '(no email)'}. To enable set OAuth Auto Register to true in admin settings.`,
|
||||
);
|
||||
throw new BadRequestException(`User does not exist and auto registering is disabled.`);
|
||||
throw new BadRequestException('OAuth authentication failed');
|
||||
}
|
||||
|
||||
if (!normalizedEmail) {
|
||||
|
||||
@@ -218,7 +218,7 @@ export class BaseService {
|
||||
async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {
|
||||
const exists = await this.userRepository.getByEmail(dto.email);
|
||||
if (exists) {
|
||||
throw new BadRequestException('User exists');
|
||||
throw new BadRequestException('Email is not available');
|
||||
}
|
||||
|
||||
if (!dto.isAdmin) {
|
||||
|
||||
@@ -110,7 +110,7 @@ export class SharedLinkService extends BaseService {
|
||||
|
||||
private handleError(error: unknown): never {
|
||||
if ((error as PostgresError).constraint_name === 'shared_link_slug_uq') {
|
||||
throw new BadRequestException('Shared link with this slug already exists');
|
||||
throw new BadRequestException('Failed to save shared link');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class UserAdminService extends BaseService {
|
||||
if (dto.email) {
|
||||
const duplicate = await this.userRepository.getByEmail(dto.email);
|
||||
if (duplicate && duplicate.id !== id) {
|
||||
throw new BadRequestException('Email already in use by another account');
|
||||
throw new BadRequestException('Email is not available');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ describe(UserService.name, () => {
|
||||
it('should throw an error if the user does not exist', async () => {
|
||||
mocks.user.get.mockResolvedValue(void 0);
|
||||
|
||||
await expect(sut.getProfileImage(userStub.admin.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.getProfileImage(userStub.admin.id)).rejects.toBeInstanceOf(NotFoundException);
|
||||
|
||||
expect(mocks.user.get).toHaveBeenCalledWith(userStub.admin.id, {});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ export class UserService extends BaseService {
|
||||
if (dto.email) {
|
||||
const duplicate = await this.userRepository.getByEmail(dto.email);
|
||||
if (duplicate && duplicate.id !== user.id) {
|
||||
throw new BadRequestException('Email already in use by another account');
|
||||
throw new BadRequestException('Email is not available');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +134,9 @@ export class UserService extends BaseService {
|
||||
}
|
||||
|
||||
async getProfileImage(id: string): Promise<ImmichFileResponse> {
|
||||
const user = await this.findOrFail(id, {});
|
||||
if (!user.profileImagePath) {
|
||||
throw new NotFoundException('User does not have a profile image');
|
||||
const user = await this.userRepository.get(id, {});
|
||||
if (!user || !user.profileImagePath) {
|
||||
throw new NotFoundException('Not found');
|
||||
}
|
||||
|
||||
return new ImmichFileResponse({
|
||||
|
||||
@@ -48,7 +48,7 @@ describe(UserService.name, () => {
|
||||
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||
const user = mediumFactory.userInsert();
|
||||
await expect(sut.createUser({ name: 'Test', email: user.email })).resolves.toMatchObject({ email: user.email });
|
||||
await expect(sut.createUser({ name: 'Test', email: user.email })).rejects.toThrow('User exists');
|
||||
await expect(sut.createUser({ name: 'Test', email: user.email })).rejects.toThrow('Email is not available');
|
||||
});
|
||||
|
||||
it('should not return password', async () => {
|
||||
|
||||
Reference in New Issue
Block a user