core: drop legacy validator (#497)

This commit is contained in:
undefined authored and GitHub committed 2023-01-28 00:03:31 +08:00
1 parent d8f5193a24
commit 1f585968a4
19 files changed
+135 -171

No files matched your search

+3 -3
View File
@@ -534,7 +534,7 @@ export class ContestManagementHandler extends ContestManagementBaseHandler {
}
@param('tid', Types.ObjectID)
@post('filename', Types.Name, true)
@post('filename', Types.Filename, true)
async postUploadFile(domainId: string, tid: ObjectID, filename: string) {
if ((this.tdoc.files?.length || 0) >= system.get('limit.contest_files')) {
throw new FileLimitExceededError('count');
@@ -557,7 +557,7 @@ export class ContestManagementHandler extends ContestManagementBaseHandler {
}
@param('tid', Types.ObjectID)
@post('files', Types.ArrayOf(Types.Name))
@post('files', Types.ArrayOf(Types.Filename))
async postDeleteFiles(domainId: string, tid: ObjectID, files: string[]) {
await Promise.all([
storage.del(files.map((t) => `contest/${domainId}/${tid}/${t}`), this.user._id),
@@ -569,7 +569,7 @@ export class ContestManagementHandler extends ContestManagementBaseHandler {
export class ContestFileDownloadHandler extends ContestDetailBaseHandler {
@param('tid', Types.ObjectID)
@param('filename', Types.Name)
@param('filename', Types.Filename)
@param('noDisposition', Types.Boolean)
async get(domainId: string, tid: ObjectID, filename: string, noDisposition = false) {
this.response.addHeader('Cache-Control', 'public');
+3 -3
View File
@@ -29,7 +29,7 @@ class DiscussionHandler extends Handler {
drrdoc?: DiscussionTailReplyDoc;
vnode?: any;
@param('type', Types.Name, true)
@param('type', Types.Range(Object.keys(typeMapper)), true)
@param('name', Types.Name, true)
@param('did', Types.ObjectID, true)
@param('drid', Types.ObjectID, true)
@@ -94,7 +94,7 @@ class DiscussionMainHandler extends Handler {
}
class DiscussionNodeHandler extends DiscussionHandler {
@param('type', Types.Name)
@param('type', Types.Range(Object.keys(typeMapper)))
@param('name', Types.Name)
@param('page', Types.PositiveInt, true)
async get(domainId: string, type: string, _name: string, page = 1) {
@@ -140,7 +140,7 @@ class DiscussionCreateHandler extends DiscussionHandler {
this.response.body = { path, vnode: this.vnode };
}
@param('type', Types.Name)
@param('type', Types.Range(Object.keys(typeMapper)))
@param('title', Types.Title)
@param('content', Types.Content)
@param('highlight', Types.Boolean)
+5 -5
View File
@@ -159,7 +159,7 @@ class DomainUserHandler extends ManageHandler {
@requireSudo
@post('uid', Types.Int)
@post('role', Types.Name)
@post('role', Types.Role)
async postSetUser(domainId: string, uid: number, role: string) {
await Promise.all([
domain.setUserRole(domainId, uid, role),
@@ -170,7 +170,7 @@ class DomainUserHandler extends ManageHandler {
@requireSudo
@param('uid', Types.NumericArray)
@param('role', Types.Name)
@param('role', Types.Role)
async postSetUsers(domainId: string, uid: number[], role: string) {
await Promise.all([
domain.setUserRole(domainId, uid, role),
@@ -213,7 +213,7 @@ class DomainRoleHandler extends ManageHandler {
this.response.body = { roles, domain: this.domain };
}
@param('role', Types.Name)
@param('role', Types.Role)
async postAdd(domainId: string, role: string) {
const roles = await domain.getRoles(this.domain);
const rdict: Dictionary<any> = {};
@@ -224,7 +224,7 @@ class DomainRoleHandler extends ManageHandler {
}
@requireSudo
@param('roles', Types.ArrayOf(Types.Name))
@param('roles', Types.ArrayOf(Types.Role))
async postDelete(domainId: string, roles: string[]) {
if (Set.intersection(roles, ['root', 'default', 'guest']).size > 0) {
throw new ValidationError('role', null, 'You cannot delete root, default or guest roles');
@@ -251,7 +251,7 @@ class DomainJoinApplicationsHandler extends ManageHandler {
@requireSudo
@post('method', Types.Range([domain.JOIN_METHOD_NONE, domain.JOIN_METHOD_ALL, domain.JOIN_METHOD_CODE]))
@post('role', Types.Name, true)
@post('role', Types.Role, true)
@post('expire', Types.Int, true)
@post('invitationCode', Types.Content, true)
async post(domainId: string, method: number, role: string, expire: number, invitationCode = '') {
+6 -7
View File
@@ -13,7 +13,6 @@ import { DomainDoc, MessageDoc, Setting } from '../interface';
import avatar from '../lib/avatar';
import * as mail from '../lib/mail';
import * as useragent from '../lib/useragent';
import { isDomainId, isEmail, isPassword } from '../lib/validator';
import { verifyTFA } from '../lib/verifyTFA';
import BlackListModel from '../model/blacklist';
import { PERM, PRIV } from '../model/builtin';
@@ -180,8 +179,8 @@ class HomeSecurityHandler extends Handler {
@requireSudo
@param('current', Types.String)
@param('password', Types.String, isPassword)
@param('verifyPassword', Types.String)
@param('password', Types.Password)
@param('verifyPassword', Types.Password)
async postChangePassword(_: string, current: string, password: string, verify: string) {
if (password !== verify) throw new VerifyPasswordError();
this.user.checkPassword(current);
@@ -191,8 +190,8 @@ class HomeSecurityHandler extends Handler {
}
@requireSudo
@param('password', Types.String)
@param('mail', Types.Name, isEmail)
@param('password', Types.Password)
@param('mail', Types.Email)
async postChangeMail(domainId: string, current: string, email: string) {
const mailDomain = email.split('@')[1];
if (await BlackListModel.get(`mail::${mailDomain}`)) throw new BlacklistedError(mailDomain);
@@ -335,7 +334,7 @@ function set(s: Setting, key: string, value: any) {
}
class HomeSettingsHandler extends Handler {
@param('category', Types.Name)
@param('category', Types.Range(['preference', 'account', 'domain']))
async get(domainId: string, category: string) {
this.response.template = 'home_settings.html';
this.response.body = {
@@ -445,7 +444,7 @@ class HomeDomainCreateHandler extends Handler {
this.response.template = 'domain_create.html';
}
@param('id', Types.Name, isDomainId)
@param('id', Types.DomainId)
@param('name', Types.Title)
@param('bulletin', Types.Content)
@param('avatar', Types.Content, true)
+2 -3
View File
@@ -6,7 +6,6 @@ import * as check from '../check';
import {
CannotEditSuperAdminError, NotLaunchedByPM2Error, UserNotFoundError, ValidationError,
} from '../error';
import { isEmail, isPassword } from '../lib/validator';
import { Logger } from '../logger';
import { PRIV, STATUS } from '../model/builtin';
import domain from '../model/domain';
@@ -230,9 +229,9 @@ class SystemUserImportHandler extends SystemHandler {
let [email, username, password, displayName, extra] = u.split(',').map((t) => t.trim());
if (!email || !username || !password) [email, username, password, displayName, extra] = u.split('\t').map((t) => t.trim());
if (email && username && password) {
if (!isEmail(email)) messages.push(`Line ${+i + 1}: Invalid email.`);
if (!Types.Email[1](email)) messages.push(`Line ${+i + 1}: Invalid email.`);
else if (!Types.Username[1](username)) messages.push(`Line ${+i + 1}: Invalid username`);
else if (!isPassword(password)) messages.push(`Line ${+i + 1}: Invalid password`);
else if (!Types.Password[1](password)) messages.push(`Line ${+i + 1}: Invalid password`);
else if (await user.getByEmail('system', email)) {
messages.push(`Line ${+i + 1}: Email ${email} already exists.`);
} else if (await user.getByUname('system', username)) {
+5 -7
View File
@@ -44,7 +44,7 @@ export class FilesHandler extends Handler {
this.response.template = 'home_files.html';
}
@post('filename', Types.Name, true)
@post('filename', Types.Filename)
async postUploadFile(domainId: string, filename: string) {
this.checkPriv(PRIV.PRIV_CREATE_FILE);
if ((this.user._files?.length || 0) >= system.get('limit.user_files')) {
@@ -57,9 +57,7 @@ export class FilesHandler extends Handler {
if (size >= system.get('limit.user_files_size')) {
if (!this.user.hasPriv(PRIV.PRIV_UNLIMITED_QUOTA)) throw new FileLimitExceededError('size');
}
filename ||= file.originalFilename || String.random(16);
if (filename.includes('/') || filename.includes('..')) throw new ValidationError('filename', null, 'Bad filename');
if (this.user._files.filter((i) => i.name === filename).length) throw new FileExistsError(filename);
if (this.user._files.find((i) => i.name === filename)) throw new FileExistsError(filename);
await storage.put(`user/${this.user._id}/${filename}`, file.filepath, this.user._id);
const meta = await storage.getMeta(`user/${this.user._id}/${filename}`);
const payload = { name: filename, ...pick(meta, ['size', 'lastModified', 'etag']) };
@@ -69,7 +67,7 @@ export class FilesHandler extends Handler {
this.back();
}
@post('files', Types.ArrayOf(Types.Name))
@post('files', Types.ArrayOf(Types.Filename))
async postDeleteFiles(domainId: string, files: string[]) {
await Promise.all([
storage.del(files.map((t) => `user/${this.user._id}/${t}`), this.user._id),
@@ -83,7 +81,7 @@ export class FSDownloadHandler extends Handler {
noCheckPermView = true;
@param('uid', Types.Int)
@param('filename', Types.Name)
@param('filename', Types.Filename)
@param('noDisposition', Types.Boolean)
async get(domainId: string, uid: number, filename: string, noDisposition = false) {
const targetUser = await user.getById('system', uid);
@@ -111,7 +109,7 @@ export class StorageHandler extends Handler {
noCheckPermView = true;
@param('target', Types.Name)
@param('filename', Types.Name, true)
@param('filename', Types.Filename, true)
@param('expire', Types.UnsignedInt)
@param('secret', Types.String)
async get(domainId: string, target: string, filename = '', expire: number, secret: string) {
+12 -12
View File
@@ -2,7 +2,7 @@ import AdmZip from 'adm-zip';
import { readFile, statSync } from 'fs-extra';
import { isBinaryFile } from 'isbinaryfile';
import {
escapeRegExp, flattenDeep, intersection, isSafeInteger, pick, uniqBy,
escapeRegExp, flattenDeep, intersection, pick, uniqBy,
} from 'lodash';
import { FilterQuery, ObjectID } from 'mongodb';
import { nanoid } from 'nanoid';
@@ -18,7 +18,6 @@ import {
ProblemDoc, ProblemSearchOptions, ProblemStatusDoc, RecordDoc, User,
} from '../interface';
import paginate from '../lib/paginate';
import { isPid, parsePid as convertPid } from '../lib/validator';
import { PERM, PRIV, STATUS } from '../model/builtin';
import * as contest from '../model/contest';
import * as discussion from '../model/discussion';
@@ -40,7 +39,6 @@ import { registerResolver, registerValue } from './api';
import { ContestDetailBaseHandler } from './contest';
export const parseCategory = (value: string) => value.replace(/,/g, ',').split(',').map((e) => e.trim());
export const parsePid = (value: string) => (isSafeInteger(value) ? +value : value);
registerValue('FileInfo', [
['_id', 'String!'],
@@ -312,7 +310,7 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler {
udoc: User;
psdoc: ProblemStatusDoc;
@route('pid', Types.Name, true, null, parsePid)
@route('pid', Types.ProblemId, true)
@query('tid', Types.ObjectID, true)
async _prepare(domainId: string, pid: number | string, tid?: ObjectID) {
this.pdoc = await problem.get(domainId, pid);
@@ -628,17 +626,18 @@ export class ProblemEditHandler extends ProblemManageHandler {
this.response.template = 'problem_edit.html';
}
@route('pid', Types.Name, null, parsePid)
@route('pid', Types.ProblemId)
@post('title', Types.Title)
@post('content', Types.Content)
@post('pid', Types.Name, isPid, convertPid, true)
@post('pid', Types.ProblemId, true)
@post('hidden', Types.Boolean)
@post('tag', Types.Content, true, null, parseCategory)
@post('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
async post(
domainId: string, pid: string | number, title: string, content: string,
newPid: string = '', hidden = false, tag: string[] = [], difficulty = 0,
newPid: string | number = '', hidden = false, tag: string[] = [], difficulty = 0,
) {
if (typeof newPid !== 'string') newPid = `P${newPid}`;
if (newPid !== this.pdoc.pid && await problem.get(domainId, newPid)) throw new ProblemAlreadyExistError(pid);
const $update: Partial<ProblemDoc> = {
title, content, pid: newPid, hidden, tag: tag ?? [], difficulty, html: false,
@@ -722,7 +721,7 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
this.response.body.links = links;
}
@post('filename', Types.Name, true)
@post('filename', Types.Filename, true)
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postUploadFile(domainId: string, filename: string, type = 'testdata') {
if (this.pdoc.reference) throw new ProblemIsReferencedError('edit files');
@@ -784,7 +783,7 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
this.back();
}
@post('files', Types.ArrayOf(Types.Name))
@post('files', Types.ArrayOf(Types.Filename))
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postDeleteFiles(domainId: string, files: string[], type = 'testdata') {
if (this.pdoc.reference) throw new ProblemIsReferencedError('delete files');
@@ -797,7 +796,7 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
export class ProblemFileDownloadHandler extends ProblemDetailHandler {
@query('type', Types.Range(['additional_file', 'testdata']), true)
@param('filename', Types.Name)
@param('filename', Types.Filename)
@param('noDisposition', Types.Boolean)
async get(domainId: string, type = 'additional_file', filename: string, noDisposition = false) {
if (this.pdoc.reference) {
@@ -966,14 +965,15 @@ export class ProblemCreateHandler extends Handler {
@post('title', Types.Title)
@post('content', Types.Content)
@post('pid', Types.Name, true, isPid, convertPid)
@post('pid', Types.ProblemId, true)
@post('hidden', Types.Boolean)
@post('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
@post('tag', Types.Content, true, null, parseCategory)
async post(
domainId: string, title: string, content: string, pid: string,
domainId: string, title: string, content: string, pid: string | number = '',
hidden = false, difficulty = 0, tag: string[] = [],
) {
if (typeof pid !== 'string') pid = `P${pid}`;
if (pid && await problem.get(domainId, pid)) throw new ProblemAlreadyExistError(pid);
const docId = await problem.add(domainId, pid, title, content, this.user._id, tag ?? [], hidden);
const files = new Set(Array.from(content.matchAll(/file:\/\/([a-zA-Z0-9_-]+\.[a-zA-Z0-9]+)/g)).map((i) => i[1]));
+9 -9
View File
@@ -25,15 +25,15 @@ class RecordListHandler extends ContestDetailBaseHandler {
tdoc?: Tdoc<30>;
@param('page', Types.PositiveInt, true)
@param('pid', Types.Name, true)
@param('pid', Types.ProblemId, true)
@param('tid', Types.ObjectID, true)
@param('uidOrName', Types.Name, true)
@param('uidOrName', Types.UidOrName, true)
@param('lang', Types.String, true)
@param('status', Types.Int, true)
@param('fullStatus', Types.Boolean)
@param('allDomain', Types.Boolean, true)
async get(
domainId: string, page = 1, pid?: string, tid?: ObjectID,
domainId: string, page = 1, pid?: string | number, tid?: ObjectID,
uidOrName?: string, lang?: string, status?: number, full = false,
all = false,
) {
@@ -64,10 +64,10 @@ class RecordListHandler extends ContestDetailBaseHandler {
if (udoc) q.uid = udoc._id;
else invalid = true;
}
if (pid && tdoc && /^[A-Z]$/.test(pid)) {
pid = tdoc.pids[parseInt(pid, 36) - 10];
}
if (pid) {
if (typeof pid === 'string' && tdoc && /^[A-Z]$/.test(pid)) {
pid = tdoc.pids[parseInt(pid, 36) - 10];
}
const pdoc = await problem.get(domainId, pid);
if (pdoc) q.pid = pdoc.docId;
else invalid = true;
@@ -236,13 +236,13 @@ class RecordMainConnectionHandler extends ConnectionHandler {
tdoc: Tdoc<30>;
@param('tid', Types.ObjectID, true)
@param('pid', Types.Name, true)
@param('uidOrName', Types.Name, true)
@param('pid', Types.ProblemId, true)
@param('uidOrName', Types.UidOrName, true)
@param('status', Types.Int, true)
@param('pretest', Types.Boolean)
@param('allDomain', Types.Boolean)
async prepare(
domainId: string, tid?: ObjectID, pid?: string, uidOrName?: string,
domainId: string, tid?: ObjectID, pid?: string | number, uidOrName?: string,
status?: number, pretest = false, all = false,
) {
if (tid) {
+3 -5
View File
@@ -256,7 +256,7 @@ export class TrainingFilesHandler extends Handler {
}
@param('tid', Types.ObjectID)
@post('filename', Types.Name, true)
@post('filename', Types.Filename, true)
async postUploadFile(domainId: string, tid: ObjectID, filename: string) {
if ((this.tdoc.files?.length || 0) >= system.get('limit.contest_files')) {
throw new FileLimitExceededError('count');
@@ -268,8 +268,6 @@ export class TrainingFilesHandler extends Handler {
if (size >= system.get('limit.contest_files_size')) {
throw new FileLimitExceededError('size');
}
filename ||= file.originalFilename || String.random(16);
if (filename.includes('/') || filename.includes('..')) throw new ValidationError('filename', null, 'Bad filename');
await storage.put(`training/${domainId}/${tid}/${filename}`, file.filepath, this.user._id);
const meta = await storage.getMeta(`training/${domainId}/${tid}/${filename}`);
const payload = { _id: filename, name: filename, ...pick(meta, ['size', 'lastModified', 'etag']) };
@@ -279,7 +277,7 @@ export class TrainingFilesHandler extends Handler {
}
@param('tid', Types.ObjectID)
@post('files', Types.ArrayOf(Types.Name))
@post('files', Types.ArrayOf(Types.Filename))
async postDeleteFiles(domainId: string, tid: ObjectID, files: string[]) {
await Promise.all([
storage.del(files.map((t) => `contest/${domainId}/${tid}/${t}`), this.user._id),
@@ -290,7 +288,7 @@ export class TrainingFilesHandler extends Handler {
}
export class TrainingFileDownloadHandler extends Handler {
@param('tid', Types.ObjectID)
@param('filename', Types.Name)
@param('filename', Types.Filename)
@param('noDisposition', Types.Boolean)
async get(domainId: string, tid: ObjectID, filename: string, noDisposition = false) {
this.response.addHeader('Cache-Control', 'public');
+8 -9
View File
@@ -8,7 +8,6 @@ import {
import { Udoc, User } from '../interface';
import avatar from '../lib/avatar';
import { sendMail } from '../lib/mail';
import { isEmail, isPassword } from '../lib/validator';
import { verifyTFA } from '../lib/verifyTFA';
import BlackListModel from '../model/blacklist';
import { PERM, PRIV, STATUS } from '../model/builtin';
@@ -81,7 +80,7 @@ class UserLoginHandler extends Handler {
}
@param('uname', Types.Username)
@param('password', Types.String)
@param('password', Types.Password)
@param('rememberme', Types.Boolean)
@param('redirect', Types.String, true)
@param('tfa', Types.String, true)
@@ -225,7 +224,7 @@ export class UserRegisterHandler extends Handler {
this.response.template = 'user_register.html';
}
@post('mail', Types.String, true, isEmail)
@post('mail', Types.Email, true)
@post('phone', Types.String, true, (s) => /^\d{11}$/.test(s))
async post(domainId: string, mail: string, phoneNumber: string) {
if (mail) {
@@ -279,8 +278,8 @@ class UserRegisterWithCodeHandler extends Handler {
this.response.body = tdoc;
}
@param('password', Types.String, isPassword)
@param('verifyPassword', Types.String)
@param('password', Types.Password)
@param('verifyPassword', Types.Password)
@param('uname', Types.Username)
@param('code', Types.String)
async post(
@@ -312,7 +311,7 @@ class UserLostPassHandler extends Handler {
this.response.template = 'user_lostpass.html';
}
@param('mail', Types.String, isEmail)
@param('mail', Types.Email)
async post(domainId: string, mail: string) {
if (!system.get('smtp.user')) throw new SystemError('Cannot send mail');
const udoc = await user.getByEmail('system', mail);
@@ -347,8 +346,8 @@ class UserLostPassWithCodeHandler extends Handler {
}
@param('code', Types.String)
@param('password', Types.String, isPassword)
@param('verifyPassword', Types.String)
@param('password', Types.Password)
@param('verifyPassword', Types.Password)
async post(domainId: string, code: string, password: string, verifyPassword: string) {
const tdoc = await token.get(code, token.TYPE_LOSTPASS);
if (!tdoc) throw new InvalidTokenError(token.TYPE_TEXTS[token.TYPE_LOSTPASS], code);
@@ -429,7 +428,7 @@ class UserDeleteHandler extends Handler {
class OauthHandler extends Handler {
noCheckPermView = true;
@param('type', Types.String)
@param('type', Types.Key)
async get(domainId: string, type: string) {
await global.Hydro.module.oauth[type]?.get.call(this);
}