Browse Source

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

* feat(server): use base64 shared links

* fix: handle array values
Jason Rasmussen 2 năm trước cách đây
mục cha
commit
4350f9363d

+ 11 - 2
server/libs/domain/src/auth/auth.service.spec.ts

@@ -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);
     });
   });
 

+ 2 - 2
server/libs/domain/src/share/response-dto/shared-link-response.dto.ts

@@ -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,

+ 5 - 2
server/libs/domain/src/share/share.core.ts

@@ -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;

+ 1 - 1
server/libs/domain/src/share/shared-link.repository.ts

@@ -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>;

+ 12 - 7
server/libs/domain/test/fixtures.ts

@@ -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,

+ 2 - 2
server/libs/infra/src/repositories/shared-link.repository.ts

@@ -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,