mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-10-31 05:24:13 +00:00 
			
		
		
		
	
							parent
							
								
									dcdb57df9d
								
							
						
					
					
						commit
						2c8f962889
					
				
					 9 changed files with 124 additions and 33 deletions
				
			
		|  | @ -317,6 +317,8 @@ common/views/components/signin.vue: | ||||||
|   login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" |   login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" | ||||||
| 
 | 
 | ||||||
| common/views/components/signup.vue: | common/views/components/signup.vue: | ||||||
|  |   invitation-code: "招待コード" | ||||||
|  |   invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。" | ||||||
|   username: "ユーザー名" |   username: "ユーザー名" | ||||||
|   checking: "確認しています..." |   checking: "確認しています..." | ||||||
|   available: "利用できます" |   available: "利用できます" | ||||||
|  |  | ||||||
|  | @ -1,5 +1,10 @@ | ||||||
| <template> | <template> | ||||||
| <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()"> | <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()"> | ||||||
|  | 	<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> | ||||||
|  | 		<span>%i18n:@invitation-code%</span> | ||||||
|  | 		<span slot="prefix">%fa:id-card-alt%</span> | ||||||
|  | 		<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p> | ||||||
|  | 	</ui-input> | ||||||
| 	<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername"> | 	<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername"> | ||||||
| 		<span>%i18n:@username%</span> | 		<span>%i18n:@username%</span> | ||||||
| 		<span slot="prefix">@</span> | 		<span slot="prefix">@</span> | ||||||
|  | @ -46,11 +51,13 @@ export default Vue.extend({ | ||||||
| 			username: '', | 			username: '', | ||||||
| 			password: '', | 			password: '', | ||||||
| 			retypedPassword: '', | 			retypedPassword: '', | ||||||
|  | 			invitationCode: '', | ||||||
| 			url, | 			url, | ||||||
| 			recaptchaSitekey, | 			recaptchaSitekey, | ||||||
| 			usernameState: null, | 			usernameState: null, | ||||||
| 			passwordStrength: '', | 			passwordStrength: '', | ||||||
| 			passwordRetypeState: null | 			passwordRetypeState: null, | ||||||
|  | 			meta: null | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
|  | @ -61,6 +68,11 @@ export default Vue.extend({ | ||||||
| 				this.usernameState != 'max-range'); | 				this.usernameState != 'max-range'); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  | 	created() { | ||||||
|  | 		(this as any).os.getMeta().then(meta => { | ||||||
|  | 			this.meta = meta; | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		onChangeUsername() { | 		onChangeUsername() { | ||||||
| 			if (this.username == '') { | 			if (this.username == '') { | ||||||
|  | @ -110,6 +122,7 @@ export default Vue.extend({ | ||||||
| 			(this as any).api('signup', { | 			(this as any).api('signup', { | ||||||
| 				username: this.username, | 				username: this.username, | ||||||
| 				password: this.password, | 				password: this.password, | ||||||
|  | 				invitationCode: this.invitationCode, | ||||||
| 				'g-recaptcha-response': recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null | 				'g-recaptcha-response': recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				(this as any).api('signin', { | 				(this as any).api('signin', { | ||||||
|  |  | ||||||
|  | @ -7,6 +7,10 @@ | ||||||
| 		<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p> | 		<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p> | ||||||
| 		<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p> | 		<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p> | ||||||
| 	</div> | 	</div> | ||||||
|  | 	<div> | ||||||
|  | 		<button class="ui" @click="invite">%i18n:@invite%</button> | ||||||
|  | 		<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p> | ||||||
|  | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -16,13 +20,21 @@ import Vue from "vue"; | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			stats: null | 			stats: null, | ||||||
|  | 			inviteCode: null | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	created() { | 	created() { | ||||||
| 		(this as any).api('stats').then(stats => { | 		(this as any).api('stats').then(stats => { | ||||||
| 			this.stats = stats; | 			this.stats = stats; | ||||||
| 		}); | 		}); | ||||||
|  | 	}, | ||||||
|  | 	methods: { | ||||||
|  | 		invite() { | ||||||
|  | 			(this as any).api('admin/invite').then(x => { | ||||||
|  | 				this.inviteCode = x.code; | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -11,4 +11,5 @@ export type IMeta = { | ||||||
| 		usersCount: number; | 		usersCount: number; | ||||||
| 		originalUsersCount: number; | 		originalUsersCount: number; | ||||||
| 	}; | 	}; | ||||||
|  | 	disableRegistration: boolean; | ||||||
| }; | }; | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								src/models/registration-tickets.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/models/registration-tickets.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import * as mongo from 'mongodb'; | ||||||
|  | import db from '../db/mongodb'; | ||||||
|  | 
 | ||||||
|  | const RegistrationTicket = db.get<IRegistrationTicket>('registrationTickets'); | ||||||
|  | RegistrationTicket.createIndex('code', { unique: true }); | ||||||
|  | export default RegistrationTicket; | ||||||
|  | 
 | ||||||
|  | export interface IRegistrationTicket { | ||||||
|  | 	_id: mongo.ObjectID; | ||||||
|  | 	createdAt: Date; | ||||||
|  | 	code: string; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								src/server/api/endpoints/admin/invite.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/server/api/endpoints/admin/invite.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | @ -4,43 +4,43 @@ import getParams from '../../get-params'; | ||||||
| import User from '../../../../models/user'; | import User from '../../../../models/user'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
|   desc: { | 	desc: { | ||||||
|     ja: '指定したユーザーを凍結します。', | 		ja: '指定したユーザーを凍結します。', | ||||||
|     en: 'Suspend a user.' | 		en: 'Suspend a user.' | ||||||
|   }, | 	}, | ||||||
| 
 | 
 | ||||||
|   requireCredential: true, | 	requireCredential: true, | ||||||
|   requireAdmin: true, | 	requireAdmin: true, | ||||||
| 
 | 
 | ||||||
|   params: { | 	params: { | ||||||
|     userId: $.type(ID).note({ | 		userId: $.type(ID).note({ | ||||||
|       desc: { | 			desc: { | ||||||
|         ja: '対象のユーザーID', | 				ja: '対象のユーザーID', | ||||||
|         en: 'The user ID which you want to suspend' | 				en: 'The user ID which you want to suspend' | ||||||
|       } | 			} | ||||||
|     }), | 		}), | ||||||
|   } | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default (params: any) => new Promise(async (res, rej) => { | export default (params: any) => new Promise(async (res, rej) => { | ||||||
|   const [ps, psErr] = getParams(meta, params); | 	const [ps, psErr] = getParams(meta, params); | ||||||
|   if (psErr) return rej(psErr); | 	if (psErr) return rej(psErr); | ||||||
| 
 | 
 | ||||||
|   const user = await User.findOne({ | 	const user = await User.findOne({ | ||||||
|     _id: ps.userId | 		_id: ps.userId | ||||||
|   }); | 	}); | ||||||
| 
 | 
 | ||||||
|   if (user == null) { | 	if (user == null) { | ||||||
|     return rej('user not found'); | 		return rej('user not found'); | ||||||
|   } | 	} | ||||||
| 
 | 
 | ||||||
|   await User.findOneAndUpdate({ | 	await User.findOneAndUpdate({ | ||||||
|     _id: user._id | 		_id: user._id | ||||||
|   }, { | 	}, { | ||||||
|       $set: { | 			$set: { | ||||||
|         isSuspended: true | 				isSuspended: true | ||||||
|       } | 			} | ||||||
|     }); | 		}); | ||||||
| 
 | 
 | ||||||
|   res(); | 	res(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ export default () => new Promise(async (res, rej) => { | ||||||
| 			model: os.cpus()[0].model, | 			model: os.cpus()[0].model, | ||||||
| 			cores: os.cpus().length | 			cores: os.cpus().length | ||||||
| 		}, | 		}, | ||||||
| 		broadcasts: meta.broadcasts | 		broadcasts: meta.broadcasts, | ||||||
|  | 		disableRegistration: meta.disableRegistration | ||||||
| 	}); | 	}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import User, { IUser, validateUsername, validatePassword, pack } from '../../../ | ||||||
| import generateUserToken from '../common/generate-native-user-token'; | import generateUserToken from '../common/generate-native-user-token'; | ||||||
| import config from '../../../config'; | import config from '../../../config'; | ||||||
| import Meta from '../../../models/meta'; | import Meta from '../../../models/meta'; | ||||||
|  | import RegistrationTicket from '../../../models/registration-tickets'; | ||||||
| 
 | 
 | ||||||
| if (config.recaptcha) { | if (config.recaptcha) { | ||||||
| 	recaptcha.init({ | 	recaptcha.init({ | ||||||
|  | @ -29,6 +30,29 @@ export default async (ctx: Koa.Context) => { | ||||||
| 
 | 
 | ||||||
| 	const username = body['username']; | 	const username = body['username']; | ||||||
| 	const password = body['password']; | 	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
 | 	// Validate username
 | ||||||
| 	if (!validateUsername(username)) { | 	if (!validateUsername(username)) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue