From 69e6536a2ba18c0ccf09ad9c3f52cdeef604e3bf Mon Sep 17 00:00:00 2001 From: Yancey Leo Date: Fri, 3 Apr 2020 19:39:57 +0800 Subject: [PATCH] feat: finish loginStatistics --- schema.gql | 15 +++++++++++++ src/auth/auth.service.ts | 28 ++++++++++++++++++------ src/auth/models/ip-model.ts | 30 ++++++++++++++++++++++++++ src/users/dtos/update-user.input.ts | 4 ++++ src/users/interfaces/user.interface.ts | 2 ++ src/users/models/User.model.ts | 4 ++++ src/users/schemas/users.schema.ts | 5 +++++ 7 files changed, 81 insertions(+), 7 deletions(-) diff --git a/schema.gql b/schema.gql index cf01261c..8ceacf6d 100644 --- a/schema.gql +++ b/schema.gql @@ -46,6 +46,12 @@ type BestAlbumModel { updatedAt: DateTime! } +type Browser { + name: String! + version: String! + major: String! +} + input ChangePasswordInput { oldPassword: String! newPassword: String! @@ -120,6 +126,9 @@ type IPModel { latitude: Float! longitude: Float! location: Location! + browser: Browser! + os: OS! + loginTime: String! } type Language { @@ -203,6 +212,11 @@ type OpenSourceModel { updatedAt: DateTime! } +type OS { + name: String! + version: String! +} + type PlayerModel { _id: ID! title: String! @@ -392,6 +406,7 @@ type UserModel { isTOTP: Boolean! totpSecret: String! recoveryCodes: [String!]! + loginStatistics: [IPModel!]! createdAt: DateTime! updatedAt: DateTime! } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 39e52fc1..7446e1da 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -2,10 +2,11 @@ import { Injectable, HttpService } from '@nestjs/common' import { ForbiddenError, AuthenticationError } from 'apollo-server-express' import { JwtService } from '@nestjs/jwt' import { Request } from 'express' -import uaParser from 'ua-parser-js' +import { UAParser } from 'ua-parser-js' import requestIP from 'request-ip' import speakeasy from 'speakeasy' import { map } from 'rxjs/operators' +import { IPModel, Browser, OS } from './models/ip-model' import { ConfigService } from '../config/config.service' import { UsersService } from '../users/users.service' import { Roles, User } from '../users/interfaces/user.interface' @@ -154,21 +155,34 @@ export class AuthService { const { sub: userId } = decodeJwt(token) const network = { - // ip: requestIP.getClientIp(req), - ip: '123.118.72.95', + ip: requestIP.getClientIp(req), userAgent, } - const ipInfo = this.httpService - .get(`${IP_STACK_URL}${network.ip}`, { + const uaParser = new UAParser(userAgent) + + const ipInfo = await this.httpService + .get(`${IP_STACK_URL}${network.ip}`, { params: { access_key: IP_STACK_ACCESS_KEY, }, }) .pipe(map((response) => response.data)) + .toPromise() + + const loginInfo = { + ...ipInfo, + browser: uaParser.getBrowser(), + os: uaParser.getOS(), + loginTime: new Date().toISOString(), + } - return ipInfo + const user = await this.usersService.findOneById(userId) + await this.usersService.updateUser({ + id: userId, + loginStatistics: [...user.loginStatistics, loginInfo], + }) - // const user = await this.usersService.findOneById(userId) + return loginInfo } } diff --git a/src/auth/models/ip-model.ts b/src/auth/models/ip-model.ts index 8d10b515..053ccb24 100644 --- a/src/auth/models/ip-model.ts +++ b/src/auth/models/ip-model.ts @@ -41,6 +41,27 @@ export class Location { public readonly is_eu: boolean } +@ObjectType() +export class Browser { + @Field() + public readonly name: string + + @Field() + public readonly version: string + + @Field() + public readonly major: string +} + +@ObjectType() +export class OS { + @Field() + public readonly name: string + + @Field() + public readonly version: string +} + @ObjectType() export class IPModel { @Field() @@ -78,4 +99,13 @@ export class IPModel { @Field(() => Location) public readonly location: Location + + @Field(() => Browser) + public readonly browser: Browser + + @Field(() => OS) + public readonly os: OS + + @Field() + public readonly loginTime: string } diff --git a/src/users/dtos/update-user.input.ts b/src/users/dtos/update-user.input.ts index c6465551..a1dd92b9 100644 --- a/src/users/dtos/update-user.input.ts +++ b/src/users/dtos/update-user.input.ts @@ -1,5 +1,6 @@ import { InputType, Field } from '@nestjs/graphql' import { IsNotEmpty, IsUUID } from 'class-validator' +import { IPModel } from '../../auth/models/ip-model' @InputType() export class UpdateUserInput { @@ -19,4 +20,7 @@ export class UpdateUserInput { @Field({ nullable: true }) public readonly password?: string + + @Field({ nullable: true }) + public readonly loginStatistics?: IPModel[] } diff --git a/src/users/interfaces/user.interface.ts b/src/users/interfaces/user.interface.ts index 389ae649..57a970e5 100644 --- a/src/users/interfaces/user.interface.ts +++ b/src/users/interfaces/user.interface.ts @@ -1,4 +1,5 @@ import { Document } from 'mongoose' +import { IPModel } from '../../auth/models/ip-model' export enum TwoFactorAuthentications { TOTP, @@ -23,6 +24,7 @@ export interface User extends Document { isTOTP: boolean totpSecret: string recoveryCodes: string[] + loginStatistics: IPModel[] createdAt: Date updatedAt: Date isValidPassword(plainPwd: string, encryptedPwd: string): boolean diff --git a/src/users/models/User.model.ts b/src/users/models/User.model.ts index dad38e5e..93f413a7 100644 --- a/src/users/models/User.model.ts +++ b/src/users/models/User.model.ts @@ -1,4 +1,5 @@ import { Field, ID, ObjectType } from '@nestjs/graphql' +import { IPModel } from '../../auth/models/ip-model' @ObjectType() export class UserModel { @@ -35,6 +36,9 @@ export class UserModel { @Field(() => [String]) public readonly recoveryCodes: string[] + @Field(() => [IPModel]) + public readonly loginStatistics: IPModel[] + @Field() public readonly createdAt: Date diff --git a/src/users/schemas/users.schema.ts b/src/users/schemas/users.schema.ts index 0f6ada36..f79319e7 100644 --- a/src/users/schemas/users.schema.ts +++ b/src/users/schemas/users.schema.ts @@ -51,6 +51,11 @@ export const UserSchema = new mongoose.Schema( type: Array, required: false, }, + loginStatistics: { + type: Array, + required: false, + default: [], + }, }, { collection: 'user',