Skip to content

Commit 85fe2d9

Browse files
authored
hotfix(mobile): refresh token for mobile
* fix(socket): add path for socket * fix(mobile): refresh token
1 parent cdab144 commit 85fe2d9

File tree

6 files changed

+91
-17
lines changed

6 files changed

+91
-17
lines changed

src/auth/auth.controller.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ import {
9393
verify_update_email_swagger,
9494
} from './auth.swagger';
9595
import { ConfirmPasswordDto } from './dto/confirm-password.dto';
96+
import { RefreshTokenDto } from './dto/refresh-token.dto';
97+
import { LogoutDto } from './dto/logout.dto';
9698

9799
@ApiTags('Authentication')
98100
@Controller('auth')
@@ -144,7 +146,7 @@ export class AuthController {
144146
const { user, access_token, refresh_token } = await this.auth_service.signupStep3(dto);
145147

146148
this.httpOnlyRefreshToken(response, refresh_token);
147-
return { user, access_token };
149+
return { user, access_token, refresh_token };
148150
}
149151

150152
@ApiOperation(login_swagger.operation)
@@ -159,7 +161,7 @@ export class AuthController {
159161
const { access_token, refresh_token, user } = await this.auth_service.login(login_dto);
160162

161163
this.httpOnlyRefreshToken(response, refresh_token);
162-
return { access_token, user };
164+
return { user, access_token, refresh_token };
163165
}
164166

165167
@ApiOperation(generate_otp_swagger.operation)
@@ -240,14 +242,19 @@ export class AuthController {
240242
@ApiBearerAuth('JWT-auth')
241243
@UseGuards(JwtAuthGuard)
242244
@ApiOperation(logout_swagger.operation)
245+
@ApiBody({ type: LogoutDto, required: false })
243246
@ApiCookieAuth('refresh_token')
244247
@ApiOkResponse(logout_swagger.responses.success)
245248
@ApiBadRequestErrorResponse(ERROR_MESSAGES.NO_REFRESH_TOKEN_PROVIDED)
246249
@ApiUnauthorizedErrorResponse(ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN)
247250
@ResponseMessage(SUCCESS_MESSAGES.LOGGED_OUT)
248251
@Post('logout')
249-
async logout(@Req() req: Request, @Res({ passthrough: true }) response: Response) {
250-
const refresh_token = req.cookies['refresh_token'];
252+
async logout(
253+
@Body() body: LogoutDto,
254+
@Req() req: Request,
255+
@Res({ passthrough: true }) response: Response
256+
) {
257+
const refresh_token = body.refresh_token || req.cookies['refresh_token'];
251258
if (!refresh_token) throw new BadRequestException('No refresh token provided');
252259
return await this.auth_service.logout(refresh_token, response);
253260
}
@@ -256,31 +263,42 @@ export class AuthController {
256263
@ApiCookieAuth('refresh_token')
257264
@UseGuards(JwtAuthGuard)
258265
@ApiOperation(logout_all_swagger.operation)
266+
@ApiBody({ type: LogoutDto, required: false })
259267
@ApiOkResponse(logout_all_swagger.responses.success)
260268
@ApiBadRequestErrorResponse(ERROR_MESSAGES.NO_REFRESH_TOKEN_PROVIDED)
261269
@ApiUnauthorizedErrorResponse(ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN)
262270
@ResponseMessage(SUCCESS_MESSAGES.LOGGED_OUT_ALL)
263271
@Post('logout-all')
264-
async logoutAll(@Req() req: Request, @Res({ passthrough: true }) response: Response) {
265-
const refresh_token = req.cookies['refresh_token'];
272+
async logoutAll(
273+
@Body() body: LogoutDto,
274+
@Req() req: Request,
275+
@Res({ passthrough: true }) response: Response
276+
) {
277+
const refresh_token = body.refresh_token || req.cookies['refresh_token'];
266278
if (!refresh_token) throw new BadRequestException('No refresh token provided');
267279
return await this.auth_service.logoutAll(refresh_token, response);
268280
}
269281

270282
@ApiOperation(refresh_token_swagger.operation)
283+
@ApiBody({ type: RefreshTokenDto, required: false })
284+
@ApiCookieAuth('refresh_token')
271285
@ApiOkResponse(refresh_token_swagger.responses.success)
272286
@ApiBadRequestErrorResponse(ERROR_MESSAGES.NO_REFRESH_TOKEN_PROVIDED)
273287
@ApiUnauthorizedErrorResponse(ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN)
274288
@ResponseMessage(SUCCESS_MESSAGES.NEW_ACCESS_TOKEN)
275289
@Post('refresh')
276-
async refresh(@Req() req: Request, @Res({ passthrough: true }) response: Response) {
277-
const refresh_token_cookie = req.cookies['refresh_token'];
278-
if (!refresh_token_cookie) throw new BadRequestException('No refresh token provided');
290+
async refresh(
291+
@Body() body: RefreshTokenDto,
292+
@Req() req: Request,
293+
@Res({ passthrough: true }) response: Response
294+
) {
295+
const refresh_token_input = body.refresh_token || req.cookies['refresh_token'];
296+
if (!refresh_token_input) throw new BadRequestException('No refresh token provided');
279297

280298
const { access_token, refresh_token } =
281-
await this.auth_service.refresh(refresh_token_cookie);
299+
await this.auth_service.refresh(refresh_token_input);
282300
this.httpOnlyRefreshToken(response, refresh_token);
283-
return { access_token };
301+
return { access_token, refresh_token };
284302
}
285303

286304
@ApiOperation(exchange_token_swagger.operation)
@@ -423,6 +441,7 @@ export class AuthController {
423441

424442
return {
425443
access_token,
444+
refresh_token,
426445
user: user,
427446
};
428447
}
@@ -576,6 +595,7 @@ export class AuthController {
576595

577596
return {
578597
access_token,
598+
refresh_token,
579599
user: user,
580600
};
581601
}
@@ -652,7 +672,7 @@ export class AuthController {
652672
await this.auth_service.oauthCompletionStep2(dto);
653673

654674
this.httpOnlyRefreshToken(response, refresh_token);
655-
return { access_token, user };
675+
return { access_token, refresh_token, user };
656676
}
657677

658678
@ApiBearerAuth('JWT-auth')

src/auth/auth.swagger.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const OAUTH_RESPONSE_EXISTING_USER = {
2727
following: 150,
2828
},
2929
access_token: "messi doesn't need a token to be authenticated bro",
30+
refresh_token:
31+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImp0aSI6IjEyMzQ1Njc4LTkwYWItY2RlZi0xMjM0LTU2Nzg5MGFiY2RlZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4NzUyNjY5fQ.abc123',
3032
},
3133
count: 1,
3234
message: SUCCESS_MESSAGES.LOGGED_IN,
@@ -168,6 +170,8 @@ Complete your registration by setting a password, choosing a username, and optio
168170
},
169171
access_token:
170172
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4MTUxNDY5fQ.DV3oA5Fn-cj-KHrGcafGaoWGyvYFx4N50L9Ke4_n6OU',
173+
refresh_token:
174+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImp0aSI6IjEyMzQ1Njc4LTkwYWItY2RlZi0xMjM0LTU2Nzg5MGFiY2RlZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4NzUyNjY5fQ.abc123',
171175
},
172176
count: 1,
173177
message: SUCCESS_MESSAGES.SIGNUP_STEP3_COMPLETED,
@@ -238,7 +242,7 @@ export const login_swagger = {
238242
operation: {
239243
summary: 'User login',
240244
description:
241-
'Authenticate user and receive access token. Refresh token is set as httpOnly cookie.',
245+
'Authenticate user and receive access token and refresh token. Refresh token is also set as httpOnly cookie for web clients.',
242246
},
243247

244248
responses: {
@@ -249,6 +253,8 @@ export const login_swagger = {
249253
data: {
250254
access_token:
251255
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4MTUxNDY5fQ.DV3oA5Fn-cj-KHrGcafGaoWGyvYFx4N50L9Ke4_n6OU',
256+
refresh_token:
257+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImp0aSI6IjEyMzQ1Njc4LTkwYWItY2RlZi0xMjM0LTU2Nzg5MGFiY2RlZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4NzUyNjY5fQ.abc123',
252258
user: {
253259
id: 'd102dadc-0b17-4e83-812b-00103b606a1f',
254260
email: 'amirakhaled928@gmail.com',
@@ -285,17 +291,20 @@ export const login_swagger = {
285291
export const refresh_token_swagger = {
286292
operation: {
287293
summary: 'Refresh access token',
288-
description: 'Use refresh token from httpOnly cookie to get a new access token.',
294+
description:
295+
'Use refresh token from httpOnly cookie or request body to get a new access token. For mobile apps, provide refresh_token in the request body.',
289296
},
290297

291298
responses: {
292299
success: {
293-
description: 'New access token generated',
300+
description: 'New access token and refresh token generated',
294301
schema: {
295302
example: {
296303
data: {
297304
access_token:
298305
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNjZTM3YjEzLWQ2ZGUtNDhjZC1iNzQ2LWRmMjY0ODQ1N2E0NiIsImlhdCI6MTc1ODE0OTI2NywiZXhwIjoxNzU4MTUyODY3fQ.M1ennV-LC8xiJpsRKCsUo9Y4o7_6mydG0SPURuNzh6I',
306+
refresh_token:
307+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNjZTM3YjEzLWQ2ZGUtNDhjZC1iNzQ2LWRmMjY0ODQ1N2E0NiIsImp0aSI6Ijk4NzY1NDMyLTEwYWItY2RlZi0xMjM0LTU2Nzg5MGFiY2RlZiIsImlhdCI6MTc1ODE0OTI2NywiZXhwIjoxNzU4NzU0MDY3fQ.xyz789',
299308
},
300309
count: 1,
301310
message: SUCCESS_MESSAGES.NEW_ACCESS_TOKEN,
@@ -975,7 +984,7 @@ export const oauth_completion_step2_swagger = {
975984
summary: 'OAuth Step 2: Complete registration with username',
976985
description: `
977986
Complete OAuth registration step 2. Finalizes user account creation with the chosen username.
978-
Returns access token and user data. Refresh token is set as httpOnly cookie.
987+
Returns access token, refresh token, and user data. Refresh token is also set as httpOnly cookie for web clients.
979988
`,
980989
},
981990

@@ -987,6 +996,8 @@ export const oauth_completion_step2_swagger = {
987996
data: {
988997
access_token:
989998
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4MTUxNDY5fQ.DV3oA5Fn-cj-KHrGcafGaoWGyvYFx4N50L9Ke4_n6OU',
999+
refresh_token:
1000+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQxMDJkYWRjLTBiMTctNGU4My04MTJiLTAwMTAzYjYwNmExZiIsImp0aSI6IjEyMzQ1Njc4LTkwYWItY2RlZi0xMjM0LTU2Nzg5MGFiY2RlZiIsImlhdCI6MTc1ODE0Nzg2OSwiZXhwIjoxNzU4NzUyNjY5fQ.abc123',
9901001
user: {
9911002
id: 'd102dadc-0b17-4e83-812b-00103b606a1f',
9921003
email: 'mariorafat10@gmail.com',

src/auth/dto/logout.dto.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3+
4+
export class LogoutDto {
5+
@ApiProperty({
6+
description: 'Refresh token (optional - can be provided in body or cookie)',
7+
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
8+
required: false,
9+
})
10+
@IsOptional()
11+
@IsString()
12+
@IsNotEmpty()
13+
refresh_token?: string;
14+
}

src/auth/dto/refresh-token.dto.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsString } from 'class-validator';
3+
4+
export class RefreshTokenDto {
5+
@ApiProperty({
6+
description: 'Refresh token (optional - can be provided in body or cookie)',
7+
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
8+
required: false,
9+
})
10+
@IsString()
11+
@IsNotEmpty()
12+
refresh_token?: string;
13+
}

src/user/user.controller.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
BadRequestException,
23
Body,
34
Controller,
45
Delete,
@@ -7,10 +8,13 @@ import {
78
Patch,
89
Post,
910
Query,
11+
Req,
12+
Res,
1013
UploadedFile,
1114
UseGuards,
1215
UseInterceptors,
1316
} from '@nestjs/common';
17+
import type { Request, Response } from 'express';
1418
import { UserService } from './user.service';
1519
import {
1620
ApiBearerAuth,
@@ -119,7 +123,14 @@ export class UserController {
119123
@ApiNotFoundErrorResponse(ERROR_MESSAGES.USER_NOT_FOUND)
120124
@ResponseMessage(SUCCESS_MESSAGES.USER_RETRIEVED)
121125
@Get('me')
122-
async getMe(@GetUserId() user_id: string) {
126+
async getMe(
127+
@GetUserId() user_id: string,
128+
@Req() req: Request,
129+
@Query('refresh_token') refresh_token_query?: string
130+
) {
131+
const refresh_token = refresh_token_query || req.cookies['refresh_token'];
132+
if (!refresh_token) throw new BadRequestException('No refresh token provided');
133+
123134
return await this.user_service.getMe(user_id);
124135
}
125136

src/user/user.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ export class UserService {
118118
}
119119

120120
async getMe(user_id: string): Promise<UserProfileDto> {
121+
const user = await this.user_repository.findOne({ where: { id: user_id } });
122+
if (!user) {
123+
throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
124+
}
125+
121126
const result = await this.user_repository.getMyProfile(user_id);
122127

123128
return plainToInstance(UserProfileDto, result, {

0 commit comments

Comments
 (0)