diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 3cda56c5f..11a242c8c 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -24,6 +24,7 @@ rules: '@typescript-eslint/naming-convention': off '@typescript-eslint/no-redeclare': off + class-methods-use-this: off global-require: off guard-for-in: off implicit-arrow-linebreak: off diff --git a/build/build.js b/build/build.js index 23c6979c8..6d793cc33 100644 --- a/build/build.js +++ b/build/build.js @@ -13,6 +13,7 @@ const compilerOptionsBase = { composite: true, strictBindCallApply: true, experimentalDecorators: true, + emitDecoratorMetadata: true, }; const config = { compilerOptions: compilerOptionsBase, diff --git a/package.json b/package.json index b311080cd..0e2bdc1ce 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,12 @@ "@shelf/jest-mongodb": "^1.2.3", "@types/cross-spawn": "^6.0.2", "@types/jest": "^26.0.20", - "@types/node": "^14.14.31", + "@types/node": "^14.14.34", "@types/semver": "^7.3.4", - "@typescript-eslint/eslint-plugin": "^4.16.1", - "@typescript-eslint/parser": "^4.16.1", + "@typescript-eslint/eslint-plugin": "^4.17.0", + "@typescript-eslint/parser": "^4.17.0", "cross-spawn": "^7.0.3", - "eslint": "^7.21.0", + "eslint": "^7.22.0", "eslint-config-airbnb-typescript": "^12.0.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", diff --git a/packages/fps-importer/package.json b/packages/fps-importer/package.json index fe9ece0d6..a6021e09d 100644 --- a/packages/fps-importer/package.json +++ b/packages/fps-importer/package.json @@ -7,7 +7,7 @@ "author": "undefined ", "license": "AGPL-3.0-only", "dependencies": { - "adm-zip": "^0.5.3", + "adm-zip": "^0.5.4", "decode-html": "^2.0.0", "fs-extra": "^9.1.0", "xml2js": "^0.4.23" diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 941d9f0a6..8381168d4 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -9,7 +9,7 @@ "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", "dependencies": { - "adm-zip": "^0.5.3", + "adm-zip": "^0.5.4", "axios": "^0.21.1", "bson": "^4.2.3", "fs-extra": "^9.1.0", @@ -17,7 +17,7 @@ "lodash": "^4.17.21", "p-queue": "^6.6.1", "shell-quote": "^1.7.2", - "systeminformation": "5.3.3", + "systeminformation": "5.6.2", "ws": "^7.4.4", "yargs": "^16.2.0" }, diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 59ed86e73..c64219344 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -11,7 +11,7 @@ "node": ">=13" }, "dependencies": { - "adm-zip": "^0.5.3", + "adm-zip": "^0.5.4", "ansi_up": "^5.0.0", "cookies": "^0.8.0", "detect-browser": "^5.1.1", @@ -34,7 +34,7 @@ "serialize-javascript": "^5.0.1", "sockjs": "^0.3.20", "superagent": "^6.1.0", - "systeminformation": "5.3.3", + "systeminformation": "5.6.2", "wtfnode": "^0.8.4", "yargs": "^16.2.0" }, @@ -50,7 +50,7 @@ "@types/lru-cache": "^5.1.0", "@types/minio": "^7.0.7", "@types/mongodb": "^3.6.9", - "@types/nodemailer": "^6.4.0", + "@types/nodemailer": "^6.4.1", "@types/serialize-javascript": "^5.0.0", "@types/sockjs": "^0.3.32", "@types/superagent": "^4.1.8", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 2898e7a35..15fd385cc 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -15,7 +15,7 @@ import * as document from '../model/document'; import * as problem from '../model/problem'; import * as domain from '../model/domain'; import * as record from '../model/record'; -import * as user from '../model/user'; +import user from '../model/user'; import * as message from '../model/message'; import { Route, Handler, Types, param, diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index 04efd2536..699c8e838 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -6,7 +6,7 @@ import { } from '../interface'; import paginate from '../lib/paginate'; import * as system from '../model/system'; -import * as user from '../model/user'; +import user from '../model/user'; import * as oplog from '../model/oplog'; import * as message from '../model/message'; import * as discussion from '../model/discussion'; diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index cc59efa9c..c0cfbe0db 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -6,7 +6,7 @@ import { } from '../error'; import { log2 } from '../utils'; import type { DomainDoc } from '../interface'; -import * as user from '../model/user'; +import user from '../model/user'; import * as domain from '../model/domain'; import * as discussion from '../model/discussion'; import * as system from '../model/system'; diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index fa31b83cd..286e0ce7d 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -14,11 +14,11 @@ import * as contest from '../model/contest'; import * as message from '../model/message'; import * as document from '../model/document'; import * as system from '../model/system'; -import * as user from '../model/user'; +import user from '../model/user'; import * as setting from '../model/setting'; import * as domain from '../model/domain'; import * as discussion from '../model/discussion'; -import * as token from '../model/token'; +import token from '../model/token'; import * as training from '../model/training'; import { PERM, PRIV } from '../model/builtin'; import { diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 2826f8c9b..60201bcc6 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -14,7 +14,7 @@ import { import * as bus from '../service/bus'; import * as domain from '../model/domain'; import { PERM } from '../model/builtin'; -import * as user from '../model/user'; +import user from '../model/user'; import * as system from '../model/system'; import * as contest from '../model/contest'; import * as discussion from '../model/discussion'; diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 87db68d70..38ef0be9c 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -5,7 +5,7 @@ import { ValidationError } from '../error'; import * as check from '../check'; import * as setting from '../model/setting'; import * as system from '../model/system'; -import * as user from '../model/user'; +import user from '../model/user'; import { STATUS, PRIV } from '../model/builtin'; import * as record from '../model/record'; import { diff --git a/packages/hydrooj/src/handler/misc.ts b/packages/hydrooj/src/handler/misc.ts index 3a900b1a3..9dc24b734 100644 --- a/packages/hydrooj/src/handler/misc.ts +++ b/packages/hydrooj/src/handler/misc.ts @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import { BadRequestError } from '../error'; import { PRIV } from '../model/builtin'; -import * as user from '../model/user'; +import user from '../model/user'; import db from '../service/db'; import { Route, Handler, Types, param, diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 9ea6a3a07..7bc13cd69 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -15,7 +15,7 @@ import * as system from '../model/system'; import * as problem from '../model/problem'; import * as record from '../model/record'; import * as domain from '../model/domain'; -import * as user from '../model/user'; +import user from '../model/user'; import * as solution from '../model/solution'; import { PERM, PRIV } from '../model/builtin'; import storage from '../service/storage'; diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 20fd8c182..61e10b414 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -1,17 +1,17 @@ import { FilterQuery, ObjectID } from 'mongodb'; import { PermissionError, RecordNotFoundError } from '../error'; +import { Rdoc } from '../interface'; import { PERM, STATUS, PRIV } from '../model/builtin'; import * as system from '../model/system'; import * as problem from '../model/problem'; import * as record from '../model/record'; import * as contest from '../model/contest'; -import * as user from '../model/user'; +import user from '../model/user'; import paginate from '../lib/paginate'; import * as bus from '../service/bus'; import { Route, Handler, Connection, ConnectionHandler, Types, param, } from '../service/server'; -import { Rdoc } from '../interface'; const RecordHandler = contest.ContestHandlerMixin(Handler); @@ -146,14 +146,10 @@ class RecordMainConnectionHandler extends RecordConnectionHandler { @param('tid', Types.ObjectID, true) async prepare(domainId: string, tid?: ObjectID) { - this.domainId = domainId; if (tid) { const tdoc = await contest.get(domainId, tid, -1); if (this.canShowRecord(tdoc)) this.tid = tid.toHexString(); - else { - this.close(); - return; - } + else throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); } this.dispose = bus.on('record/change', this.onRecordChange.bind(this)); } @@ -161,12 +157,8 @@ class RecordMainConnectionHandler extends RecordConnectionHandler { async message(msg) { if (msg.rids instanceof Array) { const rids = msg.rids.map((id: string) => new ObjectID(id)); - const rdocs = await record.getMulti( - this.domainId, { _id: { $in: rids } }, - ).toArray(); - for (const rdoc of rdocs) { - this.onRecordChange(rdoc); - } + const rdocs = await record.getMulti(this.domainId, { _id: { $in: rids } }).toArray(); + for (const rdoc of rdocs) this.onRecordChange(rdoc); } } @@ -199,10 +191,7 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti const rdoc = await record.get(domainId, rid); if (rdoc.contest) { const tdoc = await contest.get(domainId, rdoc.contest.tid, -1); - if (!this.canShowRecord(tdoc)) { - this.close(); - return; - } + if (!this.canShowRecord(tdoc)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); } this.rid = rid.toString(); this.dispose = bus.on('record/change', this.onRecordChange.bind(this)); diff --git a/packages/hydrooj/src/handler/remote.ts b/packages/hydrooj/src/handler/remote.ts index 849021747..35252a961 100644 --- a/packages/hydrooj/src/handler/remote.ts +++ b/packages/hydrooj/src/handler/remote.ts @@ -5,7 +5,7 @@ import { PermissionError, InvalidTokenError, RemoteOnlineJudgeError } from '../e import { Logger } from '../logger'; import { ProblemAdd } from '../lib/ui'; import { PERM } from '../model/builtin'; -import * as token from '../model/token'; +import token from '../model/token'; import * as problem from '../model/problem'; import * as system from '../model/system'; import { diff --git a/packages/hydrooj/src/handler/training.ts b/packages/hydrooj/src/handler/training.ts index aff8405a7..e7dec5a0e 100644 --- a/packages/hydrooj/src/handler/training.ts +++ b/packages/hydrooj/src/handler/training.ts @@ -9,7 +9,7 @@ import { PERM, PRIV } from '../model/builtin'; import * as problem from '../model/problem'; import * as builtin from '../model/builtin'; import * as training from '../model/training'; -import * as user from '../model/user'; +import user from '../model/user'; import * as bus from '../service/bus'; import { Route, Handler, Types, param, diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 8e6cfc9e7..00f9c9664 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -7,9 +7,9 @@ import { import { Route, Handler, Types, param, post, } from '../service/server'; -import * as user from '../model/user'; +import user from '../model/user'; import * as oauth from '../model/oauth'; -import * as token from '../model/token'; +import token from '../model/token'; import * as record from '../model/record'; import * as problem from '../model/problem'; import * as task from '../model/task'; diff --git a/packages/hydrooj/src/model/blacklist.ts b/packages/hydrooj/src/model/blacklist.ts index 011dbb040..a1e7bf32a 100644 --- a/packages/hydrooj/src/model/blacklist.ts +++ b/packages/hydrooj/src/model/blacklist.ts @@ -1,24 +1,30 @@ +import { ArgMethod } from '../utils'; import db from '../service/db'; import * as bus from '../service/bus'; const coll = db.collection('blacklist'); -export async function add(ip: string) { - const expireAt = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000); - const res = await coll.findOneAndUpdate( - { _id: ip }, - { $set: { expireAt } }, - { upsert: true, returnOriginal: false }, - ); - return res.value; -} +class BlackListModel { + @ArgMethod + static async add(ip: string) { + const expireAt = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000); + const res = await coll.findOneAndUpdate( + { _id: ip }, + { $set: { expireAt } }, + { upsert: true, returnOriginal: false }, + ); + return res.value; + } -export async function get(ip: string) { - return await coll.findOne({ _id: ip }); -} + @ArgMethod + static async get(ip: string) { + return await coll.findOne({ _id: ip }); + } -export async function del(ip: string) { - return await coll.deleteOne({ _id: ip }); + @ArgMethod + static async del(ip: string) { + return await coll.deleteOne({ _id: ip }); + } } async function ensureIndexes() { @@ -26,4 +32,5 @@ async function ensureIndexes() { } bus.once('app/started', ensureIndexes); -global.Hydro.model.blacklist = { add, get, del }; +export = BlackListModel; +global.Hydro.model.blacklist = BlackListModel; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 246b52884..030854601 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -1,5 +1,5 @@ import { ObjectID, FilterQuery } from 'mongodb'; -import * as user from './user'; +import user from './user'; import * as problem from './problem'; import * as document from './document'; import { PERM } from './builtin'; diff --git a/packages/hydrooj/src/model/message.ts b/packages/hydrooj/src/model/message.ts index fa2781257..2168d9447 100644 --- a/packages/hydrooj/src/model/message.ts +++ b/packages/hydrooj/src/model/message.ts @@ -1,5 +1,5 @@ import { FilterQuery, ObjectID } from 'mongodb'; -import * as user from './user'; +import user from './user'; import { Mdoc } from '../interface'; import db from '../service/db'; import * as bus from '../service/bus'; diff --git a/packages/hydrooj/src/model/token.ts b/packages/hydrooj/src/model/token.ts index 8adcebca1..7689b9d61 100644 --- a/packages/hydrooj/src/model/token.ts +++ b/packages/hydrooj/src/model/token.ts @@ -1,117 +1,105 @@ import { FilterQuery } from 'mongodb'; +import { ArgMethod } from '../utils'; import { TokenDoc } from '../interface'; import db from '../service/db'; import * as bus from '../service/bus'; -export const coll = db.collection('token'); +class TokenModel { + static coll = db.collection('token'); + static TYPE_SESSION = 0; + static TYPE_REGISTRATION = 2; + static TYPE_CHANGEMAIL = 3; + static TYPE_OAUTH = 4; + static TYPE_LOSTPASS = 5; + static TYPE_EXPORT = 6; + static TYPE_IMPORT = 7; -export const TYPE_SESSION = 0; -export const TYPE_REGISTRATION = 2; -export const TYPE_CHANGEMAIL = 3; -export const TYPE_OAUTH = 4; -export const TYPE_LOSTPASS = 5; -export const TYPE_EXPORT = 6; -export const TYPE_IMPORT = 7; + static async add( + tokenType: number, expireSeconds: number, data: any, id = String.random(32), + ): Promise<[string, TokenDoc]> { + const now = new Date(); + const res = await TokenModel.coll.insertOne({ + ...data, + _id: id, + tokenType, + createAt: now, + updateAt: now, + expireAt: new Date(now.getTime() + expireSeconds * 1000), + }); + return [id, res.ops[0]]; + } + + @ArgMethod + static async get(tokenId: string, tokenType: number): Promise { + return await TokenModel.coll.findOne({ _id: tokenId, tokenType }); + } + + static getMulti(tokenType: number, query: FilterQuery = {}) { + return TokenModel.coll.find({ tokenType, ...query }); + } + + static async update( + tokenId: string, tokenType: number, expireSeconds: number, + data: object, + ) { + const now = new Date(); + const res = await TokenModel.coll.findOneAndUpdate( + { _id: tokenId, tokenType }, + { + $set: { + ...data, + updateAt: now, + expireAt: new Date(now.getTime() + expireSeconds * 1000), + tokenType, + }, + }, + { returnOriginal: false }, + ); + return res.value; + } + + @ArgMethod + static async del(tokenId: string, tokenType: number) { + const result = await TokenModel.coll.deleteOne({ _id: tokenId, tokenType }); + return !!result.deletedCount; + } + + static async createOrUpdate( + tokenType: number, expireSeconds: number, data: any, + ): Promise { + const d = await TokenModel.coll.findOne({ tokenType, ...data }); + if (!d) { + const res = await TokenModel.add(tokenType, expireSeconds, data); + return res[0]; + } + await TokenModel.update(d._id, tokenType, expireSeconds, data); + return d._id; + } + + @ArgMethod + static getSessionListByUid(uid: number) { + return TokenModel.coll.find({ uid, tokenType: TokenModel.TYPE_SESSION }).sort('updateAt', -1).toArray(); + } + + @ArgMethod + static async getMostRecentSessionByUid(uid: number) { + const res = await TokenModel.coll.find({ uid, tokenType: TokenModel.TYPE_SESSION }).sort('updateAt', -1).toArray(); + return res[0]; + } + + @ArgMethod + static delByUid(uid: number) { + return TokenModel.coll.deleteMany({ uid }); + } +} function ensureIndexes() { return Promise.all([ - coll.createIndex({ uid: 1, tokenType: 1, updateAt: -1 }, { sparse: true }), - coll.createIndex('expireAt', { expireAfterSeconds: 0 }), + TokenModel.coll.createIndex({ uid: 1, tokenType: 1, updateAt: -1 }, { sparse: true }), + TokenModel.coll.createIndex('expireAt', { expireAfterSeconds: 0 }), ]); } -export async function add( - tokenType: number, expireSeconds: number, data: any, id = String.random(32), -): Promise<[string, TokenDoc]> { - const now = new Date(); - const res = await coll.insertOne({ - ...data, - _id: id, - tokenType, - createAt: now, - updateAt: now, - expireAt: new Date(now.getTime() + expireSeconds * 1000), - }); - return [id, res.ops[0]]; -} - -export async function get(tokenId: string, tokenType: number): Promise { - return await coll.findOne({ _id: tokenId, tokenType }); -} - -export function getMulti(tokenType: number, query: FilterQuery = {}) { - return coll.find({ tokenType, ...query }); -} - -export async function update( - tokenId: string, tokenType: number, expireSeconds: number, - data: object, -) { - const now = new Date(); - const res = await coll.findOneAndUpdate( - { _id: tokenId, tokenType }, - { - $set: { - ...data, - updateAt: now, - expireAt: new Date(now.getTime() + expireSeconds * 1000), - tokenType, - }, - }, - { returnOriginal: false }, - ); - return res.value; -} - -export async function del(tokenId: string, tokenType: number) { - const result = await coll.deleteOne({ _id: tokenId, tokenType }); - return !!result.deletedCount; -} - -export async function createOrUpdate( - tokenType: number, expireSeconds: number, data: any, -): Promise { - const d = await coll.findOne({ tokenType, ...data }); - if (!d) { - const res = await add(tokenType, expireSeconds, data); - return res[0]; - } - await update(d._id, tokenType, expireSeconds, data); - return d._id; -} - -export function getSessionListByUid(uid: number) { - return coll.find({ uid, tokenType: TYPE_SESSION }).sort('updateAt', -1).toArray(); -} - -export async function getMostRecentSessionByUid(uid: number) { - const res = await coll.find({ uid, tokenType: TYPE_SESSION }).sort('updateAt', -1).toArray(); - return res[0]; -} - -export function delByUid(uid: number) { - return coll.deleteMany({ uid }); -} - bus.once('app/started', ensureIndexes); -global.Hydro.model.token = { - coll, - - TYPE_SESSION, - TYPE_CHANGEMAIL, - TYPE_OAUTH, - TYPE_REGISTRATION, - TYPE_LOSTPASS, - TYPE_EXPORT, - TYPE_IMPORT, - - add, - createOrUpdate, - get, - getMulti, - update, - del, - delByUid, - getMostRecentSessionByUid, - getSessionListByUid, -}; +export = TokenModel; +global.Hydro.model.token = TokenModel; diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index fc9737664..55b1f2dc9 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -1,9 +1,10 @@ import { Collection } from 'mongodb'; import * as system from './system'; -import * as token from './token'; +import token from './token'; import * as setting from './setting'; import * as domain from './domain'; import { BUILTIN_USERS, PERM, PRIV } from './builtin'; +import { ArgMethod } from '../utils'; import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error'; import { User as _User, Udoc, Udict } from '../interface'; import { Value } from '../typeutils'; @@ -15,66 +16,23 @@ import * as bus from '../service/bus'; const coll: Collection = db.collection('user'); const logger = new Logger('model/user'); -export async function setPassword(uid: number, password: string): Promise { - const salt = String.random(); - const res = await coll.findOneAndUpdate( - { _id: uid }, - { $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } }, - { returnOriginal: false }, - ); - return res.value; -} - -export const defaultUser: Udoc = { - _id: 0, - uname: 'Unknown User', - unameLower: 'unknown user', - gravatar: 'unknown@hydro.local', - mail: 'unknown@hydro.local', - mailLower: 'unknown@hydro.local', - salt: '', - hash: '', - hashType: 'hydro', - priv: 0, - regat: new Date('2000-01-01'), - loginat: new Date('2000-01-01'), - regip: '127.0.0.1', - loginip: '127.0.0.1', -}; - -export class User implements _User { +class User implements _User { udoc: () => any; - dudoc: () => any; - _id: number; - mail: string; - uname: string; - salt: () => string; - hash: () => string; - hashType: string; - priv: number; - regat: Date; - loginat: Date; - perm: () => bigint; - role: string; - regip: () => string; - loginip: () => string; - scope: () => bigint; - [key: string]: any; constructor(udoc: Udoc, dudoc, scope = PERM.PERM_ALL) { @@ -132,139 +90,183 @@ export class User implements _User { if (h(password, this.salt(), this) !== this.hash()) { throw new LoginError(this.uname); } else if (this.hashType !== 'hydro') { - setPassword(this._id, password); + // eslint-disable-next-line @typescript-eslint/no-use-before-define + UserModel.setPassword(this._id, password); } } } -export async function getById(domainId: string, _id: number, scope = PERM.PERM_ALL): Promise { - const udoc = _id === 0 || _id === 1 - ? BUILTIN_USERS[_id] - : await coll.findOne({ _id }); - if (!udoc) return null; - const dudoc = await domain.getDomainUser(domainId, udoc); - return new User(udoc, dudoc, scope); -} +class UserModel { + static User = User; + static defaultUser: Udoc = { + _id: 0, + uname: 'Unknown User', + unameLower: 'unknown user', + gravatar: 'unknown@hydro.local', + mail: 'unknown@hydro.local', + mailLower: 'unknown@hydro.local', + salt: '', + hash: '', + hashType: 'hydro', + priv: 0, + regat: new Date('2000-01-01'), + loginat: new Date('2000-01-01'), + regip: '127.0.0.1', + loginip: '127.0.0.1', + }; -export async function getList(domainId: string, uids: number[]): Promise { - const _uids = new Set(uids); - const r = {}; - // eslint-disable-next-line no-await-in-loop - for (const uid of _uids) r[uid] = (await getById(domainId, uid)) || defaultUser; - return r; -} - -export async function getByUname(domainId: string, uname: string): Promise { - const unameLower = uname.trim().toLowerCase(); - const udoc = (unameLower === 'guest') - ? BUILTIN_USERS[0] - : unameLower === 'hydro' - ? BUILTIN_USERS[1] - : await coll.findOne({ unameLower }); - if (!udoc) return null; - const dudoc = await domain.getDomainUser(domainId, udoc); - return new User(udoc, dudoc); -} - -export async function getByEmail(domainId: string, mail: string): Promise { - const mailLower = mail.trim().toLowerCase(); - const udoc = (mailLower === 'guest@hydro.local') - ? BUILTIN_USERS[0] - : mailLower === 'hydro@hydro.local' - ? BUILTIN_USERS[1] - : await coll.findOne({ mailLower }); - if (!udoc) return null; - const dudoc = await domain.getDomainUser(domainId, udoc); - return new User(udoc, dudoc); -} - -export async function setById(uid: number, $set?: Partial, $unset?: Value, ''>) { - const op: any = {}; - if ($set && Object.keys($set).length) op.$set = $set; - if ($unset && Object.keys($unset).length) op.$unset = $unset; - return await coll.findOneAndUpdate({ _id: uid }, op); -} - -export function setEmail(uid: number, mail: string) { - return setById(uid, { mail, mailLower: mail.trim().toLowerCase() }); -} - -export async function inc(_id: number, field: string, n = 1) { - const udoc = await coll.findOne({ _id }); - if (!udoc) throw new UserNotFoundError(_id); - udoc[field] = udoc[field] + n || n; - await coll.updateOne({ _id }, { $set: { [field]: udoc[field] } }); - return udoc; -} - -export async function create( - mail: string, uname: string, password: string, - uid = 0, regip = '127.0.0.1', priv = PRIV.PRIV_DEFAULT, -) { - const salt = String.random(); - if (!uid) uid = await system.inc('user'); - try { - await coll.insertOne({ - _id: uid, - mail, - mailLower: mail.trim().toLowerCase(), - uname, - unameLower: uname.trim().toLowerCase(), - hash: pwhash(password.toString(), salt), - salt, - hashType: 'hydro', - regat: new Date(), - regip, - loginat: new Date(), - loginip: regip, - priv, - gravatar: mail, - }); - } catch (e) { - logger.warn('%o', e); - throw new UserAlreadyExistError([uid, uname, mail]); + @ArgMethod + static async getById(domainId: string, _id: number, scope: bigint = PERM.PERM_ALL): Promise { + const udoc = _id === 0 || _id === 1 + ? BUILTIN_USERS[_id] + : await coll.findOne({ _id }); + if (!udoc) return null; + const dudoc = await domain.getDomainUser(domainId, udoc); + return new User(udoc, dudoc, scope); } - return uid; -} -export function getMulti(params: any = {}) { - return coll.find(params); -} + static async getList(domainId: string, uids: number[]): Promise { + const _uids = new Set(uids); + const r = {}; + // eslint-disable-next-line no-await-in-loop + for (const uid of _uids) r[uid] = (await UserModel.getById(domainId, uid)) || UserModel.defaultUser; + return r; + } -export async function getPrefixList(domainId: string, prefix: string, limit = 50) { - prefix = prefix.toLowerCase(); - const $regex = new RegExp(`\\A\\Q${prefix}\\E`, 'gmi'); - const uids = await coll.find({ unameLower: { $regex } }) - .limit(limit).map((doc) => doc._id).toArray(); - const users = []; - for (const uid of uids) users.push(getById(domainId, uid)); - return await Promise.all(users); -} + @ArgMethod + static async getByUname(domainId: string, uname: string): Promise { + const unameLower = uname.trim().toLowerCase(); + const udoc = (unameLower === 'guest') + ? BUILTIN_USERS[0] + : unameLower === 'hydro' + ? BUILTIN_USERS[1] + : await coll.findOne({ unameLower }); + if (!udoc) return null; + const dudoc = await domain.getDomainUser(domainId, udoc); + return new UserModel.User(udoc, dudoc); + } -export async function setPriv(uid: number, priv: number): Promise { - const udoc = await coll.findOneAndUpdate( - { _id: uid }, - { $set: { priv } }, - { returnOriginal: false }, - ); - return udoc.value; -} + @ArgMethod + static async getByEmail(domainId: string, mail: string): Promise { + const mailLower = mail.trim().toLowerCase(); + const udoc = (mailLower === 'guest@hydro.local') + ? BUILTIN_USERS[0] + : mailLower === 'hydro@hydro.local' + ? BUILTIN_USERS[1] + : await coll.findOne({ mailLower }); + if (!udoc) return null; + const dudoc = await domain.getDomainUser(domainId, udoc); + return new UserModel.User(udoc, dudoc); + } -export async function setSuperAdmin(uid: number) { - await setPriv(uid, PRIV.PRIV_ALL); - return uid; -} + @ArgMethod + static async setById(uid: number, $set?: Partial, $unset?: Value, ''>) { + const op: any = {}; + if ($set && Object.keys($set).length) op.$set = $set; + if ($unset && Object.keys($unset).length) op.$unset = $unset; + return await coll.findOneAndUpdate({ _id: uid }, op); + } -export async function setJudge(uid: number) { - await setPriv(uid, PRIV.PRIV_USER_PROFILE | PRIV.PRIV_JUDGE); - return uid; -} + @ArgMethod + static setEmail(uid: number, mail: string) { + return UserModel.setById(uid, { mail, mailLower: mail.trim().toLowerCase() }); + } -export function ban(uid: number) { - return Promise.all([ - setPriv(uid, PRIV.PRIV_NONE), - token.delByUid(uid), - ]); + @ArgMethod + static async setPassword(uid: number, password: string): Promise { + const salt = String.random(); + const res = await coll.findOneAndUpdate( + { _id: uid }, + { $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } }, + { returnOriginal: false }, + ); + return res.value; + } + + @ArgMethod + static async inc(_id: number, field: string, n: number = 1) { + const udoc = await coll.findOne({ _id }); + if (!udoc) throw new UserNotFoundError(_id); + udoc[field] = udoc[field] + n || n; + await coll.updateOne({ _id }, { $set: { [field]: udoc[field] } }); + return udoc; + } + + @ArgMethod + static async create( + mail: string, uname: string, password: string, + uid: number = 0, regip: string = '127.0.0.1', priv: number = PRIV.PRIV_DEFAULT, + ) { + const salt = String.random(); + if (!uid) uid = await system.inc('user'); + try { + await coll.insertOne({ + _id: uid, + mail, + mailLower: mail.trim().toLowerCase(), + uname, + unameLower: uname.trim().toLowerCase(), + hash: pwhash(password.toString(), salt), + salt, + hashType: 'hydro', + regat: new Date(), + regip, + loginat: new Date(), + loginip: regip, + priv, + gravatar: mail, + }); + } catch (e) { + logger.warn('%o', e); + throw new UserAlreadyExistError([uid, uname, mail]); + } + return uid; + } + + static getMulti(params: any = {}) { + return coll.find(params); + } + + @ArgMethod + static async getPrefixList(domainId: string, prefix: string, limit: number = 50) { + prefix = prefix.toLowerCase(); + const $regex = new RegExp(`\\A\\Q${prefix}\\E`, 'gmi'); + const uids = await coll.find({ unameLower: { $regex } }) + .limit(limit).map((doc) => doc._id).toArray(); + const users = []; + for (const uid of uids) users.push(UserModel.getById(domainId, uid)); + return await Promise.all(users); + } + + @ArgMethod + static async setPriv(uid: number, priv: number): Promise { + const udoc = await coll.findOneAndUpdate( + { _id: uid }, + { $set: { priv } }, + { returnOriginal: false }, + ); + return udoc.value; + } + + @ArgMethod + static async setSuperAdmin(uid: number) { + await UserModel.setPriv(uid, PRIV.PRIV_ALL); + return uid; + } + + @ArgMethod + static async setJudge(uid: number) { + await UserModel.setPriv(uid, PRIV.PRIV_USER_PROFILE | PRIV.PRIV_JUDGE); + return uid; + } + + @ArgMethod + static ban(uid: number) { + return Promise.all([ + UserModel.setPriv(uid, PRIV.PRIV_NONE), + token.delByUid(uid), + ]); + } } function ensureIndexes() { @@ -275,22 +277,5 @@ function ensureIndexes() { } bus.once('app/started', ensureIndexes); -global.Hydro.model.user = { - User, - defaultUser, - create, - getByEmail, - getById, - getByUname, - getMulti, - inc, - setById, - setEmail, - setPassword, - getPrefixList, - setPriv, - getList, - setSuperAdmin, - setJudge, - ban, -}; +export = UserModel; +global.Hydro.model.user = UserModel; diff --git a/packages/hydrooj/src/script/blacklist.ts b/packages/hydrooj/src/script/blacklist.ts index d6ef7a34b..a9dc0305a 100644 --- a/packages/hydrooj/src/script/blacklist.ts +++ b/packages/hydrooj/src/script/blacklist.ts @@ -2,8 +2,8 @@ import { ObjectID } from 'mongodb'; import * as document from '../model/document'; import * as discussion from '../model/discussion'; -import * as user from '../model/user'; -import * as blacklist from '../model/blacklist'; +import user from '../model/user'; +import blacklist from '../model/blacklist'; import db from '../service/db'; export const description = 'Add blacklist by ip, uid'; diff --git a/packages/hydrooj/src/script/deleteUser.ts b/packages/hydrooj/src/script/deleteUser.ts index e547ecca1..cee2dbf17 100644 --- a/packages/hydrooj/src/script/deleteUser.ts +++ b/packages/hydrooj/src/script/deleteUser.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ import { Udoc } from '../interface'; import { Value } from '../typeutils'; -import * as user from '../model/user'; +import user from '../model/user'; import * as document from '../model/document'; import * as setting from '../model/setting'; import db from '../service/db'; diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 1bf404c47..5f2e79c17 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -26,11 +26,11 @@ import { } from '../error'; import hash from '../lib/hash.hydro'; import * as misc from '../lib/misc'; -import * as user from '../model/user'; +import user from '../model/user'; import * as domain from '../model/domain'; import * as system from '../model/system'; -import * as blacklist from '../model/blacklist'; -import * as token from '../model/token'; +import blacklist from '../model/blacklist'; +import token from '../model/token'; import * as opcount from '../model/opcount'; import { PERM } from '../model/builtin'; diff --git a/packages/hydrooj/src/utils.ts b/packages/hydrooj/src/utils.ts index 3eff24963..82bd8e20d 100644 --- a/packages/hydrooj/src/utils.ts +++ b/packages/hydrooj/src/utils.ts @@ -279,3 +279,7 @@ export function errorMessage(err: Error | string) { err.stack = q.join('\n'); return err; } + +export function ArgMethod(target: any, funcName: string, obj: any) { + return obj; +} diff --git a/packages/import-qduoj/package.json b/packages/import-qduoj/package.json index fdb3e6e04..b9f69d43e 100644 --- a/packages/import-qduoj/package.json +++ b/packages/import-qduoj/package.json @@ -7,7 +7,7 @@ "author": "undefined ", "license": "AGPL-3.0-only", "dependencies": { - "adm-zip": "^0.5.3", + "adm-zip": "^0.5.4", "decode-html": "^2.0.0", "fs-extra": "^9.1.0" }, diff --git a/packages/syzoj-ng-api/handlers/problem.ts b/packages/syzoj-ng-api/handlers/problem.ts index f421a1694..5bad2c2ef 100644 --- a/packages/syzoj-ng-api/handlers/problem.ts +++ b/packages/syzoj-ng-api/handlers/problem.ts @@ -2,7 +2,7 @@ import { FilterQuery } from 'mongodb'; import { Pdoc } from 'hydrooj/dist/interface'; import * as problem from 'hydrooj/dist/model/problem'; import * as record from 'hydrooj/dist/model/record'; -import * as user from 'hydrooj/dist/model/user'; +import user from 'hydrooj/dist/model/user'; import { PERM, PRIV } from 'hydrooj/dist/model/builtin'; import { Route, Handler, Types, post,