diff --git a/CHANGELOG.md b/CHANGELOG.md index c92a2a1..9ba32e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added + +- `SIGNUP_DISABLED=true` to prevent users from signing up + ### Changed ### Fixed diff --git a/src/dto/setting/pager.setting.model.ts b/src/dto/setting/pager.setting.model.ts new file mode 100644 index 0000000..cafc3f4 --- /dev/null +++ b/src/dto/setting/pager.setting.model.ts @@ -0,0 +1,25 @@ +import { Field, ObjectType } from '@nestjs/graphql'; +import { GraphQLInt } from 'graphql'; +import {SettingModel} from './setting.model' + +@ObjectType('PagerSetting') +export class PagerSettingModel { + @Field(() => [SettingModel]) + entries: SettingModel[] + + @Field(() => GraphQLInt) + total: number + + @Field(() => GraphQLInt) + limit: number + + @Field(() => GraphQLInt) + start: number + + constructor(entries: SettingModel[], total: number, limit: number, start: number) { + this.entries = entries + this.total = total + this.limit = limit + this.start = start + } +} diff --git a/src/dto/setting/setting.model.ts b/src/dto/setting/setting.model.ts new file mode 100644 index 0000000..d15aef2 --- /dev/null +++ b/src/dto/setting/setting.model.ts @@ -0,0 +1,24 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; + +@ObjectType('Setting') +export class SettingModel { + @Field(() => ID) + readonly key: string + + @Field() + readonly value: string + + @Field() + readonly isTrue: boolean + + @Field() + readonly isFalse: boolean + + constructor(key: string, value: string) { + this.key = key + this.value = value + + this.isTrue = value.toLowerCase() === 'true' || value === '1' + this.isFalse = !this.isTrue + } +} diff --git a/src/resolver/auth/auth.register.resolver.ts b/src/resolver/auth/auth.register.resolver.ts index 2a86082..490df6a 100644 --- a/src/resolver/auth/auth.register.resolver.ts +++ b/src/resolver/auth/auth.register.resolver.ts @@ -4,12 +4,14 @@ import { PinoLogger } from 'nestjs-pino/dist'; import { AuthJwtModel } from '../../dto/auth/auth.jwt.model'; import { UserCreateInput } from '../../dto/user/user.create.input'; import { AuthService } from '../../service/auth/auth.service'; +import {SettingService} from '../../service/setting.service' import { UserCreateService } from '../../service/user/user.create.service'; @Injectable() export class AuthRegisterResolver { constructor( private readonly createUser: UserCreateService, + private readonly settingService: SettingService, private readonly auth: AuthService, private readonly logger: PinoLogger, ) { @@ -19,6 +21,10 @@ export class AuthRegisterResolver { async authRegister( @Args({ name: 'user' }) data: UserCreateInput, ): Promise { + if (await this.settingService.isTrue('SIGNUP_DISABLED')) { + throw new Error('signup disabled') + } + this.logger.info({ email: data.email, username: data.username, diff --git a/src/resolver/index.ts b/src/resolver/index.ts index d77c4db..c3ab89d 100644 --- a/src/resolver/index.ts +++ b/src/resolver/index.ts @@ -1,6 +1,7 @@ import { authServices } from './auth'; import { formResolvers } from './form'; import { profileResolvers } from './profile'; +import {settingsResolvers} from './setting' import { StatusResolver } from './status.resolver'; import { submissionResolvers } from './submission'; import { userResolvers } from './user'; @@ -12,4 +13,5 @@ export const resolvers = [ ...profileResolvers, ...formResolvers, ...submissionResolvers, + ...settingsResolvers, ] diff --git a/src/resolver/setting/index.ts b/src/resolver/setting/index.ts new file mode 100644 index 0000000..50b1b78 --- /dev/null +++ b/src/resolver/setting/index.ts @@ -0,0 +1,7 @@ +import {SettingMutation} from './setting.mutation' +import {SettingResolver} from './setting.resolver' + +export const settingsResolvers = [ + SettingResolver, + SettingMutation, +] diff --git a/src/resolver/setting/setting.mutation.ts b/src/resolver/setting/setting.mutation.ts new file mode 100644 index 0000000..a3755ff --- /dev/null +++ b/src/resolver/setting/setting.mutation.ts @@ -0,0 +1,8 @@ +import {Roles} from '../../decorator/roles.decorator' + +export class SettingMutation { + @Roles('superuser') + setSetting(key: string, value: string) { + // TODO https://github.com/ohmyform/api/issues/3 + } +} diff --git a/src/resolver/setting/setting.resolver.ts b/src/resolver/setting/setting.resolver.ts new file mode 100644 index 0000000..90cf829 --- /dev/null +++ b/src/resolver/setting/setting.resolver.ts @@ -0,0 +1,44 @@ +import {ConfigService} from '@nestjs/config' +import {Args, ID, Query} from '@nestjs/graphql' +import {Roles} from '../../decorator/roles.decorator' +import {User} from '../../decorator/user.decorator' +import {PagerSettingModel} from '../../dto/setting/pager.setting.model' +import {SettingModel} from '../../dto/setting/setting.model' +import {UserModel} from '../../dto/user/user.model' +import {UserDocument} from '../../schema/user.schema' +import {SettingService} from '../../service/setting.service' + +export class SettingResolver { + private publicKeys: string[] = [ + 'SIGNUP_DISABLED', + ] + + constructor( + private readonly settingService: SettingService, + ) { + } + + @Query(() => PagerSettingModel) + @Roles('superuser') + async getSettings(): Promise { + // TODO https://github.com/ohmyform/api/issues/3 + return new PagerSettingModel( + [], + 0, + 0, + 0, + ) + } + + @Query(() => SettingModel) + async getSetting( + @Args('key', {type: () => ID}) key: string, + @User() user: UserDocument, + ): Promise { + if (!this.publicKeys.includes(key) && !user.roles.includes('superuser')) { + throw new Error(`no access to key ${key}`) + } + + return await this.settingService.getByKey(key) + } +} diff --git a/src/service/index.ts b/src/service/index.ts index 56d20a8..cfe0694 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -8,6 +8,7 @@ import { formServices } from './form'; import { InstallationMetricsService } from './installation.metrics.service'; import { MailService } from './mail.service'; import { profileServices } from './profile'; +import {SettingService} from './setting.service' import { submissionServices } from './submission'; import { userServices } from './user'; @@ -17,6 +18,7 @@ export const services = [ ...formServices, ...authServices, ...submissionServices, + SettingService, MailService, InstallationMetricsService, { diff --git a/src/service/setting.service.ts b/src/service/setting.service.ts new file mode 100644 index 0000000..e68e3c1 --- /dev/null +++ b/src/service/setting.service.ts @@ -0,0 +1,28 @@ +import {Injectable} from '@nestjs/common' +import {ConfigService} from '@nestjs/config' +import {SettingModel} from '../dto/setting/setting.model' + +@Injectable() +export class SettingService { + constructor( + private readonly configService: ConfigService, + ) { + } + + async getByKey(key: string): Promise { + switch (key) { + case 'SIGNUP_DISABLED': + return new SettingModel(key, this.configService.get(key)) + } + + throw new Error(`no config stored for key ${key}`) + } + + async isTrue(key: string): Promise { + return (await this.getByKey(key)).isTrue + } + + async isFalse(key: string): Promise { + return (await this.getByKey(key)).isFalse + } +}