diff --git a/locales/ja.yml b/locales/ja.yml index 310a73a64e..acc4efe89f 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -317,6 +317,8 @@ common/views/components/signin.vue: login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" common/views/components/signup.vue: + invitation-code: "招待コード" + invitation-info: "招待コードをお持ちでない方は、管理者までご連絡ください。" username: "ユーザー名" checking: "確認しています..." available: "利用できます" diff --git a/src/client/app/common/views/components/signup.vue b/src/client/app/common/views/components/signup.vue index 45a183e144..1d33702159 100644 --- a/src/client/app/common/views/components/signup.vue +++ b/src/client/app/common/views/components/signup.vue @@ -1,5 +1,10 @@ @@ -16,13 +20,21 @@ import Vue from "vue"; export default Vue.extend({ data() { return { - stats: null + stats: null, + inviteCode: null }; }, created() { (this as any).api('stats').then(stats => { this.stats = stats; }); + }, + methods: { + invite() { + (this as any).api('admin/invite').then(x => { + this.inviteCode = x.code; + }); + } } }); diff --git a/src/models/meta.ts b/src/models/meta.ts index 11b9b186ce..aef0163dfe 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -11,4 +11,5 @@ export type IMeta = { usersCount: number; originalUsersCount: number; }; + disableRegistration: boolean; }; diff --git a/src/models/registration-tickets.ts b/src/models/registration-tickets.ts new file mode 100644 index 0000000000..846acefedf --- /dev/null +++ b/src/models/registration-tickets.ts @@ -0,0 +1,12 @@ +import * as mongo from 'mongodb'; +import db from '../db/mongodb'; + +const RegistrationTicket = db.get('registrationTickets'); +RegistrationTicket.createIndex('code', { unique: true }); +export default RegistrationTicket; + +export interface IRegistrationTicket { + _id: mongo.ObjectID; + createdAt: Date; + code: string; +} diff --git a/src/server/api/endpoints/admin/invite.ts b/src/server/api/endpoints/admin/invite.ts new file mode 100644 index 0000000000..77608e715c --- /dev/null +++ b/src/server/api/endpoints/admin/invite.ts @@ -0,0 +1,26 @@ +import rndstr from 'rndstr'; +import RegistrationTicket from '../../../../models/registration-tickets'; + +export const meta = { + desc: { + ja: '招待コードを発行します。' + }, + + requireCredential: true, + requireAdmin: true, + + params: {} +}; + +export default (params: any) => new Promise(async (res, rej) => { + const code = rndstr({ length: 5, chars: '0-9' }); + + await RegistrationTicket.insert({ + createdAt: new Date(), + code: code + }); + + res({ + code: code + }); +}); diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 8698120cdb..9c32ba987d 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -4,43 +4,43 @@ import getParams from '../../get-params'; import User from '../../../../models/user'; export const meta = { - desc: { - ja: '指定したユーザーを凍結します。', - en: 'Suspend a user.' - }, + desc: { + ja: '指定したユーザーを凍結します。', + en: 'Suspend a user.' + }, - requireCredential: true, - requireAdmin: true, + requireCredential: true, + requireAdmin: true, - params: { - userId: $.type(ID).note({ - desc: { - ja: '対象のユーザーID', - en: 'The user ID which you want to suspend' - } - }), - } + params: { + userId: $.type(ID).note({ + desc: { + ja: '対象のユーザーID', + en: 'The user ID which you want to suspend' + } + }), + } }; export default (params: any) => new Promise(async (res, rej) => { - const [ps, psErr] = getParams(meta, params); - if (psErr) return rej(psErr); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - const user = await User.findOne({ - _id: ps.userId - }); + const user = await User.findOne({ + _id: ps.userId + }); - if (user == null) { - return rej('user not found'); - } + if (user == null) { + return rej('user not found'); + } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSuspended: true - } - }); + await User.findOneAndUpdate({ + _id: user._id + }, { + $set: { + isSuspended: true + } + }); - res(); + res(); }); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index c2d93997a7..000a56024d 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -28,6 +28,7 @@ export default () => new Promise(async (res, rej) => { model: os.cpus()[0].model, cores: os.cpus().length }, - broadcasts: meta.broadcasts + broadcasts: meta.broadcasts, + disableRegistration: meta.disableRegistration }); }); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 16ec33bcbf..2bf56a9791 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -6,6 +6,7 @@ import User, { IUser, validateUsername, validatePassword, pack } from '../../../ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import Meta from '../../../models/meta'; +import RegistrationTicket from '../../../models/registration-tickets'; if (config.recaptcha) { recaptcha.init({ @@ -29,6 +30,29 @@ export default async (ctx: Koa.Context) => { const username = body['username']; const password = body['password']; + const invitationCode = body['invitationCode']; + + const meta = await Meta.findOne({}); + + if (meta.disableRegistration) { + if (invitationCode == null || typeof invitationCode != 'string') { + ctx.status = 400; + return; + } + + const ticket = await RegistrationTicket.findOne({ + code: invitationCode + }); + + if (ticket == null) { + ctx.status = 400; + return; + } + + RegistrationTicket.remove({ + _id: ticket._id + }); + } // Validate username if (!validateUsername(username)) {