1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-04 02:46:47 +01:00

feat(server): use base64 shared links (#2633)

* feat(server): use base64 shared links

* fix: handle array values
This commit is contained in:
Jason Rasmussen 2023-06-01 16:56:37 -04:00 committed by GitHub
parent 76a1629e75
commit 4350f9363d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 33 additions and 16 deletions

View file

@ -277,11 +277,20 @@ describe('AuthService', () => {
await expect(sut.validate(headers, {})).rejects.toBeInstanceOf(UnauthorizedException); await expect(sut.validate(headers, {})).rejects.toBeInstanceOf(UnauthorizedException);
}); });
it('should accept a valid key', async () => { it('should accept a base64url key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid); shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
userMock.get.mockResolvedValue(userEntityStub.admin); userMock.get.mockResolvedValue(userEntityStub.admin);
const headers: IncomingHttpHeaders = { 'x-immich-share-key': 'key' }; const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') };
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink); await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink);
expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
});
it('should accept a hex key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
userMock.get.mockResolvedValue(userEntityStub.admin);
const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') };
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink);
expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
}); });
}); });

View file

@ -31,7 +31,7 @@ export function mapSharedLink(sharedLink: SharedLinkEntity): SharedLinkResponseD
id: sharedLink.id, id: sharedLink.id,
description: sharedLink.description, description: sharedLink.description,
userId: sharedLink.userId, userId: sharedLink.userId,
key: sharedLink.key.toString('hex'), key: sharedLink.key.toString('base64url'),
type: sharedLink.type, type: sharedLink.type,
createdAt: sharedLink.createdAt, createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt, expiresAt: sharedLink.expiresAt,
@ -53,7 +53,7 @@ export function mapSharedLinkWithNoExif(sharedLink: SharedLinkEntity): SharedLin
id: sharedLink.id, id: sharedLink.id,
description: sharedLink.description, description: sharedLink.description,
userId: sharedLink.userId, userId: sharedLink.userId,
key: sharedLink.key.toString('hex'), key: sharedLink.key.toString('base64url'),
type: sharedLink.type, type: sharedLink.type,
createdAt: sharedLink.createdAt, createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt, expiresAt: sharedLink.expiresAt,

View file

@ -82,8 +82,11 @@ export class ShareCore {
} }
} }
async validate(key: string): Promise<AuthUserDto | null> { async validate(key: string | string[]): Promise<AuthUserDto | null> {
const link = await this.repository.getByKey(key); key = Array.isArray(key) ? key[0] : key;
const bytes = Buffer.from(key, key.length === 100 ? 'hex' : 'base64url');
const link = await this.repository.getByKey(bytes);
if (link) { if (link) {
if (!link.expiresAt || new Date(link.expiresAt) > new Date()) { if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
const user = link.user; const user = link.user;

View file

@ -5,7 +5,7 @@ export const ISharedLinkRepository = 'ISharedLinkRepository';
export interface ISharedLinkRepository { export interface ISharedLinkRepository {
getAll(userId: string): Promise<SharedLinkEntity[]>; getAll(userId: string): Promise<SharedLinkEntity[]>;
get(userId: string, id: string): Promise<SharedLinkEntity | null>; get(userId: string, id: string): Promise<SharedLinkEntity | null>;
getByKey(key: string): Promise<SharedLinkEntity | null>; getByKey(key: Buffer): Promise<SharedLinkEntity | null>;
create(entity: Omit<SharedLinkEntity, 'id' | 'user'>): Promise<SharedLinkEntity>; create(entity: Omit<SharedLinkEntity, 'id' | 'user'>): Promise<SharedLinkEntity>;
remove(entity: SharedLinkEntity): Promise<void>; remove(entity: SharedLinkEntity): Promise<void>;
save(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity>; save(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity>;

View file

@ -38,6 +38,11 @@ const yesterday = new Date();
tomorrow.setDate(today.getDate() + 1); tomorrow.setDate(today.getDate() + 1);
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
const sharedLinkBytes = Buffer.from(
'2c2b646895f84753bff43fb696ad124f3b0faf2a0bd547406f26fa4a76b5c71990092baa536275654b2ab7a191fb21a6d6cd',
'hex',
);
export const authStub = { export const authStub = {
admin: Object.freeze<AuthUserDto>({ admin: Object.freeze<AuthUserDto>({
id: 'admin_id', id: 'admin_id',
@ -662,7 +667,7 @@ export const sharedLinkStub = {
id: '123', id: '123',
userId: authStub.admin.id, userId: authStub.admin.id,
user: userEntityStub.admin, user: userEntityStub.admin,
key: Buffer.from('secret-key', 'utf8'), key: sharedLinkBytes,
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
createdAt: today, createdAt: today,
expiresAt: tomorrow, expiresAt: tomorrow,
@ -676,7 +681,7 @@ export const sharedLinkStub = {
id: '123', id: '123',
userId: authStub.admin.id, userId: authStub.admin.id,
user: userEntityStub.admin, user: userEntityStub.admin,
key: Buffer.from('secret-key', 'utf8'), key: sharedLinkBytes,
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
createdAt: today, createdAt: today,
expiresAt: yesterday, expiresAt: yesterday,
@ -689,7 +694,7 @@ export const sharedLinkStub = {
id: '123', id: '123',
userId: authStub.admin.id, userId: authStub.admin.id,
user: userEntityStub.admin, user: userEntityStub.admin,
key: Buffer.from('secret-key', 'utf8'), key: sharedLinkBytes,
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
createdAt: today, createdAt: today,
expiresAt: tomorrow, expiresAt: tomorrow,
@ -786,7 +791,7 @@ export const sharedLinkResponseStub = {
description: undefined, description: undefined,
expiresAt: tomorrow, expiresAt: tomorrow,
id: '123', id: '123',
key: '7365637265742d6b6579', key: sharedLinkBytes.toString('base64url'),
showExif: true, showExif: true,
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
userId: 'admin_id', userId: 'admin_id',
@ -800,7 +805,7 @@ export const sharedLinkResponseStub = {
description: undefined, description: undefined,
expiresAt: yesterday, expiresAt: yesterday,
id: '123', id: '123',
key: '7365637265742d6b6579', key: sharedLinkBytes.toString('base64url'),
showExif: true, showExif: true,
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
userId: 'admin_id', userId: 'admin_id',
@ -808,7 +813,7 @@ export const sharedLinkResponseStub = {
readonly: Object.freeze<SharedLinkResponseDto>({ readonly: Object.freeze<SharedLinkResponseDto>({
id: '123', id: '123',
userId: 'admin_id', userId: 'admin_id',
key: '7365637265742d6b6579', key: sharedLinkBytes.toString('base64url'),
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
createdAt: today, createdAt: today,
expiresAt: tomorrow, expiresAt: tomorrow,
@ -822,7 +827,7 @@ export const sharedLinkResponseStub = {
readonlyNoExif: Object.freeze<SharedLinkResponseDto>({ readonlyNoExif: Object.freeze<SharedLinkResponseDto>({
id: '123', id: '123',
userId: 'admin_id', userId: 'admin_id',
key: '7365637265742d6b6579', key: sharedLinkBytes.toString('base64url'),
type: SharedLinkType.ALBUM, type: SharedLinkType.ALBUM,
createdAt: today, createdAt: today,
expiresAt: tomorrow, expiresAt: tomorrow,

View file

@ -60,10 +60,10 @@ export class SharedLinkRepository implements ISharedLinkRepository {
}); });
} }
async getByKey(key: string): Promise<SharedLinkEntity | null> { async getByKey(key: Buffer): Promise<SharedLinkEntity | null> {
return await this.repository.findOne({ return await this.repository.findOne({
where: { where: {
key: Buffer.from(key, 'hex'), key,
}, },
relations: { relations: {
assets: true, assets: true,