1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-24 08:52:28 +02: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:
Skyler Mäntysaari 2023-01-21 18:16:53 +02:00 committed by GitHub
parent c0a6b3d5a3
commit 5262e92b9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 32 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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