Skip to content

Commit 3644c0d

Browse files
committed
fix: auth throws
1 parent c33bef6 commit 3644c0d

11 files changed

Lines changed: 144 additions & 164 deletions

src/auth/application/controllers/oauth/controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class OAuthController {
5656
@SkipContract()
5757
async oauthCallback(
5858
@Query() query: { code?: string; state?: string },
59-
@Param('provider') provider: 'google' | 'yandex' | 'github' | 'vkontakte',
59+
@Param('provider') provider: 'google' | 'yandex' | 'github',
6060
@Res({ passthrough: true }) res: FastifyReply,
6161
@Req() req: FastifyRequest,
6262
) {
@@ -79,7 +79,7 @@ export class OAuthController {
7979

8080
const result = await this.facade.authenticateOAuth(dto, meta, state);
8181

82-
if (result.isSign) {
82+
if (!result.isConnect) {
8383
res.redirect(`${baseUrl}/oauth?${result.query.toString()}`, 302);
8484
} else {
8585
res.redirect(`${baseUrl}/user/profile?${result.query.toString()}`, 302);

src/auth/application/dtos/oauth.dto.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const OAuthResponseSchema = z.object({
99
avatar_url: z.string().nullish(),
1010
bio: z.string().nullish(),
1111
sex: z.enum(['male', 'female']).or(z.string()),
12-
provider: z.enum(['google', 'yandex', 'github', 'vkontakte']),
12+
provider: z.enum(['google', 'yandex', 'github']),
1313
});
1414

1515
export class OAuthResponse extends createZodDto(OAuthResponseSchema) {}
@@ -71,13 +71,9 @@ export const ExchangeSchema = z.object({
7171

7272
export class ExchangeDto extends createZodDto(ExchangeSchema) {}
7373

74-
export interface IOAuthExchangeData {
75-
userId: string;
76-
isNewUser: boolean;
77-
email: string;
78-
provider: 'google' | 'yandex' | 'github' | 'vkontakte';
79-
ip: string;
80-
}
74+
export type IOAuthExchangeData = z.infer<typeof OAuthResponseSchema> & {
75+
ip: string | null;
76+
};
8177

8278
export const ExchangeResponseSchema = z.object({
8379
success: z.boolean().describe('Успешность операции'),
@@ -91,7 +87,6 @@ export const ExchangeResponseSchema = z.object({
9187
.min(10, 'access токен слишком короткий')
9288
.max(500, 'access токен слишком длинный')
9389
.describe('JWT access токен'),
94-
isNewUser: z.boolean().describe('Новый пользователь?'),
9590
provider: z
9691
.enum(['google', 'yandex', 'github', 'vkontakte'], {
9792
message: 'provider должен быть: google, yandex, github или vkontakte',

src/auth/application/use-cases/auth/sign-up.use-case.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export class SignUpUseCase {
4040
}
4141

4242
try {
43-
await this.findUserQ.execute({ email: dto.email }, { throwIfExists: true });
43+
await this.findUserQ.execute(
44+
{ email: dto.email },
45+
{ throwIfExists: true, throwIfNotFound: false },
46+
);
4447

4548
const hashPass = await argon.hash(dto.password);
4649

src/auth/application/use-cases/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { DisconnectProviderUseCase } from './oauth/disconnect-provider.use-case'
1111
import { ExchangeUseCase } from './oauth/exchange.use-case';
1212
import { GetConnectedProvidersQuery } from './oauth/get-connected-providers.query';
1313
import { GetEnabledProvidersQuery } from './oauth/get-enabled-providers.query';
14-
import { OAuthOrchestratorUseCase } from './oauth/oauth-orchestrator.use-case';
1514
import { ProcessOAuthLoginUseCase } from './oauth/process-oauth-login.use-case';
1615
import { ProcessOAuthRegistrationUseCase } from './oauth/process-oauth-registration.use-case';
1716
import { ConfirmResetPasswordUseCase } from './password/confirm-reset-password.use-case';
@@ -24,7 +23,6 @@ export const AuthUseCases = [
2423
GetConnectedProvidersQuery,
2524
DisconnectProviderUseCase,
2625
GetEnabledProvidersQuery,
27-
OAuthOrchestratorUseCase,
2826
ProcessOAuthLoginUseCase,
2927
ProcessOAuthRegistrationUseCase,
3028
ConnectOAuthProviderUseCase,
@@ -53,7 +51,6 @@ export * from './auth/sign-up-verify.use-case';
5351
export * from './oauth/get-enabled-providers.query';
5452
export * from './oauth/exchange.use-case';
5553

56-
export * from './oauth/oauth-orchestrator.use-case';
5754
export * from './oauth/process-oauth-login.use-case';
5855
export * from './oauth/process-oauth-registration.use-case';
5956
export * from './oauth/connect-oauth-provider.use-case';
Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,46 @@
1-
import crypto from 'node:crypto';
1+
import { Injectable } from '@nestjs/common';
2+
import { isBaseExceptionWithCode } from '@shared/error';
23

3-
import { Inject, Injectable } from '@nestjs/common';
4-
import { CACHE_SERVICE } from '@shared/adapters/cache/constants';
5-
import { ICacheService } from '@shared/adapters/cache/ports';
4+
import { OAuthErrorCodes } from '../../../domain/errors';
65

7-
import { EXCHANGE_TOKEN_NAME, EXCHANGE_TOKEN_TTL } from '../../../infrastructure/constants';
8-
9-
import { OAuthOrchestratorUseCase } from './oauth-orchestrator.use-case';
6+
import { ConnectOAuthProviderUseCase } from './connect-oauth-provider.use-case';
7+
import { ProcessOAuthLoginUseCase } from './process-oauth-login.use-case';
8+
import { ProcessOAuthRegistrationUseCase } from './process-oauth-registration.use-case';
109

1110
import type { OAuthResponse } from '../../dtos';
1211
import type { DeviceMetadata } from '@core/auth/infrastructure/utils';
1312

1413
@Injectable()
1514
export class AuthenticateOAuthUseCase {
1615
constructor(
17-
@Inject(CACHE_SERVICE)
18-
private readonly cacheService: ICacheService,
19-
private readonly orchestrator: OAuthOrchestratorUseCase,
16+
private readonly processLogin: ProcessOAuthLoginUseCase,
17+
private readonly connectProvider: ConnectOAuthProviderUseCase,
18+
private readonly processRegistration: ProcessOAuthRegistrationUseCase,
2019
) {}
2120

2221
async execute(dto: OAuthResponse, meta: DeviceMetadata, state?: string) {
23-
const { user, isNewUser, isConnect } = await this.orchestrator.execute(dto, state);
24-
25-
if (isConnect) {
26-
const query = new URLSearchParams({
27-
success: 'true',
28-
message: `Провайдер ${dto.provider} успешно привязан`,
29-
});
30-
31-
return {
32-
query,
33-
isSign: false,
34-
refresh: null,
35-
expiresAt: null,
36-
};
22+
if (state) {
23+
try {
24+
return this.connectProvider.execute(dto, state);
25+
} catch (error) {
26+
if (!isBaseExceptionWithCode(error, 'INVALID_ACTION')) {
27+
throw error;
28+
}
29+
}
3730
}
38-
const token = crypto.randomBytes(32).toString('hex');
39-
40-
const data = {
41-
userId: user.id,
42-
isNewUser,
43-
email: user.email,
44-
provider: dto.provider,
45-
ip: meta.ip,
46-
};
47-
48-
await this.cacheService.setOne(
49-
EXCHANGE_TOKEN_NAME(token),
50-
JSON.stringify(data),
51-
EXCHANGE_TOKEN_TTL,
52-
);
53-
54-
const query = new URLSearchParams({
55-
token,
56-
success: 'true',
31+
32+
const login = await this.processLogin.execute(dto).catch((err) => {
33+
if (isBaseExceptionWithCode(err, OAuthErrorCodes.OAUTH_LOGIN_NOT_FOUND)) {
34+
return null;
35+
}
36+
37+
throw err;
5738
});
5839

59-
return { query, isSign: true };
40+
if (login) {
41+
return login;
42+
}
43+
44+
return this.processRegistration.execute(dto, meta);
6045
}
6146
}

src/auth/application/use-cases/oauth/connect-oauth-provider.use-case.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class ConnectOAuthProviderUseCase {
3030
await this.identityRepo.create({
3131
userId: user.id,
3232
avatarUrl: dto.avatar_url,
33-
provider: dto.provider as any,
33+
provider: dto.provider,
3434
providerUserId: dto.id,
3535
email: dto.email,
3636
});
@@ -40,7 +40,11 @@ export class ConnectOAuthProviderUseCase {
4040
`oauth:state:${state}`,
4141
]);
4242

43-
return { user, isConnect: true, isNewUser: false };
43+
const query = new URLSearchParams({
44+
success: 'true',
45+
});
46+
47+
return { isConnect: true, query };
4448
}
4549

4650
private async getStateData(state: string) {

src/auth/application/use-cases/oauth/exchange.use-case.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import { RegisterUserUseCase } from '@core/user';
2+
import { InjectQueue } from '@nestjs/bullmq';
13
import { HttpStatus, Inject, Injectable } from '@nestjs/common';
24
import { createId } from '@paralleldrive/cuid2';
35
import { CACHE_SERVICE } from '@shared/adapters/cache/constants';
46
import { ICacheService } from '@shared/adapters/cache/ports';
57
import { BaseException } from '@shared/error';
8+
import { Queue } from 'bullmq';
69

10+
import { AuthQueues, AuthUserJobs } from '../../../domain/enums';
711
import { OAuthErrorCodes, OAuthErrorMessages } from '../../../domain/errors';
8-
import { ISessionRepository } from '../../../domain/repository';
12+
import { CreateUserWorkspaceEvent } from '../../../domain/events';
13+
import { IIdentityRepository, ISessionRepository } from '../../../domain/repository';
914
import { EXCHANGE_TOKEN_NAME } from '../../../infrastructure/constants';
1015
import { TokenService } from '../../../infrastructure/security';
1116
import { ExchangeDto, type IOAuthExchangeData } from '../../dtos';
@@ -19,7 +24,12 @@ export class ExchangeUseCase {
1924
private readonly sessionRepo: ISessionRepository,
2025
@Inject(CACHE_SERVICE)
2126
private readonly cacheService: ICacheService,
27+
@InjectQueue(AuthQueues.AUTH_USER)
28+
private readonly queue: Queue,
29+
@Inject('IIdentityRepository')
30+
private readonly identityRepo: IIdentityRepository,
2231
private readonly tokenService: TokenService,
32+
private readonly registerUserUC: RegisterUserUseCase,
2333
) {}
2434

2535
async execute(dto: ExchangeDto, meta: DeviceMetadata) {
@@ -40,7 +50,7 @@ export class ExchangeUseCase {
4050
const data: IOAuthExchangeData = JSON.parse(rawData);
4151
await this.cacheService.removeOne(key);
4252

43-
if (!data.userId || !data.email || data.provider !== dto.provider) {
53+
if (!data.email || data.provider) {
4454
await this.cacheService.removeOne(key);
4555
throw new BaseException(
4656
{
@@ -51,18 +61,47 @@ export class ExchangeUseCase {
5161
);
5262
}
5363

64+
if (data.provider !== dto.provider) {
65+
throw new BaseException(
66+
{
67+
code: OAuthErrorCodes.EXCHANGE_TOKEN_INVALID,
68+
message: OAuthErrorMessages[OAuthErrorCodes.EXCHANGE_TOKEN_INVALID],
69+
},
70+
HttpStatus.BAD_REQUEST,
71+
);
72+
}
73+
5474
try {
75+
const user = await this.registerUserUC.execute({
76+
email: data.email,
77+
firstName: data.first_name || 'User',
78+
lastName: data.last_name ?? '',
79+
password: null,
80+
bio: data.bio,
81+
gender: data.sex === 'male' ? 'male' : data.sex === 'female' ? 'female' : 'none',
82+
avatarUrl: data.avatar_url,
83+
});
84+
85+
await this.identityRepo.create({
86+
userId: user.id,
87+
avatarUrl: data.avatar_url,
88+
provider: data.provider,
89+
providerUserId: data.id,
90+
email: data.email,
91+
});
92+
5593
const sessionId = createId();
94+
5695
const { access, expiresAt, refresh } = await this.tokenService.generateTokens(
57-
{ id: data.userId, email: data.email },
96+
{ id: user.id, email: user.email },
5897
sessionId,
5998
);
6099

61100
const result = await this.sessionRepo.create({
62101
id: sessionId,
63102
...meta,
64103
expiresAt: expiresAt.toISOString(),
65-
userId: data.userId,
104+
userId: user.id,
66105
});
67106

68107
if (!result?.id) {
@@ -75,11 +114,13 @@ export class ExchangeUseCase {
75114
);
76115
}
77116

117+
const event = new CreateUserWorkspaceEvent(user.id, user.firstName);
118+
await this.queue.add(AuthUserJobs.CREATE_WORKSPACE, event);
119+
78120
return {
79121
success: true,
80122
message: 'Вход выполнен успешно',
81123
access,
82-
isNewUser: data.isNewUser,
83124
provider: data.provider,
84125
refresh,
85126
expiresAt,

src/auth/application/use-cases/oauth/oauth-orchestrator.use-case.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)