remember me with max age 30 days :).

This commit is contained in:
2026-07-02 17:59:49 +03:30
parent 653d67e15b
commit 2f95559087
6 changed files with 120 additions and 36 deletions

View File

@@ -35,6 +35,8 @@ import { UpdateLanguageDto } from './dto/update-language.dto';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
private static readonly REMEMBER_ME_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
constructor(private readonly authService: AuthService) {}
// =========================
@@ -56,9 +58,14 @@ export class AuthController {
console.log('Login endpoint hit');
const result = await this.authService.login(loginDto, req.user);
const rememberMe = Boolean(loginDto.rememberMe);
// ✅ SET COOKIES HERE
this.setAuthCookies(res, result.data.accessToken, result.data.refreshToken);
this.setAuthCookies(
res,
result.data.accessToken,
result.data.refreshToken,
rememberMe,
);
return {
success: true,
@@ -113,8 +120,11 @@ export class AuthController {
organizationId
);
// 🔥 Replace access token with org-scoped token
this.setAccessToken(res, result.data.accessToken);
this.setAccessToken(
res,
result.data.accessToken,
this.isPersistentSession(req),
);
return {
success: true,
@@ -191,7 +201,11 @@ export class AuthController {
const result = await this.authService.refreshToken(refreshToken);
this.setAccessToken(res, result.data.accessToken);
this.setAccessToken(
res,
result.data.accessToken,
this.isPersistentSession(req),
);
return {
success: true,
@@ -235,45 +249,64 @@ export class AuthController {
// =========================
// 🔥 COOKIE HELPERS
// =========================
private isPersistentSession(req: { cookies?: Record<string, string> }): boolean {
return req?.cookies?.authRemember === '1';
}
private baseCookieOptions() {
return {
httpOnly: true,
secure: false, // ⚠️ true in production (HTTPS)
sameSite: 'lax' as const,
path: '/',
};
}
private setAuthCookies(
res: Response,
accessToken: string,
refreshToken: string
refreshToken: string,
rememberMe = false,
) {
this.setAccessToken(res, accessToken);
this.setRefreshToken(res, refreshToken);
this.setAccessToken(res, accessToken, rememberMe);
this.setRefreshToken(res, refreshToken, rememberMe);
this.setRememberMeFlag(res, rememberMe);
}
private setAccessToken(res: Response, token: string) {
private setAccessToken(res: Response, token: string, rememberMe = false) {
res.cookie('accessToken', token, {
httpOnly: true,
secure: false, // ⚠️ true in production (HTTPS)
sameSite: 'lax',
path: '/',
...this.baseCookieOptions(),
...(rememberMe
? { maxAge: AuthController.REMEMBER_ME_MAX_AGE_MS }
: {}),
});
}
private setRefreshToken(res: Response, token: string) {
private setRefreshToken(res: Response, token: string, rememberMe = false) {
res.cookie('refreshToken', token, {
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
...this.baseCookieOptions(),
...(rememberMe
? { maxAge: AuthController.REMEMBER_ME_MAX_AGE_MS }
: {}),
});
}
private setRememberMeFlag(res: Response, rememberMe: boolean) {
if (rememberMe) {
res.cookie('authRemember', '1', {
...this.baseCookieOptions(),
maxAge: AuthController.REMEMBER_ME_MAX_AGE_MS,
});
return;
}
res.clearCookie('authRemember', this.baseCookieOptions());
}
private clearAuthCookies(res: Response) {
res.clearCookie('accessToken', {
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
});
res.clearCookie('refreshToken', {
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
});
const options = this.baseCookieOptions();
res.clearCookie('accessToken', options);
res.clearCookie('refreshToken', options);
res.clearCookie('authRemember', options);
}
}

View File

@@ -1,5 +1,5 @@
// backend/src/modules/auth/dto/login.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
import { IsBoolean, IsEmail, IsOptional, IsString, MinLength } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class LoginDto {
@@ -20,4 +20,13 @@ export class LoginDto {
@IsString()
@MinLength(6, { message: 'Password must be at least 6 characters long' })
password: string;
@ApiProperty({
description: 'Keep the user signed in for 30 days on this device',
required: false,
default: false,
})
@IsOptional()
@IsBoolean()
rememberMe?: boolean;
}