[Feature] 白名单功能服务端 (#302)
* feat: 添加问卷信息字段、去掉C端获取问卷信息的敏感字段 * feat: 白名单验证接口 * test: 白名单验证单元测试、参数类型优化 * test: 增加白名单验证单元测试 * feat: 提交问卷时校验白名单 * test: 提交问卷验证verifyId * test: verifyId不匹配测试
This commit is contained in:
parent
2e1af4ae3a
commit
667d13962c
@ -12,6 +12,7 @@ export enum EXCEPTION_CODE {
|
|||||||
SURVEY_NOT_FOUND = 3004, // 问卷不存在
|
SURVEY_NOT_FOUND = 3004, // 问卷不存在
|
||||||
SURVEY_CONTENT_NOT_ALLOW = 3005, // 存在禁用内容
|
SURVEY_CONTENT_NOT_ALLOW = 3005, // 存在禁用内容
|
||||||
CAPTCHA_INCORRECT = 4001, // 验证码不正确
|
CAPTCHA_INCORRECT = 4001, // 验证码不正确
|
||||||
|
WHITELIST_ERROR = 4002, // 白名单校验错误
|
||||||
|
|
||||||
RESPONSE_SIGN_ERROR = 9001, // 签名不正确
|
RESPONSE_SIGN_ERROR = 9001, // 签名不正确
|
||||||
RESPONSE_CURRENT_TIME_NOT_ALLOW = 9002, // 当前时间不允许提交
|
RESPONSE_CURRENT_TIME_NOT_ALLOW = 9002, // 当前时间不允许提交
|
||||||
|
@ -94,6 +94,23 @@ export interface SubmitConf {
|
|||||||
msgContent: MsgContent;
|
msgContent: MsgContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 白名单类型
|
||||||
|
export enum WhitelistType {
|
||||||
|
ALL = 'ALL',
|
||||||
|
// 空间成员
|
||||||
|
MEMBER = 'MEMBER',
|
||||||
|
// 自定义
|
||||||
|
CUSTOM = 'CUSTOM',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 白名单用户类型
|
||||||
|
export enum MemberType {
|
||||||
|
// 手机号
|
||||||
|
MOBILE = 'MOBILE',
|
||||||
|
// 邮箱
|
||||||
|
EMAIL = 'EMAIL',
|
||||||
|
}
|
||||||
|
|
||||||
export interface BaseConf {
|
export interface BaseConf {
|
||||||
begTime: string;
|
begTime: string;
|
||||||
endTime: string;
|
endTime: string;
|
||||||
@ -101,6 +118,18 @@ export interface BaseConf {
|
|||||||
answerEndTime: string;
|
answerEndTime: string;
|
||||||
tLimit: number;
|
tLimit: number;
|
||||||
language: string;
|
language: string;
|
||||||
|
// 访问密码开关
|
||||||
|
passwordSwitch?: boolean;
|
||||||
|
// 密码
|
||||||
|
password?: string | null;
|
||||||
|
// 白名单类型
|
||||||
|
whitelistType?: WhitelistType;
|
||||||
|
// 白名单用户类型
|
||||||
|
memberType?: MemberType;
|
||||||
|
// 白名单列表
|
||||||
|
whitelist?: string[];
|
||||||
|
// 提示语
|
||||||
|
whitelistTip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SkinConf {
|
export interface SkinConf {
|
||||||
|
8
server/src/models/whitelistVerify.entity.ts
Normal file
8
server/src/models/whitelistVerify.entity.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Entity, Column } from 'typeorm';
|
||||||
|
import { BaseEntity } from './base.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'whitelistVerify' })
|
||||||
|
export class WhitelistVerify extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
surveyPath: string;
|
||||||
|
}
|
@ -11,11 +11,16 @@ import { Captcha } from 'src/models/captcha.entity';
|
|||||||
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { WhitelistService } from './services/whitelist.service';
|
||||||
|
import { WhitelistVerify } from 'src/models/whitelistVerify.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User, Captcha]), ConfigModule],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([User, Captcha, WhitelistVerify]),
|
||||||
|
ConfigModule,
|
||||||
|
],
|
||||||
controllers: [AuthController, UserController],
|
controllers: [AuthController, UserController],
|
||||||
providers: [UserService, AuthService, CaptchaService],
|
providers: [UserService, AuthService, CaptchaService, WhitelistService],
|
||||||
exports: [UserService, AuthService],
|
exports: [UserService, AuthService, WhitelistService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
30
server/src/modules/auth/services/whitelist.service.ts
Normal file
30
server/src/modules/auth/services/whitelist.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { WhitelistVerify } from 'src/models/whitelistVerify.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WhitelistService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(WhitelistVerify)
|
||||||
|
private readonly whitelistVerifyRepo: MongoRepository<WhitelistVerify>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
async create(surveyPath: string) {
|
||||||
|
const data = this.whitelistVerifyRepo.create({
|
||||||
|
surveyPath,
|
||||||
|
});
|
||||||
|
return await this.whitelistVerifyRepo.save(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配
|
||||||
|
async match(surveyPath: string, verifyId: string) {
|
||||||
|
return await this.whitelistVerifyRepo.findOne({
|
||||||
|
where: {
|
||||||
|
surveyPath,
|
||||||
|
_id: verifyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,23 +6,72 @@ import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
|||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { WhitelistService } from 'src/modules/auth/services/whitelist.service';
|
||||||
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { WhitelistVerify } from 'src/models/whitelistVerify.entity';
|
||||||
|
|
||||||
jest.mock('../services/responseScheme.service');
|
jest.mock('../services/responseScheme.service');
|
||||||
|
|
||||||
describe('ResponseSchemaController', () => {
|
describe('ResponseSchemaController', () => {
|
||||||
let controller: ResponseSchemaController;
|
let controller: ResponseSchemaController;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
|
let whitelistService: WhitelistService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [ResponseSchemaController],
|
controllers: [ResponseSchemaController],
|
||||||
providers: [ResponseSchemaService],
|
providers: [
|
||||||
|
ResponseSchemaService,
|
||||||
|
AuthService,
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
info: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useValue: {
|
||||||
|
getUserByUsername: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {
|
||||||
|
findAllByUserId: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WhitelistService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AuthService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<ResponseSchemaController>(ResponseSchemaController);
|
controller = module.get<ResponseSchemaController>(ResponseSchemaController);
|
||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
|
whitelistService = module.get<WhitelistService>(WhitelistService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSchema', () => {
|
describe('getSchema', () => {
|
||||||
@ -66,5 +115,157 @@ describe('ResponseSchemaController', () => {
|
|||||||
new HttpException('问卷已删除', EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED),
|
new HttpException('问卷已删除', EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should throw SurveyNotFoundException when survey is removed', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue(null);
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(new SurveyNotFoundException('该问卷不存在,无法提交'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should throw WHITELIST_ERROR code when password is incorrect', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123457',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should return verifyId successfully', async () => {
|
||||||
|
const surveyPath = 'test';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
|
||||||
|
const id = 'c79c6fee22cbed6f0b087a27';
|
||||||
|
jest.spyOn(whitelistService, 'create').mockResolvedValue({
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
surveyPath,
|
||||||
|
} as WhitelistVerify);
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
}),
|
||||||
|
).resolves.toBe(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should throw WHITELIST_ERROR code when mobile or email is incorrect', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: 'CUSTOM',
|
||||||
|
memberType: 'MOBILE',
|
||||||
|
whitelist: ['13500000000'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
value: '13500000001',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should throw WHITELIST_ERROR code when member is incorrect', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: 'MEMBER',
|
||||||
|
whitelist: ['Jack'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
value: 'James',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should return verifyId successfully', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: 'CUSTOM',
|
||||||
|
memberType: 'MOBILE',
|
||||||
|
whitelist: ['13500000000'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
|
||||||
|
const id = 'c79c6fee22cbed6f0b087a27';
|
||||||
|
jest.spyOn(whitelistService, 'create').mockResolvedValue({
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
surveyPath,
|
||||||
|
} as WhitelistVerify);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
value: '13500000000',
|
||||||
|
}),
|
||||||
|
).resolves.toBe(id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -21,6 +21,11 @@ import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugi
|
|||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
|
import { WhitelistService } from 'src/modules/auth/services/whitelist.service';
|
||||||
|
import { MemberType, WhitelistType } from 'src/interfaces/survey';
|
||||||
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { WhitelistVerify } from 'src/models/whitelistVerify.entity';
|
||||||
|
|
||||||
const mockDecryptErrorBody = {
|
const mockDecryptErrorBody = {
|
||||||
surveyPath: 'EBzdmnSp',
|
surveyPath: 'EBzdmnSp',
|
||||||
@ -76,6 +81,7 @@ describe('SurveyResponseController', () => {
|
|||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
let surveyResponseService: SurveyResponseService;
|
let surveyResponseService: SurveyResponseService;
|
||||||
let clientEncryptService: ClientEncryptService;
|
let clientEncryptService: ClientEncryptService;
|
||||||
|
let whitelistService: WhitelistService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -124,6 +130,12 @@ describe('SurveyResponseController', () => {
|
|||||||
info: jest.fn(),
|
info: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: WhitelistService,
|
||||||
|
useValue: {
|
||||||
|
match: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
@ -136,6 +148,7 @@ describe('SurveyResponseController', () => {
|
|||||||
);
|
);
|
||||||
clientEncryptService =
|
clientEncryptService =
|
||||||
module.get<ClientEncryptService>(ClientEncryptService);
|
module.get<ClientEncryptService>(ClientEncryptService);
|
||||||
|
whitelistService = module.get<WhitelistService>(WhitelistService);
|
||||||
|
|
||||||
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||||
XiaojuSurveyPluginManager,
|
XiaojuSurveyPluginManager,
|
||||||
@ -306,5 +319,61 @@ describe('SurveyResponseController', () => {
|
|||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw HttpException if verifyId is empty', async () => {
|
||||||
|
const reqBody = { ...mockSubmitData };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: WhitelistType.CUSTOM,
|
||||||
|
memberType: MemberType.MOBILE,
|
||||||
|
whitelist: ['13500000000'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
|
||||||
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
|
new HttpException('白名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw HttpException if verifyId does not match', async () => {
|
||||||
|
const reqBody = {
|
||||||
|
...mockSubmitData,
|
||||||
|
verifyId: 'xxx',
|
||||||
|
sign: '6e9fda60c7fd9466eda480e3c5a03c2de0e33becc193b82f6aa6d25ff3b69146.1710400229589',
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: WhitelistType.CUSTOM,
|
||||||
|
memberType: MemberType.MOBILE,
|
||||||
|
whitelist: ['13500000000'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
|
||||||
|
jest.spyOn(whitelistService, 'match').mockResolvedValueOnce(null);
|
||||||
|
|
||||||
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
|
new HttpException('白名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,35 @@
|
|||||||
import { Controller, Get, HttpCode, Query } from '@nestjs/common';
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
|
import { WhitelistType } from 'src/interfaces/survey';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { WhitelistService } from 'src/modules/auth/services/whitelist.service';
|
||||||
|
|
||||||
@ApiTags('surveyResponse')
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/responseSchema')
|
@Controller('/api/responseSchema')
|
||||||
export class ResponseSchemaController {
|
export class ResponseSchemaController {
|
||||||
constructor(private readonly responseSchemaService: ResponseSchemaService) {}
|
constructor(
|
||||||
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly userService: UserService,
|
||||||
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
|
private readonly whitelistService: WhitelistService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get('/getSchema')
|
@Get('/getSchema')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
@ -34,9 +55,81 @@ export class ResponseSchemaController {
|
|||||||
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 去掉C端的敏感字段
|
||||||
|
if (responseSchema.code?.baseConf) {
|
||||||
|
responseSchema.code.baseConf.password = null;
|
||||||
|
responseSchema.code.baseConf.whitelist = [];
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: responseSchema,
|
data: responseSchema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 白名单验证
|
||||||
|
@Post('/:surveyPath/validate')
|
||||||
|
@HttpCode(200)
|
||||||
|
async whitelistValidate(
|
||||||
|
@Param('surveyPath') surveyPath,
|
||||||
|
@Body() body,
|
||||||
|
): Promise<string> {
|
||||||
|
const { value, error } = Joi.object({
|
||||||
|
password: Joi.string().allow(null, ''),
|
||||||
|
value: Joi.string().allow(null, ''),
|
||||||
|
}).validate(body, { allowUnknown: true });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(`whitelistValidate error: ${error.message}`, {});
|
||||||
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 问卷信息
|
||||||
|
const schema =
|
||||||
|
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
||||||
|
if (!schema || schema.curStatus.status === 'removed') {
|
||||||
|
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { password, value: val } = value;
|
||||||
|
const {
|
||||||
|
passwordSwitch,
|
||||||
|
password: settingPassword,
|
||||||
|
whitelistType,
|
||||||
|
whitelist,
|
||||||
|
} = schema.code.baseConf;
|
||||||
|
|
||||||
|
// 密码校验
|
||||||
|
if (passwordSwitch) {
|
||||||
|
if (settingPassword !== password) {
|
||||||
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 名单校验(手机号/邮箱)
|
||||||
|
if (whitelistType === WhitelistType.CUSTOM) {
|
||||||
|
if (!whitelist.includes(val)) {
|
||||||
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 团队成员昵称校验
|
||||||
|
if (whitelistType === WhitelistType.MEMBER) {
|
||||||
|
const user = await this.userService.getUserByUsername(val);
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
||||||
|
{ userId: user._id },
|
||||||
|
);
|
||||||
|
if (!workspaceMember.length) {
|
||||||
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回verifyId
|
||||||
|
const res = await this.whitelistService.create(surveyPath);
|
||||||
|
return res._id.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ import * as Joi from 'joi';
|
|||||||
import * as forge from 'node-forge';
|
import * as forge from 'node-forge';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
|
import { WhitelistType } from 'src/interfaces/survey';
|
||||||
|
import { WhitelistService } from 'src/modules/auth/services/whitelist.service';
|
||||||
|
|
||||||
@ApiTags('surveyResponse')
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/surveyResponse')
|
@Controller('/api/surveyResponse')
|
||||||
@ -28,6 +30,7 @@ export class SurveyResponseController {
|
|||||||
private readonly clientEncryptService: ClientEncryptService,
|
private readonly clientEncryptService: ClientEncryptService,
|
||||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
|
private readonly whitelistService: WhitelistService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/createResponse')
|
@Post('/createResponse')
|
||||||
@ -43,6 +46,7 @@ export class SurveyResponseController {
|
|||||||
sessionId: Joi.string(),
|
sessionId: Joi.string(),
|
||||||
clientTime: Joi.number().required(),
|
clientTime: Joi.number().required(),
|
||||||
difTime: Joi.number(),
|
difTime: Joi.number(),
|
||||||
|
verifyId: Joi.string().allow(null, ''),
|
||||||
}).validate(reqBody, { allowUnknown: true });
|
}).validate(reqBody, { allowUnknown: true });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -52,8 +56,15 @@ export class SurveyResponseController {
|
|||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { surveyPath, encryptType, data, sessionId, clientTime, difTime } =
|
const {
|
||||||
value;
|
surveyPath,
|
||||||
|
encryptType,
|
||||||
|
data,
|
||||||
|
sessionId,
|
||||||
|
clientTime,
|
||||||
|
difTime,
|
||||||
|
verifyId,
|
||||||
|
} = value;
|
||||||
|
|
||||||
// 查询schema
|
// 查询schema
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
@ -62,6 +73,30 @@ export class SurveyResponseController {
|
|||||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 白名单的verifyId校验
|
||||||
|
const baseConf = responseSchema.code.baseConf;
|
||||||
|
const shouldValidateVerifyId =
|
||||||
|
(baseConf?.passwordSwitch && baseConf.password) ||
|
||||||
|
(baseConf?.whitelistType && baseConf.whitelistType !== WhitelistType.ALL);
|
||||||
|
if (shouldValidateVerifyId) {
|
||||||
|
// 无verifyId
|
||||||
|
if (!verifyId) {
|
||||||
|
throw new HttpException(
|
||||||
|
'白名单验证失败',
|
||||||
|
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据库中查询是否存在对应的verifyId
|
||||||
|
const record = await this.whitelistService.match(surveyPath, verifyId);
|
||||||
|
if (!record) {
|
||||||
|
throw new HttpException(
|
||||||
|
'白名单验证失败',
|
||||||
|
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
// 提交时间限制
|
// 提交时间限制
|
||||||
const begTime = responseSchema.code?.baseConf?.begTime || 0;
|
const begTime = responseSchema.code?.baseConf?.begTime || 0;
|
||||||
|
@ -20,6 +20,8 @@ import { CounterController } from './controllers/counter.controller';
|
|||||||
import { ResponseSchemaController } from './controllers/responseSchema.controller';
|
import { ResponseSchemaController } from './controllers/responseSchema.controller';
|
||||||
import { SurveyResponseController } from './controllers/surveyResponse.controller';
|
import { SurveyResponseController } from './controllers/surveyResponse.controller';
|
||||||
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';
|
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -31,6 +33,8 @@ import { SurveyResponseUIController } from './controllers/surveyResponseUI.contr
|
|||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
MessageModule,
|
MessageModule,
|
||||||
|
AuthModule,
|
||||||
|
WorkspaceModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
ClientEncryptController,
|
ClientEncryptController,
|
||||||
|
Loading…
Reference in New Issue
Block a user