Skip to content

Commit 6169299

Browse files
authored
Merge pull request #69 from teacoder-team/dev
Dev
2 parents a4a7be8 + 077e97a commit 6169299

31 files changed

Lines changed: 587 additions & 95 deletions

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
},
2929
"dependencies": {
3030
"@aws-sdk/client-s3": "^3.693.0",
31+
"@fingerprintjs/fingerprintjs-pro-server-api": "^6.10.0",
32+
"@minilibs/ip2geo": "^0.0.6",
3133
"@nest-lab/throttler-storage-redis": "^1.1.0",
3234
"@nestjs-modules/mailer": "^2.0.2",
3335
"@nestjs/axios": "^4.0.0",
@@ -55,12 +57,14 @@
5557
"ejs": "^3.1.10",
5658
"form-data": "4.0.4",
5759
"geoip-country": "^5.0.202412202341",
60+
"geoip-lite": "^1.4.10",
5861
"handlebars": "^4.7.8",
5962
"helmet": "^8.0.0",
6063
"hi-base32": "^0.5.1",
6164
"ioredis": "^5.4.1",
6265
"ip-cidr": "^4.0.2",
6366
"liquidjs": "^10.23.0",
67+
"location-from-ip": "^1.0.1",
6468
"mjml": "^4.16.1",
6569
"nalog.ru": "^1.0.1",
6670
"nestjs-cloudflare-captcha": "^0.0.10",

src/api/api.module.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import { ScheduleModule } from '@nestjs/schedule'
55
import { ThrottlerModule } from '@nestjs/throttler'
66
import { TurnstileModule } from 'nestjs-cloudflare-captcha'
77

8-
import { getThrottlerConfig, getTurnstileConfig } from '@/config'
8+
import {
9+
getFingerprintConfig,
10+
getThrottlerConfig,
11+
getTurnstileConfig
12+
} from '@/config'
13+
import { FingerprintModule } from '@/libs/fingerprint/fingerprint.module'
914
import { EnhancedThrottlerGuard } from '@/shared/guards'
1015

1116
import { AccountModule } from './auth/account/account.module'
@@ -32,6 +37,11 @@ import { UsersModule } from './users/users.module'
3237
useFactory: getTurnstileConfig,
3338
inject: [ConfigService]
3439
}),
40+
FingerprintModule.forRootAsync({
41+
imports: [ConfigModule],
42+
useFactory: getFingerprintConfig,
43+
inject: [ConfigService]
44+
}),
3545
ScheduleModule.forRoot(),
3646
AccountModule,
3747
SsoModule,

src/api/auth/account/account.service.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class AccountService {
6666
}
6767

6868
public async create(dto: CreateUserRequest, ip: string, userAgent: string) {
69-
const { name, email, password } = dto
69+
const { name, email, password, visitorId, requestId } = dto
7070

7171
// const { valid } = await validate(email)
7272

@@ -93,11 +93,12 @@ export class AccountService {
9393
}
9494
})
9595

96-
const session = await this.redisService.createSession(
97-
user,
96+
const session = await this.redisService.createSession(user, {
9897
ip,
99-
userAgent
100-
)
98+
userAgent,
99+
visitorId: visitorId ?? null,
100+
requestId: requestId ?? null
101+
})
101102

102103
const userSession = await this.redisService.getUserSession(session.id)
103104

src/api/auth/account/dto/create-user.dto.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'
22
import {
33
IsEmail,
44
IsNotEmpty,
5+
IsOptional,
56
IsString,
67
MaxLength,
78
MinLength
@@ -51,6 +52,24 @@ export class CreateUserRequest {
5152
@IsString({ message: 'Капча должна быть строкой' })
5253
@IsNotEmpty({ message: 'Капча обязательна' })
5354
public captcha: string
55+
56+
@ApiProperty({
57+
description: 'Fingerprint visitor ID',
58+
example: 'g8GhE1JtVZ9xYkLm',
59+
required: false
60+
})
61+
@IsString({ message: 'visitorId должен быть строкой' })
62+
@IsOptional()
63+
public visitorId?: string
64+
65+
@ApiProperty({
66+
description: 'Fingerprint request ID',
67+
example: 'dbe7b3b8-22f4-4b89-9db9-f8e3798a2b1e',
68+
required: false
69+
})
70+
@IsString({ message: 'requestId должен быть строкой' })
71+
@IsOptional()
72+
public requestId?: string
5473
}
5574

5675
export class CreateUserResponse extends LoginSessionResponse {}

src/api/auth/mfa/mfa.service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,12 @@ export class MfaService {
299299
)
300300
}
301301

302-
const session = await this.redisService.createSession(
303-
user,
302+
const session = await this.redisService.createSession(user, {
304303
ip,
305-
userAgent
306-
)
304+
userAgent,
305+
visitorId: ticket.visitorId ?? null,
306+
requestId: ticket.requestId ?? null
307+
})
307308

308309
await this.redisService.del(`mfa_tickets:${dto.ticket}`)
309310

src/api/auth/session/dto/login.dto.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger'
22
import {
33
IsEmail,
44
IsNotEmpty,
5+
IsOptional,
56
IsString,
67
MaxLength,
78
MinLength
@@ -42,6 +43,24 @@ export class LoginRequest {
4243
@IsString({ message: 'Капча должна быть строкой' })
4344
@IsNotEmpty({ message: 'Капча обязательна' })
4445
public captcha: string
46+
47+
@ApiProperty({
48+
description: 'Fingerprint visitor ID',
49+
example: 'g8GhE1JtVZ9xYkLm',
50+
required: false
51+
})
52+
@IsString({ message: 'visitorId должен быть строкой' })
53+
@IsOptional()
54+
public visitorId?: string
55+
56+
@ApiProperty({
57+
description: 'Fingerprint request ID',
58+
example: 'dbe7b3b8-22f4-4b89-9db9-f8e3798a2b1e',
59+
required: false
60+
})
61+
@IsString({ message: 'requestId должен быть строкой' })
62+
@IsOptional()
63+
public requestId?: string
4564
}
4665

4766
export class LoginSessionResponse implements Session {

src/api/auth/session/session.service.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ import { LoginRequest } from './dto'
1717
export class SessionService {
1818
public constructor(
1919
private readonly prismaService: PrismaService,
20-
private readonly redisService: RedisService,
21-
private readonly configService: ConfigService
20+
private readonly redisService: RedisService
2221
) {}
2322

2423
public async login(dto: LoginRequest, ip: string, userAgent: string) {
25-
const { email, password } = dto
24+
const { email, password, requestId, visitorId } = dto
2625

2726
const user = await this.prismaService.user.findFirst({
2827
where: {
@@ -64,17 +63,22 @@ export class SessionService {
6463

6564
const ticket = await this.redisService.createMfaTicket(
6665
user.id,
67-
allowedMethods
66+
allowedMethods,
67+
{
68+
visitorId,
69+
requestId
70+
}
6871
)
6972

7073
return ticket
7174
}
7275

73-
const session = await this.redisService.createSession(
74-
user,
76+
const session = await this.redisService.createSession(user, {
7577
ip,
76-
userAgent
77-
)
78+
userAgent,
79+
visitorId: visitorId ?? null,
80+
requestId: requestId ?? null
81+
})
7882

7983
return session
8084
}
@@ -123,11 +127,10 @@ export class SessionService {
123127
return ticket
124128
}
125129

126-
const session = await this.redisService.createSession(
127-
admin,
130+
const session = await this.redisService.createSession(admin, {
128131
ip,
129132
userAgent
130-
)
133+
})
131134

132135
return session
133136
}
@@ -232,8 +235,8 @@ export class SessionService {
232235
return {
233236
id: session.id,
234237
createdAt: userSession.createdAt,
235-
country: userSession.geo.name,
236-
city: userSession.geo.capital,
238+
country: userSession.geo.country,
239+
city: userSession.geo.city,
237240
browser: userSession.browser.name,
238241
os: userSession.os.name
239242
}

src/api/auth/sso/dto/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './sso-connect.dto'
2+
export * from './sso-login.dto'
23
export * from './sso-status.dto'
34
export * from './telegram-auth.dto'
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ApiProperty } from '@nestjs/swagger'
2+
import { IsOptional, IsString } from 'class-validator'
3+
4+
export class SsoLoginRequest {
5+
@ApiProperty({
6+
description: 'Visitor fingerprint ID',
7+
example: 'visitor-123456'
8+
})
9+
@IsOptional()
10+
@IsString()
11+
public visitorId?: string
12+
13+
@ApiProperty({
14+
description: 'Request ID for fingerprint tracking',
15+
example: 'req-987654'
16+
})
17+
@IsOptional()
18+
@IsString()
19+
public requestId?: string
20+
}
21+
22+
export class SsoLoginResponse {
23+
@ApiProperty({
24+
description:
25+
'The URL for authorization via the external provider (e.g., Google, GitHub)',
26+
example:
27+
'https://auth.example.com/oauth/authorize?client_id=xyz123&redirect_uri=https://example.com/callback'
28+
})
29+
public url: string
30+
}

src/api/auth/sso/dto/telegram-auth.dto.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ export class TelegramAuthRequest {
5959
})
6060
@IsString()
6161
public hash: string
62+
63+
@ApiProperty({
64+
description: 'Visitor fingerprint ID',
65+
example: 'visitor-123456'
66+
})
67+
@IsOptional()
68+
@IsString()
69+
public visitorId?: string
70+
71+
@ApiProperty({
72+
description: 'Request ID for fingerprint tracking',
73+
example: 'req-987654'
74+
})
75+
@IsOptional()
76+
@IsString()
77+
public requestId?: string
6278
}
6379

6480
export class TelegramAuthResponse extends LoginSessionResponse {}

0 commit comments

Comments
 (0)