mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	fix(server/cookies): Making the cookie better (#1366)
* fix(server/cookies): Making the cookie better Cookie should have SameSite=Stict and Secure if served via https, otherwise just SameSite=Strict set. * feat(server): forgot to add secure to the other cookie. * Fixed the cookies and tests for them.
This commit is contained in:
		
							parent
							
								
									c0a6b3d5a3
								
							
						
					
					
						commit
						5262e92b9f
					
				@ -25,9 +25,13 @@ export class AuthController {
 | 
				
			|||||||
    @Body(new ValidationPipe({ transform: true })) loginCredential: LoginCredentialDto,
 | 
					    @Body(new ValidationPipe({ transform: true })) loginCredential: LoginCredentialDto,
 | 
				
			||||||
    @Ip() clientIp: string,
 | 
					    @Ip() clientIp: string,
 | 
				
			||||||
    @Res({ passthrough: true }) response: Response,
 | 
					    @Res({ passthrough: true }) response: Response,
 | 
				
			||||||
 | 
					    @Req() request: Request,
 | 
				
			||||||
  ): Promise<LoginResponseDto> {
 | 
					  ): Promise<LoginResponseDto> {
 | 
				
			||||||
    const loginResponse = await this.authService.login(loginCredential, clientIp);
 | 
					    const loginResponse = await this.authService.login(loginCredential, clientIp);
 | 
				
			||||||
    response.setHeader('Set-Cookie', this.immichJwtService.getCookies(loginResponse, AuthType.PASSWORD));
 | 
					    response.setHeader(
 | 
				
			||||||
 | 
					      'Set-Cookie',
 | 
				
			||||||
 | 
					      this.immichJwtService.getCookies(loginResponse, AuthType.PASSWORD, request.secure),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    return loginResponse;
 | 
					    return loginResponse;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -33,9 +33,10 @@ export class OAuthController {
 | 
				
			|||||||
  public async callback(
 | 
					  public async callback(
 | 
				
			||||||
    @Res({ passthrough: true }) response: Response,
 | 
					    @Res({ passthrough: true }) response: Response,
 | 
				
			||||||
    @Body(ValidationPipe) dto: OAuthCallbackDto,
 | 
					    @Body(ValidationPipe) dto: OAuthCallbackDto,
 | 
				
			||||||
 | 
					    @Req() request: Request,
 | 
				
			||||||
  ): Promise<LoginResponseDto> {
 | 
					  ): Promise<LoginResponseDto> {
 | 
				
			||||||
    const loginResponse = await this.oauthService.login(dto);
 | 
					    const loginResponse = await this.oauthService.login(dto);
 | 
				
			||||||
    response.setHeader('Set-Cookie', this.immichJwtService.getCookies(loginResponse, AuthType.OAUTH));
 | 
					    response.setHeader('Set-Cookie', this.immichJwtService.getCookies(loginResponse, AuthType.OAUTH, request.secure));
 | 
				
			||||||
    return loginResponse;
 | 
					    return loginResponse;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -36,13 +36,23 @@ describe('ImmichJwtService', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('getCookies', () => {
 | 
					  describe('getCookies', () => {
 | 
				
			||||||
    it('should generate the cookie headers', async () => {
 | 
					    it('should generate the cookie headers (secure)', async () => {
 | 
				
			||||||
      jwtServiceMock.sign.mockImplementation((value) => value as string);
 | 
					      jwtServiceMock.sign.mockImplementation((value) => value as string);
 | 
				
			||||||
      const dto = { accessToken: 'test-user@immich.com', userId: 'test-user' };
 | 
					      const dto = { accessToken: 'test-user@immich.com', userId: 'test-user' };
 | 
				
			||||||
      const cookies = await sut.getCookies(dto as LoginResponseDto, AuthType.PASSWORD);
 | 
					      const cookies = sut.getCookies(dto as LoginResponseDto, AuthType.PASSWORD, true);
 | 
				
			||||||
      expect(cookies).toEqual([
 | 
					      expect(cookies).toEqual([
 | 
				
			||||||
        'immich_access_token=test-user@immich.com; HttpOnly; Path=/; Max-Age=604800',
 | 
					        'immich_access_token=test-user@immich.com; Secure; Path=/; Max-Age=604800; SameSite=Strict;',
 | 
				
			||||||
        'immich_auth_type=password; Path=/; Max-Age=604800',
 | 
					        'immich_auth_type=password; Secure; Path=/; Max-Age=604800; SameSite=Strict;',
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should generate the cookie headers (insecure)', () => {
 | 
				
			||||||
 | 
					      jwtServiceMock.sign.mockImplementation((value) => value as string);
 | 
				
			||||||
 | 
					      const dto = { accessToken: 'test-user@immich.com', userId: 'test-user' };
 | 
				
			||||||
 | 
					      const cookies = sut.getCookies(dto as LoginResponseDto, AuthType.PASSWORD, false);
 | 
				
			||||||
 | 
					      expect(cookies).toEqual([
 | 
				
			||||||
 | 
					        'immich_access_token=test-user@immich.com; HttpOnly; Path=/; Max-Age=604800 SameSite=Strict;',
 | 
				
			||||||
 | 
					        'immich_auth_type=password; HttpOnly; Path=/; Max-Age=604800; SameSite=Strict;',
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -22,11 +22,19 @@ export class ImmichJwtService {
 | 
				
			|||||||
    return [IMMICH_ACCESS_COOKIE, IMMICH_AUTH_TYPE_COOKIE];
 | 
					    return [IMMICH_ACCESS_COOKIE, IMMICH_AUTH_TYPE_COOKIE];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public getCookies(loginResponse: LoginResponseDto, authType: AuthType) {
 | 
					  public getCookies(loginResponse: LoginResponseDto, authType: AuthType, isSecure: boolean) {
 | 
				
			||||||
    const maxAge = 7 * 24 * 3600; // 7 days
 | 
					    const maxAge = 7 * 24 * 3600; // 7 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge}`;
 | 
					    let accessTokenCookie = '';
 | 
				
			||||||
    const authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; Path=/; Max-Age=${maxAge}`;
 | 
					    let authTypeCookie = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isSecure) {
 | 
				
			||||||
 | 
					      accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; Secure; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
 | 
				
			||||||
 | 
					      authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; Secure; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge} SameSite=Strict;`;
 | 
				
			||||||
 | 
					      authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return [accessTokenCookie, authTypeCookie];
 | 
					    return [accessTokenCookie, authTypeCookie];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user