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);
});
it('should accept a valid key', async () => {
it('should accept a base64url key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
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);
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,
description: sharedLink.description,
userId: sharedLink.userId,
key: sharedLink.key.toString('hex'),
key: sharedLink.key.toString('base64url'),
type: sharedLink.type,
createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt,
@ -53,7 +53,7 @@ export function mapSharedLinkWithNoExif(sharedLink: SharedLinkEntity): SharedLin
id: sharedLink.id,
description: sharedLink.description,
userId: sharedLink.userId,
key: sharedLink.key.toString('hex'),
key: sharedLink.key.toString('base64url'),
type: sharedLink.type,
createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt,

View file

@ -82,8 +82,11 @@ export class ShareCore {
}
}
async validate(key: string): Promise<AuthUserDto | null> {
const link = await this.repository.getByKey(key);
async validate(key: string | string[]): Promise<AuthUserDto | null> {
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.expiresAt || new Date(link.expiresAt) > new Date()) {
const user = link.user;

View file

@ -5,7 +5,7 @@ export const ISharedLinkRepository = 'ISharedLinkRepository';
export interface ISharedLinkRepository {
getAll(userId: string): Promise<SharedLinkEntity[]>;
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>;
remove(entity: SharedLinkEntity): Promise<void>;
save(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity>;

View file

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