Skip to content

Commit c4e3cf8

Browse files
sulnongmaslow
andauthored
feat(server): new authentication implements (#897)
* feat(authentication): new authentication implements * feat(authentication): impl bind username and phone * feat(authentication): adjust to code review feedbacks * feat(authentication): modify code according to cr-gpt --------- Co-authored-by: maslow <[email protected]>
1 parent a8f358c commit c4e3cf8

25 files changed

+1508
-7
lines changed

server/package-lock.json

+331-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
"test:e2e": "jest --config ./test/jest-e2e.json"
2626
},
2727
"dependencies": {
28+
"@alicloud/dysmsapi20170525": "^2.0.23",
29+
"@alicloud/openapi-client": "^0.4.5",
30+
"@alicloud/tea-util": "^1.4.5",
2831
"@aws-sdk/client-s3": "^3.245.0",
2932
"@aws-sdk/client-sts": "^3.226.0",
3033
"@kubernetes/client-node": "^0.17.1",

server/prisma/schema.prisma

+47-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,21 @@ model User {
2727
personalAccessTokens PersonalAccessToken[]
2828
}
2929

30+
model UserPassword {
31+
id String @id @default(auto()) @map("_id") @db.ObjectId
32+
uid String @db.ObjectId
33+
password String
34+
state String @default("Active") // Active, Inactive
35+
createdAt DateTime @default(now())
36+
updatedAt DateTime @updatedAt
37+
}
38+
3039
model UserProfile {
3140
id String @id @default(auto()) @map("_id") @db.ObjectId
3241
uid String @unique @db.ObjectId
3342
openid String?
3443
from String?
44+
openData Json?
3545
avatar String?
3646
name String?
3747
createdAt DateTime @default(now())
@@ -51,7 +61,7 @@ model PersonalAccessToken {
5161
user User @relation(fields: [uid], references: [id])
5262
}
5363

54-
// region schemas
64+
// region schemas
5565

5666
type RegionClusterConf {
5767
driver String // kubernetes
@@ -595,3 +605,39 @@ model WebsiteHosting {
595605
596606
bucket StorageBucket @relation(fields: [bucketName], references: [name])
597607
}
608+
609+
enum AuthProviderState {
610+
Enabled
611+
Disabled
612+
}
613+
614+
model AuthProvider {
615+
id String @id @default(auto()) @map("_id") @db.ObjectId
616+
name String @unique
617+
bind Json
618+
register Boolean
619+
default Boolean
620+
state AuthProviderState
621+
config Json
622+
}
623+
624+
// Sms schemas
625+
enum SmsVerifyCodeType {
626+
Signin
627+
Signup
628+
ResetPassword
629+
Bind
630+
Unbind
631+
ChangePhone
632+
}
633+
634+
model SmsVerifyCode {
635+
id String @id @default(auto()) @map("_id") @db.ObjectId
636+
phone String
637+
code String
638+
ip String
639+
type SmsVerifyCodeType
640+
state Int @default(0) // 0: created, 1: used
641+
createdAt DateTime @default(now())
642+
updatedAt DateTime @updatedAt
643+
}

server/src/auth/auth.module.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SmsService } from 'src/auth/phone/sms.service'
12
import { Global, Module } from '@nestjs/common'
23
import { JwtModule } from '@nestjs/jwt'
34
import { PassportModule } from '@nestjs/passport'
@@ -9,6 +10,12 @@ import { JwtStrategy } from './jwt.strategy'
910
import { AuthController } from './auth.controller'
1011
import { HttpModule } from '@nestjs/axios'
1112
import { PatService } from 'src/user/pat.service'
13+
import { UserPasswordController } from './user-passwd/user-password.controller'
14+
import { UserPasswordService } from './user-passwd/user-password.service'
15+
import { PhoneController } from './phone/phone.controller'
16+
import { PhoneService } from './phone/phone.service'
17+
import { AuthenticationController } from './authentication.controller'
18+
import { AuthenticationService } from './authentication.service'
1219

1320
@Global()
1421
@Module({
@@ -21,8 +28,22 @@ import { PatService } from 'src/user/pat.service'
2128
UserModule,
2229
HttpModule,
2330
],
24-
providers: [AuthService, JwtStrategy, CasdoorService, PatService],
31+
providers: [
32+
AuthService,
33+
JwtStrategy,
34+
CasdoorService,
35+
PatService,
36+
UserPasswordService,
37+
PhoneService,
38+
SmsService,
39+
AuthenticationService,
40+
],
2541
exports: [AuthService],
26-
controllers: [AuthController],
42+
controllers: [
43+
AuthController,
44+
UserPasswordController,
45+
PhoneController,
46+
AuthenticationController,
47+
],
2748
})
2849
export class AuthModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { AuthenticationService } from './authentication.service'
2+
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'
3+
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
4+
import { ResponseUtil } from 'src/utils/response'
5+
import { JwtAuthGuard } from './jwt.auth.guard'
6+
import { BindUsernameDto } from './dto/bind-username.dto'
7+
import { IRequest } from 'src/utils/interface'
8+
import { BindPhoneDto } from './dto/bind-phone.dto'
9+
import { SmsService } from './phone/sms.service'
10+
import { SmsVerifyCodeType } from '@prisma/client'
11+
import { UserService } from 'src/user/user.service'
12+
13+
@ApiTags('Authentication - New')
14+
@Controller('auth')
15+
export class AuthenticationController {
16+
constructor(
17+
private readonly authenticationService: AuthenticationService,
18+
private readonly smsService: SmsService,
19+
private readonly userService: UserService,
20+
) {}
21+
22+
/**
23+
* Auth providers
24+
*/
25+
@ApiOperation({ summary: 'Auth providers' })
26+
@ApiResponse({ type: ResponseUtil })
27+
@Get('providers')
28+
async getProviders() {
29+
const providers = await this.authenticationService.getProviders()
30+
return ResponseUtil.ok(providers)
31+
}
32+
33+
/**
34+
* Bind phone
35+
*/
36+
@ApiOperation({ summary: 'Bind username' })
37+
@ApiResponse({ type: ResponseUtil })
38+
@UseGuards(JwtAuthGuard)
39+
@Post('bind/phone')
40+
async bindPhone(@Body() dto: BindPhoneDto, @Req() req: IRequest) {
41+
const { phone, code } = dto
42+
// check code valid
43+
const err = await this.smsService.validCode(
44+
phone,
45+
code,
46+
SmsVerifyCodeType.Bind,
47+
)
48+
if (err) {
49+
return ResponseUtil.error(err)
50+
}
51+
52+
// check phone if have already been bound
53+
const user = await this.userService.find(phone)
54+
if (user) {
55+
return ResponseUtil.error('phone already been bound')
56+
}
57+
58+
// bind phone
59+
await this.userService.updateUser({
60+
where: {
61+
id: req.user.id,
62+
},
63+
data: {
64+
phone,
65+
},
66+
})
67+
}
68+
69+
/**
70+
* Bind username, not support bind existed username
71+
*/
72+
@ApiOperation({ summary: 'Bind username' })
73+
@ApiResponse({ type: ResponseUtil })
74+
@UseGuards(JwtAuthGuard)
75+
@Post('bind/username')
76+
async bindUsername(@Body() dto: BindUsernameDto, @Req() req: IRequest) {
77+
const { username, phone, code } = dto
78+
79+
// check code valid
80+
const err = await this.smsService.validCode(
81+
phone,
82+
code,
83+
SmsVerifyCodeType.Bind,
84+
)
85+
if (err) {
86+
return ResponseUtil.error(err)
87+
}
88+
89+
// check username if have already been bound
90+
const user = await this.userService.find(username)
91+
if (user) {
92+
return ResponseUtil.error('username already been bound')
93+
}
94+
95+
// bind username
96+
await this.userService.updateUser({
97+
where: {
98+
id: req.user.id,
99+
},
100+
data: {
101+
username,
102+
},
103+
})
104+
}
105+
}
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { JwtService } from '@nestjs/jwt'
2+
import { PrismaService } from 'src/prisma/prisma.service'
3+
import { Injectable, Logger } from '@nestjs/common'
4+
import { AuthProviderState, User } from '@prisma/client'
5+
import {
6+
PASSWORD_AUTH_PROVIDER_NAME,
7+
PHONE_AUTH_PROVIDER_NAME,
8+
} from 'src/constants'
9+
10+
@Injectable()
11+
export class AuthenticationService {
12+
logger: Logger = new Logger(AuthenticationService.name)
13+
constructor(
14+
private readonly prismaService: PrismaService,
15+
private readonly jwtService: JwtService,
16+
) {}
17+
18+
/**
19+
* Get all auth provides
20+
* @returns
21+
*/
22+
async getProviders() {
23+
return await this.prismaService.authProvider.findMany({
24+
where: { state: AuthProviderState.Enabled },
25+
select: {
26+
id: false,
27+
name: true,
28+
bind: true,
29+
register: true,
30+
default: true,
31+
state: true,
32+
config: false,
33+
},
34+
})
35+
}
36+
37+
async getPhoneProvider() {
38+
return await this.getProvider(PHONE_AUTH_PROVIDER_NAME)
39+
}
40+
41+
async getPasswdProvider() {
42+
return await this.getProvider(PASSWORD_AUTH_PROVIDER_NAME)
43+
}
44+
45+
// Get auth provider by name
46+
async getProvider(name: string) {
47+
return await this.prismaService.authProvider.findUnique({
48+
where: { name },
49+
})
50+
}
51+
52+
/**
53+
* Get access token by user
54+
* @param user
55+
* @returns
56+
*/
57+
getAccessTokenByUser(user: User): string {
58+
const payload = {
59+
sub: user.id,
60+
}
61+
const token = this.jwtService.sign(payload)
62+
return token
63+
}
64+
}

server/src/auth/dto/bind-phone.dto.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ApiProperty } from '@nestjs/swagger'
2+
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator'
3+
4+
export class BindPhoneDto {
5+
@ApiProperty({
6+
description: 'phone number',
7+
example: '13805718888',
8+
})
9+
@IsString()
10+
@IsNotEmpty()
11+
@Matches(/^1[3-9]\d{9}$/)
12+
phone: string
13+
14+
@ApiProperty({
15+
description: 'sms verify code',
16+
example: '032476',
17+
})
18+
@IsNotEmpty()
19+
@Length(6, 6)
20+
code: string
21+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ApiProperty } from '@nestjs/swagger'
2+
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator'
3+
4+
export class BindUsernameDto {
5+
@ApiProperty({
6+
description: 'username',
7+
example: 'laf-user',
8+
})
9+
@IsString()
10+
@IsNotEmpty()
11+
@Length(3, 64)
12+
username: string
13+
14+
@ApiProperty({
15+
description: 'phone',
16+
example: '13805718888',
17+
})
18+
@IsString()
19+
@IsNotEmpty()
20+
@Matches(/^1[3-9]\d{9}$/)
21+
phone: string
22+
23+
@ApiProperty({
24+
description: 'sms verify code',
25+
example: '032476',
26+
})
27+
@IsNotEmpty()
28+
@Length(6, 6)
29+
code: string
30+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ApiProperty } from '@nestjs/swagger'
2+
import { SmsVerifyCodeType } from '@prisma/client'
3+
import { IsEnum, IsNotEmpty, IsString, Length, Matches } from 'class-validator'
4+
5+
export class PasswdResetDto {
6+
@ApiProperty({
7+
description: 'new password, 8-64 characters',
8+
example: 'laf-user-password',
9+
})
10+
@IsString()
11+
@IsNotEmpty()
12+
@Length(8, 64)
13+
password: string
14+
15+
@ApiProperty({
16+
description: 'phone',
17+
example: '13805718888',
18+
})
19+
@IsString()
20+
@Matches(/^1[3-9]\d{9}$/)
21+
phone: string
22+
23+
@ApiProperty({
24+
description: 'verify code',
25+
example: '032456',
26+
})
27+
@IsString()
28+
@Length(6, 6)
29+
code: string
30+
31+
@ApiProperty({
32+
description: 'type',
33+
example: 'ResetPassword',
34+
})
35+
@IsEnum(SmsVerifyCodeType)
36+
type: SmsVerifyCodeType
37+
}

0 commit comments

Comments
 (0)