add user confirmation, add validation for submission data
This commit is contained in:
parent
29a74ea9c9
commit
1f413c2949
@ -24,6 +24,7 @@ module.exports = {
|
|||||||
jest: true,
|
jest: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||||
'@typescript-eslint/no-unsafe-call': 'warn',
|
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||||
|
|||||||
@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- logic backend components
|
- logic backend components
|
||||||
- forms now have multiple notification
|
- forms now have multiple notification
|
||||||
- layout for forms
|
- layout for forms
|
||||||
- mariadb / mysql support
|
- mariadb / mysql support (fixes https://github.com/ohmyform/ohmyform/issues/143)
|
||||||
- user confirmation tokens
|
- user confirmation tokens
|
||||||
- email verification
|
- email verification
|
||||||
|
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export const imports = [
|
|||||||
JwtModule.registerAsync({
|
JwtModule.registerAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: async (configService: ConfigService): Promise<JwtModuleOptions> => ({
|
useFactory: (configService: ConfigService): JwtModuleOptions => ({
|
||||||
secret: configService.get<string>('SECRET_KEY'),
|
secret: configService.get<string>('SECRET_KEY'),
|
||||||
signOptions: {
|
signOptions: {
|
||||||
expiresIn: '4h',
|
expiresIn: '4h',
|
||||||
@ -154,7 +154,7 @@ export const imports = [
|
|||||||
MailerModule.forRootAsync({
|
MailerModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: async (configService: ConfigService) => ({
|
useFactory: (configService: ConfigService) => ({
|
||||||
transport: configService.get<string>('MAILER_URI', 'smtp://localhost:1025'),
|
transport: configService.get<string>('MAILER_URI', 'smtp://localhost:1025'),
|
||||||
defaults: {
|
defaults: {
|
||||||
from: configService.get<string>('MAILER_FROM', 'OhMyForm <no-reply@localhost>'),
|
from: configService.get<string>('MAILER_FROM', 'OhMyForm <no-reply@localhost>'),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ const bootstrap = new BootstrapConsole({
|
|||||||
module: AppModule,
|
module: AppModule,
|
||||||
useDecorators: true,
|
useDecorators: true,
|
||||||
});
|
});
|
||||||
bootstrap.init().then(async (app) => {
|
void bootstrap.init().then(async (app) => {
|
||||||
try {
|
try {
|
||||||
await app.init();
|
await app.init();
|
||||||
await bootstrap.boot();
|
await bootstrap.boot();
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export class UserCommand {
|
|||||||
@Command({
|
@Command({
|
||||||
command: 'activate <username>',
|
command: 'activate <username>',
|
||||||
})
|
})
|
||||||
async activate(username: string): Promise<void> {
|
activate(username: string): void {
|
||||||
console.log(`activate user ${username}`)
|
console.log(`activate user ${username}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,8 @@ export const fieldTypes = [
|
|||||||
|
|
||||||
export const matchType = {
|
export const matchType = {
|
||||||
color: /^#([A-F0-9]{6}|[A-F0-9]{3})$/i,
|
color: /^#([A-F0-9]{6}|[A-F0-9]{3})$/i,
|
||||||
url: /((([A-Z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/i,
|
// eslint-disable-next-line max-len
|
||||||
|
url: /((([A-Z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/i,
|
||||||
email: /.+@.+\..+/,
|
email: /.+@.+\..+/,
|
||||||
slug: /^[a-z0-9_]+$/,
|
slug: /^[a-z0-9_]+$/,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,8 +44,8 @@ export class FormFieldModel {
|
|||||||
this.description = document.description
|
this.description = document.description
|
||||||
this.required = document.required
|
this.required = document.required
|
||||||
this.value = document.value
|
this.value = document.value
|
||||||
this.options = document.options ? document.options.map(option => new FormFieldOptionModel(option)) : []
|
this.options = document.options?.map(option => new FormFieldOptionModel(option)) || []
|
||||||
this.logic = document.logic ? document.logic.map(logic => new FormFieldLogicModel(logic)) : []
|
this.logic = document.logic?.map(logic => new FormFieldLogicModel(logic)) || []
|
||||||
this.rating = document.rating ? new FormFieldRatingModel(document.rating) : null
|
this.rating = document.rating ? new FormFieldRatingModel(document.rating) : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import cors from 'cors'
|
|||||||
import { Logger } from 'nestjs-pino'
|
import { Logger } from 'nestjs-pino'
|
||||||
import { AppModule } from './app.module'
|
import { AppModule } from './app.module'
|
||||||
|
|
||||||
(async () => {
|
void (async () => {
|
||||||
const options: NestApplicationOptions = {
|
const options: NestApplicationOptions = {
|
||||||
bufferLogs: true,
|
bufferLogs: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export class AuthRegisterResolver {
|
|||||||
async authRegister(
|
async authRegister(
|
||||||
@Args({ name: 'user' }) data: UserCreateInput,
|
@Args({ name: 'user' }) data: UserCreateInput,
|
||||||
): Promise<AuthJwtModel> {
|
): Promise<AuthJwtModel> {
|
||||||
if (await this.settingService.isTrue('SIGNUP_DISABLED')) {
|
if (this.settingService.isTrue('SIGNUP_DISABLED')) {
|
||||||
throw new Error('signup disabled')
|
throw new Error('signup disabled')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class FormStatisticResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => FormStatisticModel)
|
@Query(() => FormStatisticModel)
|
||||||
async getFormStatistic(): Promise<FormStatisticModel> {
|
getFormStatistic(): FormStatisticModel {
|
||||||
return new FormStatisticModel()
|
return new FormStatisticModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export class SettingResolver {
|
|||||||
|
|
||||||
@Query(() => SettingPagerModel)
|
@Query(() => SettingPagerModel)
|
||||||
@Roles('superuser')
|
@Roles('superuser')
|
||||||
async getSettings(): Promise<SettingPagerModel> {
|
getSettings(): SettingPagerModel {
|
||||||
// TODO https://github.com/ohmyform/api/issues/3
|
// TODO https://github.com/ohmyform/api/issues/3
|
||||||
return new SettingPagerModel(
|
return new SettingPagerModel(
|
||||||
[],
|
[],
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { StatusModel } from '../dto/status.model'
|
|||||||
@Resolver(() => StatusModel)
|
@Resolver(() => StatusModel)
|
||||||
export class StatusResolver {
|
export class StatusResolver {
|
||||||
@Query(() => StatusModel)
|
@Query(() => StatusModel)
|
||||||
async status(): Promise<StatusModel> {
|
status(): StatusModel {
|
||||||
return new StatusModel({
|
return new StatusModel({
|
||||||
version: process.env.version || 'dev',
|
version: process.env.version || 'dev',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -18,9 +18,16 @@ export class SubmissionFieldResolver {
|
|||||||
@Parent() parent: SubmissionFieldModel,
|
@Parent() parent: SubmissionFieldModel,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<FormFieldModel> {
|
): Promise<FormFieldModel> {
|
||||||
const submissionField = await cache.get<SubmissionFieldEntity>(cache.getCacheKey(SubmissionFieldEntity.name, parent.id))
|
const submissionField = await cache.get<SubmissionFieldEntity>(
|
||||||
|
cache.getCacheKey(SubmissionFieldEntity.name, parent.id)
|
||||||
|
)
|
||||||
|
|
||||||
const field = await cache.get<FormFieldEntity>(cache.getCacheKey(FormFieldEntity.name, submissionField.fieldId), () => this.formFieldService.findById(submissionField.fieldId, submissionField.field))
|
const field = await cache.get<FormFieldEntity>(
|
||||||
|
cache.getCacheKey(
|
||||||
|
FormFieldEntity.name,
|
||||||
|
submissionField.fieldId),
|
||||||
|
() => this.formFieldService.findById(submissionField.fieldId, submissionField.field)
|
||||||
|
)
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -15,7 +15,9 @@ export class SubmissionResolver {
|
|||||||
@Parent() parent: SubmissionModel,
|
@Parent() parent: SubmissionModel,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<SubmissionFieldModel[]> {
|
): Promise<SubmissionFieldModel[]> {
|
||||||
const submission = await cache.get<SubmissionEntity>(cache.getCacheKey(SubmissionEntity.name, parent.id))
|
const submission = await cache.get<SubmissionEntity>(
|
||||||
|
cache.getCacheKey(SubmissionEntity.name, parent.id)
|
||||||
|
)
|
||||||
|
|
||||||
return submission.fields.map(field => {
|
return submission.fields.map(field => {
|
||||||
cache.add(cache.getCacheKey(SubmissionFieldEntity.name, field.id), field)
|
cache.add(cache.getCacheKey(SubmissionFieldEntity.name, field.id), field)
|
||||||
|
|||||||
@ -34,7 +34,9 @@ export class SubmissionSearchResolver {
|
|||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
|
|
||||||
submissions.forEach(submission => cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission))
|
submissions.forEach(submission => {
|
||||||
|
cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission)
|
||||||
|
})
|
||||||
|
|
||||||
return new SubmissionPagerModel(
|
return new SubmissionPagerModel(
|
||||||
submissions.map(submission => new SubmissionModel(submission)),
|
submissions.map(submission => new SubmissionModel(submission)),
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export class SubmissionStatisticResolver {
|
|||||||
|
|
||||||
|
|
||||||
@Query(() => SubmissionStatisticModel)
|
@Query(() => SubmissionStatisticModel)
|
||||||
async getSubmissionStatistic(): Promise<SubmissionStatisticModel> {
|
getSubmissionStatistic(): SubmissionStatisticModel {
|
||||||
return new SubmissionStatisticModel()
|
return new SubmissionStatisticModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export class UserResolver {
|
|||||||
@Query(() => UserModel)
|
@Query(() => UserModel)
|
||||||
@Roles('admin')
|
@Roles('admin')
|
||||||
public async getUserById(
|
public async getUserById(
|
||||||
@Args('id', {type: () => ID}) id,
|
@Args('id', {type: () => ID}) id: string,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<UserModel> {
|
): Promise<UserModel> {
|
||||||
const user = await this.userService.findById(id)
|
const user = await this.userService.findById(id)
|
||||||
@ -33,18 +33,18 @@ export class UserResolver {
|
|||||||
@Parent() parent: UserModel,
|
@Parent() parent: UserModel,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
return await this.returnFieldForSuperuser(
|
return this.returnFieldForSuperuser(
|
||||||
await cache.get<UserEntity>(cache.getCacheKey(UserEntity.name, parent.id)),
|
await cache.get<UserEntity>(cache.getCacheKey(UserEntity.name, parent.id)),
|
||||||
user,
|
user,
|
||||||
c => c.roles
|
c => c.roles
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async returnFieldForSuperuser<T>(
|
returnFieldForSuperuser<T>(
|
||||||
parent: UserEntity,
|
parent: UserEntity,
|
||||||
user: UserEntity,
|
user: UserEntity,
|
||||||
callback: (user: UserEntity) => T
|
callback: (user: UserEntity) => T
|
||||||
): Promise<T> {
|
): T {
|
||||||
if (user.id !== parent.id && !this.userService.isSuperuser(user)) {
|
if (user.id !== parent.id && !this.userService.isSuperuser(user)) {
|
||||||
throw new Error('No access to roles')
|
throw new Error('No access to roles')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Query, ResolveField, Resolver } from '@nestjs/graphql'
|
import { Int, Query, ResolveField, Resolver } from '@nestjs/graphql'
|
||||||
import { GraphQLInt } from 'graphql'
|
|
||||||
import { Roles } from '../../decorator/roles.decorator'
|
import { Roles } from '../../decorator/roles.decorator'
|
||||||
import { UserStatisticModel } from '../../dto/user/user.statistic.model'
|
import { UserStatisticModel } from '../../dto/user/user.statistic.model'
|
||||||
import { UserStatisticService } from '../../service/user/user.statistic.service'
|
import { UserStatisticService } from '../../service/user/user.statistic.service'
|
||||||
@ -12,11 +11,11 @@ export class UserStatisticResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => UserStatisticModel)
|
@Query(() => UserStatisticModel)
|
||||||
async getUserStatistic(): Promise<UserStatisticModel> {
|
getUserStatistic(): UserStatisticModel {
|
||||||
return new UserStatisticModel()
|
return new UserStatisticModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField('total', () => GraphQLInt)
|
@ResolveField('total', () => Int)
|
||||||
@Roles('admin')
|
@Roles('admin')
|
||||||
getTotal(): Promise<number> {
|
getTotal(): Promise<number> {
|
||||||
return this.statisticService.getTotal()
|
return this.statisticService.getTotal()
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { JwtService } from '@nestjs/jwt'
|
import { JwtService } from '@nestjs/jwt'
|
||||||
import { PinoLogger } from 'nestjs-pino'
|
import { PinoLogger } from 'nestjs-pino'
|
||||||
|
import { serializeError } from 'serialize-error'
|
||||||
import { AuthJwtModel } from '../../dto/auth/auth.jwt.model'
|
import { AuthJwtModel } from '../../dto/auth/auth.jwt.model'
|
||||||
import { UserEntity } from '../../entity/user.entity'
|
import { UserEntity } from '../../entity/user.entity'
|
||||||
import { UserService } from '../user/user.service'
|
import { UserService } from '../user/user.service'
|
||||||
@ -26,7 +27,10 @@ export class AuthService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`failed to verify user? ${e.message}`)
|
this.logger.error({
|
||||||
|
error: serializeError(e),
|
||||||
|
username,
|
||||||
|
},'failed to verify user')
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user