Skip to content
This repository has been archived by the owner on Nov 29, 2021. It is now read-only.

Commit

Permalink
feat: finish changepassword
Browse files Browse the repository at this point in the history
  • Loading branch information
YanceyOfficial committed Apr 1, 2020
1 parent 9641d93 commit bec654e
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 16 deletions.
6 changes: 6 additions & 0 deletions schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ type BestAlbumModel {
updatedAt: DateTime!
}

input ChangePasswordInput {
oldPassword: String!
newPassword: String!
}

input CreateAgendaInput {
title: String!
startDate: String!
Expand Down Expand Up @@ -122,6 +127,7 @@ type Mutation {
validateTOTP(input: ValidateTOTPInput!): UserModel!
createRecoveryCodes(userId: ID!): RecoveryCodeModel!
validateRecoveryCode(input: ValidateTOTPInput!): UserModel!
changePassword(input: ChangePasswordInput!): UserModel!
createAnnouncement(input: CreateAnnouncementInput!): AnnouncementModel!
updateAnnouncementById(input: UpdateAnnouncementInput!): AnnouncementModel!
deleteAnnouncementById(id: ID!): AnnouncementModel!
Expand Down
11 changes: 10 additions & 1 deletion src/auth/auth.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { UseGuards } from '@nestjs/common'
import { Args, Query, Resolver, Mutation, ID } from '@nestjs/graphql'
import { Request } from 'express'
import requestIP from 'request-ip'
import { AuthService } from './auth.service'
import { UserModel } from '../users/models/User.model'
import { TOTPModel } from './models/totp.model'
import { RecoveryCodeModel } from './models/recovery-code.model'
import { LoginInput } from './dtos/login.input'
import { RegisterInput } from './dtos/register.input'
import { ValidateTOTPInput } from './dtos/validate-totp.input'
import { ChangePasswordInput } from './dtos/change-password.input'
import { GqlAuthGuard } from '../shared/guard/gqlAuth.guard'
import { ReqDecorator } from '../shared/decorators/req.decorator'

Expand Down Expand Up @@ -59,4 +59,13 @@ export class AuthResolver {
) {
return this.authService.validateRecoveryCode(input, req.headers.authorization)
}

@Mutation(() => UserModel)
@UseGuards(GqlAuthGuard)
public async changePassword(
@Args('input') input: ChangePasswordInput,
@ReqDecorator() req: Request,
) {
return this.authService.changePassword(input, req.headers.authorization)
}
}
24 changes: 19 additions & 5 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Injectable } from '@nestjs/common'
import { ForbiddenError, AuthenticationError } from 'apollo-server-express'
import jwt from 'jsonwebtoken'
import { JwtService } from '@nestjs/jwt'
import speakeasy from 'speakeasy'
import { UsersService } from '../users/users.service'
import { Roles, User } from '../users/interfaces/user.interface'
import { LoginInput } from './dtos/login.input'
import { RegisterInput } from './dtos/register.input'
import { ValidateTOTPInput } from './dtos/validate-totp.input'
import { Payload } from './interfaces/jwt.interface'
import { ChangePasswordInput } from './dtos/change-password.input'
import { TOTP_ENCODE } from '../shared/constants'
import { generateQRCode, generateRecoveryCodes, decodeJwt } from '../shared/utils'
import { generateQRCode, generateRecoveryCodes, decodeJwt, encryptPassword } from '../shared/utils'

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -48,9 +47,8 @@ export class AuthService {
public async register(registerInput: RegisterInput) {
const { email, username } = registerInput
const curEmail = await this.usersService.findOneByEmail(email)
const curUser = await this.usersService.findOneByUserName(username)

if (curUser || curEmail) {
if (curEmail) {
throw new ForbiddenError('Email is already registered!')
} else {
// TODO: 通过脚本初始化 root 用户
Expand Down Expand Up @@ -120,4 +118,20 @@ export class AuthService {

throw new ForbiddenError('Two factor authentication failed!')
}

public async changePassword(input: ChangePasswordInput, token: string) {
const { oldPassword, newPassword } = input
const { sub: userId } = decodeJwt(token)
const user = await this.usersService.findOneById(userId)

if (user && user.isValidPassword(oldPassword, user.password)) {
const res = await this.usersService.updateUser({
id: userId,
password: encryptPassword(newPassword),
})
return res
}

throw new ForbiddenError('Change password error!')
}
}
15 changes: 15 additions & 0 deletions src/auth/dtos/change-password.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { InputType, Field } from '@nestjs/graphql'
import { IsNotEmpty, IsString } from 'class-validator'

@InputType()
export class ChangePasswordInput {
@Field()
@IsString()
@IsNotEmpty()
public readonly oldPassword: string

@Field()
@IsString()
@IsNotEmpty()
public readonly newPassword: string
}
2 changes: 1 addition & 1 deletion src/auth/dtos/validate-totp.input.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InputType, Field } from '@nestjs/graphql'
import { IsNotEmpty, IsUUID, IsString } from 'class-validator'
import { IsNotEmpty, IsString } from 'class-validator'

@InputType()
export class ValidateTOTPInput {
Expand Down
3 changes: 3 additions & 0 deletions src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import QRCode from 'qrcode'
import jwt from 'jsonwebtoken'
import { ApolloError } from 'apollo-server-express'
import { randomSeries } from 'yancey-js-util'
import bcrypt from 'bcrypt'
import { Payload } from '../auth/interfaces/jwt.interface'

export const generateSMSVerificationCode = () =>
Expand Down Expand Up @@ -30,3 +31,5 @@ export const generateRecoveryCodes = () => {
}

export const decodeJwt = (token: string) => jwt.decode(token.slice(7)) as Payload

export const encryptPassword = (password: string) => bcrypt.hashSync(password, 10)
5 changes: 1 addition & 4 deletions src/users/dtos/update-user.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,5 @@ export class UpdateUserInput {
public readonly recoveryCodes?: string[]

@Field({ nullable: true })
public readonly oldPassword?: string

@Field({ nullable: true })
public readonly newPassword?: string
public readonly password?: string
}
3 changes: 2 additions & 1 deletion src/users/schemas/users.schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mongoose, { HookNextFunction } from 'mongoose'
import { v4 } from 'uuid'
import bcrypt from 'bcrypt'
import { encryptPassword } from '../../shared/utils'
import { Roles, User } from '../interfaces/user.interface'

export const UserSchema = new mongoose.Schema(
Expand Down Expand Up @@ -58,7 +59,7 @@ export const UserSchema = new mongoose.Schema(
)

UserSchema.pre<User>('save', function (next: HookNextFunction) {
this.password = bcrypt.hashSync(this.password, 10)
this.password = encryptPassword(this.password)
next()
})

Expand Down
4 changes: 0 additions & 4 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ export class UsersService {
return this.UserModel.findOne({ email })
}

public async findOneByUserName(username: string): Promise<User> {
return this.UserModel.findOne({ username })
}

public async create(createUserInput: CreateUserInput): Promise<User> {
return this.UserModel.create(createUserInput)
}
Expand Down

0 comments on commit bec654e

Please sign in to comment.