mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
使用 BigInt 进行权限管理
This commit is contained in:
1 parent
eaed0e84f5
commit
8aef05a375
17 files changed
+252
-197
No files matched your search
@@ -9,6 +9,7 @@ module.exports = {
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly',
|
||||
BigInt: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@requireCsrfToken
|
||||
为model.system添加缓存
|
||||
bug修复
|
||||
使用BigInt管理权限
|
||||
修复discussion前端权限节点显示异常
|
||||
+13
-2
@@ -114,6 +114,13 @@ class DomainUserHandler extends ManageHandler {
|
||||
class DomainPermissionHandler extends ManageHandler {
|
||||
async get({ domainId }) {
|
||||
const roles = await domain.getRoles(domainId);
|
||||
const log2 = (val: bigint) => {
|
||||
// @ts-ignore
|
||||
for (let i = 0n; ; i++) {
|
||||
// @ts-ignore
|
||||
if ((val >> i) === 0n) return parseInt(i.toString(), 10) - 1;
|
||||
}
|
||||
};
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['domain', null],
|
||||
@@ -121,7 +128,7 @@ class DomainPermissionHandler extends ManageHandler {
|
||||
];
|
||||
this.response.template = 'domain_permission.html';
|
||||
this.response.body = {
|
||||
roles, PERMS_BY_FAMILY, domain: this.domain, path,
|
||||
roles, PERMS_BY_FAMILY, domain: this.domain, path, log2,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -129,7 +136,11 @@ class DomainPermissionHandler extends ManageHandler {
|
||||
const roles = {};
|
||||
for (const role in this.request.body) {
|
||||
if (this.request.body[role] instanceof Array) {
|
||||
roles[role] = this.request.body[role].join('');
|
||||
const perms = this.request.body[role];
|
||||
// @ts-ignore
|
||||
roles[role] = 0n;
|
||||
// @ts-ignore
|
||||
for (const r of perms) roles[role] += 1n << BigInt(r);
|
||||
}
|
||||
}
|
||||
await domain.setRoles(domainId, roles);
|
||||
|
||||
@@ -82,7 +82,7 @@ class StatusHandler extends Handler {
|
||||
|
||||
class StatusUpdateHandler extends Handler {
|
||||
async post(args) {
|
||||
this.checkPerm(PRIV.PRIV_JUDGE);
|
||||
this.checkPriv(PRIV.PRIV_JUDGE);
|
||||
args.type = 'judger';
|
||||
return coll.updateOne(
|
||||
{ mid: args.mid, type: 'judger' },
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
NoProblemError, ProblemDataNotFoundError, BadRequestError,
|
||||
SolutionNotFoundError,
|
||||
} from '../error';
|
||||
import { Pdoc, Udoc, Rdoc } from '../interface';
|
||||
import { Pdoc, User, Rdoc } from '../interface';
|
||||
|
||||
const parseCategory = (value: string) => {
|
||||
if (!value) return [];
|
||||
@@ -165,7 +165,7 @@ class ProblemRandomHandler extends ProblemHandler {
|
||||
class ProblemDetailHandler extends ProblemHandler {
|
||||
pdoc: Pdoc;
|
||||
|
||||
udoc: Udoc;
|
||||
udoc: User;
|
||||
|
||||
@param('pid', Types.String, null, parsePid)
|
||||
async _prepare(domainId: string, pid: number | string) {
|
||||
@@ -457,12 +457,9 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
async postEditReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) {
|
||||
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
|
||||
if (!(psdoc.owner === this.user._id
|
||||
&& this.user.hasPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF_SOLUTION))) {
|
||||
if (!(psrdoc.owner === this.user._id
|
||||
&& this.user.hasPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF))) {
|
||||
this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
}
|
||||
if (!(psrdoc.owner === this.user._id
|
||||
&& this.user.hasPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF))) {
|
||||
this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
}
|
||||
await solution.editReply(domainId, psid, psrid, content);
|
||||
this.back();
|
||||
@@ -473,12 +470,9 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
async postDeleteReply(domainId: string, psid: ObjectID, psrid: ObjectID) {
|
||||
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
|
||||
if (!(psdoc.owner === this.user._id
|
||||
&& this.user.hasPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF_SOLUTION))) {
|
||||
if (!(psrdoc.owner === this.user._id
|
||||
&& this.user.hasPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF))) {
|
||||
this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY);
|
||||
}
|
||||
if (!(psrdoc.owner === this.user._id
|
||||
&& this.user.hasPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF))) {
|
||||
this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY);
|
||||
}
|
||||
await solution.delReply(domainId, psid, psrid);
|
||||
this.back();
|
||||
|
||||
@@ -172,9 +172,13 @@ class UserDetailHandler extends Handler {
|
||||
sdoc.updateIp = '';
|
||||
sdoc._id = '';
|
||||
}
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['user_detail', 'user_detail', { uid }],
|
||||
];
|
||||
this.response.template = 'user_detail.html';
|
||||
this.response.body = {
|
||||
isSelfProfile, udoc, sdoc, rdocs, pdict,
|
||||
isSelfProfile, udoc, sdoc, rdocs, pdict, path,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -321,7 +325,7 @@ class OauthCallbackHandler extends Handler {
|
||||
r.uname.push(String.random(16));
|
||||
for (const uname of r.uname) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const nudoc = await user.getByUname('system', uname, true);
|
||||
const nudoc = await user.getByUname('system', uname);
|
||||
if (!nudoc) {
|
||||
username = uname;
|
||||
break;
|
||||
|
||||
+19
-3
@@ -15,6 +15,22 @@ export interface Setting {
|
||||
}
|
||||
|
||||
export interface Udoc extends Dictionary<any> {
|
||||
_id: number,
|
||||
mail: string,
|
||||
mailLower: string,
|
||||
uname: string,
|
||||
unameLower: string,
|
||||
salt: string,
|
||||
hash: string,
|
||||
hashType: string,
|
||||
priv: number,
|
||||
regat: Date,
|
||||
loginat: Date,
|
||||
regip: string,
|
||||
loginip: string,
|
||||
}
|
||||
|
||||
export interface User extends Dictionary<any> {
|
||||
udoc: () => any,
|
||||
dudoc: () => any,
|
||||
_id: number,
|
||||
@@ -26,16 +42,16 @@ export interface Udoc extends Dictionary<any> {
|
||||
priv: number,
|
||||
regat: Date,
|
||||
loginat: Date,
|
||||
perm: string,
|
||||
perm: bigint,
|
||||
role: string,
|
||||
regip: () => string,
|
||||
loginip: () => string,
|
||||
hasPerm: (perm: string) => boolean,
|
||||
hasPerm: (perm: bigint) => boolean,
|
||||
hasPriv: (priv: number) => boolean,
|
||||
checkPassword: (password: string) => void,
|
||||
}
|
||||
|
||||
export type Udict = NumericDictionary<Udoc>;
|
||||
export type Udict = NumericDictionary<User>;
|
||||
|
||||
export interface Pdoc {
|
||||
_id: ObjectID,
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import { PERM, PRIV } from '../model/builtin';
|
||||
global.Hydro.ui.nav = [];
|
||||
|
||||
const trueChecker = () => true;
|
||||
const Checker = (perm: string, priv: number, checker: Function = trueChecker) => (handler) => (
|
||||
const Checker = (perm: bigint, priv: number, checker: Function = trueChecker) => (handler) => (
|
||||
checker(handler)
|
||||
&& (perm ? handler.user.hasPerm(perm) : true)
|
||||
&& (priv ? handler.user.hasPriv(priv) : true)
|
||||
@@ -12,10 +12,10 @@ const Checker = (perm: string, priv: number, checker: Function = trueChecker) =>
|
||||
|
||||
const Item = (
|
||||
name: string, args: any, prefix: string,
|
||||
...permPrivChecker: Array<number | string | Function | Array<number | string>>
|
||||
...permPrivChecker: Array<number | bigint | Function | Array<bigint | string>>
|
||||
) => {
|
||||
let _priv: number;
|
||||
let _perm: string;
|
||||
let _perm: bigint;
|
||||
let checker: Function = trueChecker;
|
||||
for (const item of permPrivChecker) {
|
||||
if (item instanceof Object && !isNull(item)) {
|
||||
@@ -23,7 +23,7 @@ const Item = (
|
||||
if (typeof item[0] === 'number') {
|
||||
// @ts-ignore
|
||||
_priv = item;
|
||||
} else if (typeof item[0] === 'string') {
|
||||
} else if (typeof item[0] === 'bigint') {
|
||||
// @ts-ignore
|
||||
_perm = item;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ const Item = (
|
||||
}
|
||||
} else if (typeof item === 'number') {
|
||||
_priv = item;
|
||||
} else if (typeof item === 'string') {
|
||||
} else if (typeof item === 'bigint') {
|
||||
_perm = item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import serialize from 'serialize-javascript';
|
||||
import nunjucks from 'nunjucks';
|
||||
import * as markdown from './markdown';
|
||||
import { STATUS, PERM, PRIV } from '../model/builtin';
|
||||
@@ -33,6 +34,7 @@ class Nunjucks extends nunjucks.Environment {
|
||||
constructor() {
|
||||
super(new Loader(), { autoescape: true, trimBlocks: true });
|
||||
this.addFilter('json', (self) => JSON.stringify(self), false);
|
||||
this.addFilter('serialize', (self, ignoreFunction = true) => serialize(self, { ignoreFunction }));
|
||||
this.addFilter('assign', (self, data) => Object.assign(self, data));
|
||||
this.addFilter('markdown', (self, safe = true) => {
|
||||
if (safe) return markdown.safe.render(self);
|
||||
|
||||
+122
-118
@@ -1,91 +1,95 @@
|
||||
// @ts-nocheck
|
||||
/* Why nocheck?
|
||||
* BitInt requires at least ES2020, but we can't use it as
|
||||
* NodeJS doesn't support some of this new features.
|
||||
* e.g.: object?.property
|
||||
*/
|
||||
export const PERM = {
|
||||
PERM_NONE: '',
|
||||
PERM_NONE: 0n,
|
||||
|
||||
// Domain Settings
|
||||
PERM_VIEW: '0',
|
||||
PERM_EDIT_DOMAIN: '1',
|
||||
PERM_MOD_BADGE: '2',
|
||||
PERM_VIEW: 1n << 0n,
|
||||
PERM_EDIT_DOMAIN: 1n << 1n,
|
||||
PERM_MOD_BADGE: 1n << 2n,
|
||||
|
||||
// Problem and Record
|
||||
PERM_CREATE_PROBLEM: '3',
|
||||
PERM_EDIT_PROBLEM: '4',
|
||||
PERM_EDIT_PROBLEM_SELF: '5',
|
||||
PERM_VIEW_PROBLEM: '6',
|
||||
PERM_VIEW_PROBLEM_HIDDEN: '7',
|
||||
PERM_SUBMIT_PROBLEM: '8',
|
||||
PERM_READ_PROBLEM_DATA: '9',
|
||||
PERM_READ_PROBLEM_DATA_SELF: 'A',
|
||||
PERM_READ_RECORD_CODE: 'B',
|
||||
PERM_REJUDGE_PROBLEM: 'C',
|
||||
PERM_REJUDGE: 'D',
|
||||
PERM_CREATE_PROBLEM: 1n << 4n,
|
||||
PERM_EDIT_PROBLEM: 1n << 5n,
|
||||
PERM_EDIT_PROBLEM_SELF: 1n << 6n,
|
||||
PERM_VIEW_PROBLEM: 1n << 7n,
|
||||
PERM_VIEW_PROBLEM_HIDDEN: 1n << 8n,
|
||||
PERM_SUBMIT_PROBLEM: 1n << 9n,
|
||||
PERM_READ_PROBLEM_DATA: 1n << 10n,
|
||||
PERM_READ_PROBLEM_DATA_SELF: 1n << 11n,
|
||||
PERM_READ_RECORD_CODE: 1n << 12n,
|
||||
PERM_REJUDGE_PROBLEM: 1n << 13n,
|
||||
PERM_REJUDGE: 1n << 14n,
|
||||
|
||||
// Problem Solution
|
||||
PERM_VIEW_PROBLEM_SOLUTION: 'E',
|
||||
PERM_CREATE_PROBLEM_SOLUTION: 'F',
|
||||
PERM_VOTE_PROBLEM_SOLUTION: 'G',
|
||||
PERM_EDIT_PROBLEM_SOLUTION: 'H',
|
||||
PERM_EDIT_PROBLEM_SOLUTION_SELF: 'I',
|
||||
PERM_DELETE_PROBLEM_SOLUTION: 'J',
|
||||
PERM_DELETE_PROBLEM_SOLUTION_SELF: 'K',
|
||||
PERM_REPLY_PROBLEM_SOLUTION: 'L',
|
||||
PERM_EDIT_PROBLEM_SOLUTION_REPLY: 'M',
|
||||
PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF: 'N',
|
||||
PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF_SOLUTION: 'x',
|
||||
PERM_DELETE_PROBLEM_SOLUTION_REPLY: 'O',
|
||||
PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF: 'P',
|
||||
PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF_SOLUTION: 'y',
|
||||
PERM_VIEW_PROBLEM_SOLUTION: 1n << 15n,
|
||||
PERM_CREATE_PROBLEM_SOLUTION: 1n << 16n,
|
||||
PERM_VOTE_PROBLEM_SOLUTION: 1n << 17n,
|
||||
PERM_EDIT_PROBLEM_SOLUTION: 1n << 18n,
|
||||
PERM_EDIT_PROBLEM_SOLUTION_SELF: 1n << 19n,
|
||||
PERM_DELETE_PROBLEM_SOLUTION: 1n << 20n,
|
||||
PERM_DELETE_PROBLEM_SOLUTION_SELF: 1n << 21n,
|
||||
PERM_REPLY_PROBLEM_SOLUTION: 1n << 22n,
|
||||
PERM_EDIT_PROBLEM_SOLUTION_REPLY: 1n << 23n,
|
||||
PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF: 1n << 24n,
|
||||
PERM_DELETE_PROBLEM_SOLUTION_REPLY: 1n << 25n,
|
||||
PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF: 1n << 26n,
|
||||
|
||||
// Discussion
|
||||
PERM_VIEW_DISCUSSION: 'Q',
|
||||
PERM_CREATE_DISCUSSION: 'R',
|
||||
PERM_HIGHLIGHT_DISCUSSION: 'S',
|
||||
PERM_EDIT_DISCUSSION: 'T',
|
||||
PERM_EDIT_DISCUSSION_SELF: 'U',
|
||||
PERM_DELETE_DISCUSSION: 'V',
|
||||
PERM_DELETE_DISCUSSION_SELF: 'W',
|
||||
PERM_REPLY_DISCUSSION: 'X',
|
||||
PERM_EDIT_DISCUSSION_REPLY: 'Y',
|
||||
PERM_EDIT_DISCUSSION_REPLY_SELF: 'Z',
|
||||
PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION: 'a',
|
||||
PERM_DELETE_DISCUSSION_REPLY: 'b',
|
||||
PERM_DELETE_DISCUSSION_REPLY_SELF: 'c',
|
||||
PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION: 'd',
|
||||
PERM_VIEW_DISCUSSION: 1n << 27n,
|
||||
PERM_CREATE_DISCUSSION: 1n << 28n,
|
||||
PERM_HIGHLIGHT_DISCUSSION: 1n << 29n,
|
||||
PERM_EDIT_DISCUSSION: 1n << 30n,
|
||||
PERM_EDIT_DISCUSSION_SELF: 1n << 31n,
|
||||
PERM_DELETE_DISCUSSION: 1n << 32n,
|
||||
PERM_DELETE_DISCUSSION_SELF: 1n << 33n,
|
||||
PERM_REPLY_DISCUSSION: 1n << 34n,
|
||||
PERM_EDIT_DISCUSSION_REPLY: 1n << 35n,
|
||||
PERM_EDIT_DISCUSSION_REPLY_SELF: 1n << 36n,
|
||||
PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 37n,
|
||||
PERM_DELETE_DISCUSSION_REPLY: 1n << 38n,
|
||||
PERM_DELETE_DISCUSSION_REPLY_SELF: 1n << 39n,
|
||||
PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 40n,
|
||||
|
||||
// Contest
|
||||
PERM_VIEW_CONTEST: 'e',
|
||||
PERM_VIEW_CONTEST_SCOREBOARD: 'f',
|
||||
PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD: 'g',
|
||||
PERM_CREATE_CONTEST: 'h',
|
||||
PERM_ATTEND_CONTEST: 'i',
|
||||
PERM_EDIT_CONTEST: 'j',
|
||||
PERM_EDIT_CONTEST_SELF: 'k',
|
||||
PERM_VIEW_CONTEST: 1n << 41n,
|
||||
PERM_VIEW_CONTEST_SCOREBOARD: 1n << 42n,
|
||||
PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD: 1n << 43n,
|
||||
PERM_CREATE_CONTEST: 1n << 44n,
|
||||
PERM_ATTEND_CONTEST: 1n << 45n,
|
||||
PERM_EDIT_CONTEST: 1n << 50n,
|
||||
PERM_EDIT_CONTEST_SELF: 1n << 51n,
|
||||
|
||||
// Homework
|
||||
PERM_VIEW_HOMEWORK: 'l',
|
||||
PERM_VIEW_HOMEWORK_SCOREBOARD: 'm',
|
||||
PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD: 'n',
|
||||
PERM_CREATE_HOMEWORK: 'o',
|
||||
PERM_ATTEND_HOMEWORK: 'p',
|
||||
PERM_EDIT_HOMEWORK: 'q',
|
||||
PERM_EDIT_HOMEWORK_SELF: 'r',
|
||||
PERM_VIEW_HOMEWORK: 1n << 52n,
|
||||
PERM_VIEW_HOMEWORK_SCOREBOARD: 1n << 53n,
|
||||
PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD: 1n << 54n,
|
||||
PERM_CREATE_HOMEWORK: 1n << 55n,
|
||||
PERM_ATTEND_HOMEWORK: 1n << 56n,
|
||||
PERM_EDIT_HOMEWORK: 1n << 57n,
|
||||
PERM_EDIT_HOMEWORK_SELF: 1n << 58n,
|
||||
|
||||
// Training
|
||||
PERM_VIEW_TRAINING: 's',
|
||||
PERM_CREATE_TRAINING: 't',
|
||||
PERM_EDIT_TRAINING: 'u',
|
||||
PERM_EDIT_TRAINING_SELF: 'v',
|
||||
PERM_VIEW_TRAINING: 1n << 46n,
|
||||
PERM_CREATE_TRAINING: 1n << 47n,
|
||||
PERM_EDIT_TRAINING: 1n << 48n,
|
||||
PERM_EDIT_TRAINING_SELF: 1n << 49n,
|
||||
|
||||
// Ranking
|
||||
PERM_VIEW_RANKING: 'w',
|
||||
PERM_VIEW_RANKING: 1n << 50n,
|
||||
|
||||
// Placeholder
|
||||
PERM_ALL: '',
|
||||
PERM_BASIC: '',
|
||||
PERM_DEFAULT: '',
|
||||
PERM_ADMIN: '',
|
||||
PERM_ALL: 0n,
|
||||
PERM_BASIC: 0n,
|
||||
PERM_DEFAULT: 0n,
|
||||
PERM_ADMIN: 0n,
|
||||
};
|
||||
|
||||
export const Permission = (family: string, key: string, desc: string) => ({ family, key, desc });
|
||||
export const Permission = (family: string, key: BigInt, desc: string) => ({ family, key, desc });
|
||||
|
||||
export const PERMS = [
|
||||
Permission('perm_general', PERM.PERM_VIEW, 'View this domain'),
|
||||
@@ -149,7 +153,7 @@ export const PERMS = [
|
||||
Permission('perm_ranking', PERM.PERM_VIEW_RANKING, 'View ranking'),
|
||||
];
|
||||
|
||||
let PERM_ALL = '';
|
||||
let PERM_ALL = 0n;
|
||||
for (const p in PERM) if (PERM[p]) PERM_ALL += PERM[p];
|
||||
PERM.PERM_ALL = PERM_ALL;
|
||||
|
||||
@@ -160,59 +164,59 @@ for (const p of PERMS) {
|
||||
}
|
||||
|
||||
PERM.PERM_BASIC = PERM.PERM_VIEW
|
||||
+ PERM.PERM_VIEW_PROBLEM
|
||||
+ PERM.PERM_VIEW_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_VIEW_DISCUSSION
|
||||
+ PERM.PERM_VIEW_CONTEST
|
||||
+ PERM.PERM_VIEW_CONTEST_SCOREBOARD
|
||||
+ PERM.PERM_VIEW_HOMEWORK
|
||||
+ PERM.PERM_VIEW_HOMEWORK_SCOREBOARD
|
||||
+ PERM.PERM_VIEW_TRAINING
|
||||
+ PERM.PERM_VIEW_RANKING;
|
||||
| PERM.PERM_VIEW_PROBLEM
|
||||
| PERM.PERM_VIEW_PROBLEM_SOLUTION
|
||||
| PERM.PERM_VIEW_DISCUSSION
|
||||
| PERM.PERM_VIEW_CONTEST
|
||||
| PERM.PERM_VIEW_CONTEST_SCOREBOARD
|
||||
| PERM.PERM_VIEW_HOMEWORK
|
||||
| PERM.PERM_VIEW_HOMEWORK_SCOREBOARD
|
||||
| PERM.PERM_VIEW_TRAINING
|
||||
| PERM.PERM_VIEW_RANKING;
|
||||
|
||||
PERM.PERM_DEFAULT = PERM.PERM_VIEW
|
||||
+ PERM.PERM_VIEW_PROBLEM
|
||||
+ PERM.PERM_EDIT_PROBLEM_SELF
|
||||
+ PERM.PERM_SUBMIT_PROBLEM
|
||||
+ PERM.PERM_READ_PROBLEM_DATA_SELF
|
||||
+ PERM.PERM_VIEW_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_CREATE_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_VOTE_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF
|
||||
+ PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF
|
||||
+ PERM.PERM_REPLY_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF
|
||||
+ PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF
|
||||
+ PERM.PERM_VIEW_DISCUSSION
|
||||
+ PERM.PERM_CREATE_DISCUSSION
|
||||
+ PERM.PERM_EDIT_DISCUSSION_SELF
|
||||
+ PERM.PERM_REPLY_DISCUSSION
|
||||
+ PERM.PERM_EDIT_DISCUSSION_REPLY_SELF
|
||||
| PERM.PERM_VIEW_PROBLEM
|
||||
| PERM.PERM_EDIT_PROBLEM_SELF
|
||||
| PERM.PERM_SUBMIT_PROBLEM
|
||||
| PERM.PERM_READ_PROBLEM_DATA_SELF
|
||||
| PERM.PERM_VIEW_PROBLEM_SOLUTION
|
||||
| PERM.PERM_CREATE_PROBLEM_SOLUTION
|
||||
| PERM.PERM_VOTE_PROBLEM_SOLUTION
|
||||
| PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF
|
||||
| PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF
|
||||
| PERM.PERM_REPLY_PROBLEM_SOLUTION
|
||||
| PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF
|
||||
| PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF
|
||||
| PERM.PERM_VIEW_DISCUSSION
|
||||
| PERM.PERM_CREATE_DISCUSSION
|
||||
| PERM.PERM_EDIT_DISCUSSION_SELF
|
||||
| PERM.PERM_REPLY_DISCUSSION
|
||||
| PERM.PERM_EDIT_DISCUSSION_REPLY_SELF
|
||||
// PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION
|
||||
+ PERM.PERM_DELETE_DISCUSSION_REPLY_SELF
|
||||
+ PERM.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION
|
||||
+ PERM.PERM_VIEW_CONTEST
|
||||
+ PERM.PERM_VIEW_CONTEST_SCOREBOARD
|
||||
+ PERM.PERM_ATTEND_CONTEST
|
||||
+ PERM.PERM_EDIT_CONTEST_SELF
|
||||
+ PERM.PERM_VIEW_HOMEWORK
|
||||
+ PERM.PERM_VIEW_HOMEWORK_SCOREBOARD
|
||||
+ PERM.PERM_ATTEND_HOMEWORK
|
||||
+ PERM.PERM_EDIT_HOMEWORK_SELF
|
||||
+ PERM.PERM_VIEW_TRAINING
|
||||
+ PERM.PERM_CREATE_TRAINING
|
||||
+ PERM.PERM_EDIT_TRAINING_SELF
|
||||
+ PERM.PERM_VIEW_RANKING
|
||||
+ PERM.PERM_SUBMIT_PROBLEM
|
||||
+ PERM.PERM_CREATE_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_VOTE_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_REPLY_PROBLEM_SOLUTION
|
||||
+ PERM.PERM_CREATE_DISCUSSION
|
||||
+ PERM.PERM_REPLY_DISCUSSION
|
||||
+ PERM.PERM_ATTEND_CONTEST
|
||||
+ PERM.PERM_CREATE_TRAINING
|
||||
+ PERM.PERM_ATTEND_HOMEWORK
|
||||
+ PERM.PERM_VIEW_RANKING;
|
||||
| PERM.PERM_DELETE_DISCUSSION_REPLY_SELF
|
||||
| PERM.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION
|
||||
| PERM.PERM_VIEW_CONTEST
|
||||
| PERM.PERM_VIEW_CONTEST_SCOREBOARD
|
||||
| PERM.PERM_ATTEND_CONTEST
|
||||
| PERM.PERM_EDIT_CONTEST_SELF
|
||||
| PERM.PERM_VIEW_HOMEWORK
|
||||
| PERM.PERM_VIEW_HOMEWORK_SCOREBOARD
|
||||
| PERM.PERM_ATTEND_HOMEWORK
|
||||
| PERM.PERM_EDIT_HOMEWORK_SELF
|
||||
| PERM.PERM_VIEW_TRAINING
|
||||
| PERM.PERM_CREATE_TRAINING
|
||||
| PERM.PERM_EDIT_TRAINING_SELF
|
||||
| PERM.PERM_VIEW_RANKING
|
||||
| PERM.PERM_SUBMIT_PROBLEM
|
||||
| PERM.PERM_CREATE_PROBLEM_SOLUTION
|
||||
| PERM.PERM_VOTE_PROBLEM_SOLUTION
|
||||
| PERM.PERM_REPLY_PROBLEM_SOLUTION
|
||||
| PERM.PERM_CREATE_DISCUSSION
|
||||
| PERM.PERM_REPLY_DISCUSSION
|
||||
| PERM.PERM_ATTEND_CONTEST
|
||||
| PERM.PERM_CREATE_TRAINING
|
||||
| PERM.PERM_ATTEND_HOMEWORK
|
||||
| PERM.PERM_VIEW_RANKING;
|
||||
|
||||
PERM.PERM_ADMIN = PERM.PERM_ALL;
|
||||
|
||||
|
||||
+14
-12
@@ -8,7 +8,7 @@ const collUser = db.collection('domain.user');
|
||||
|
||||
export async function add(domainId: string, owner: number, name: string, bulletin: string) {
|
||||
await coll.insertOne({
|
||||
_id: domainId, owner, name, bulletin, roles: BUILTIN_ROLES,
|
||||
_id: domainId, owner, name, bulletin, roles: {},
|
||||
});
|
||||
return domainId;
|
||||
}
|
||||
@@ -50,14 +50,14 @@ export function setUserRole(domainId: string, uid: number, role: string) {
|
||||
return collUser.updateOne({ uid, domainId }, { role }, { upsert: true });
|
||||
}
|
||||
|
||||
export async function getRoles(arg: string | any) {
|
||||
let ddoc;
|
||||
if (typeof arg === 'string') ddoc = get(arg);
|
||||
export async function getRoles(arg: string | DomainDoc) {
|
||||
let ddoc: DomainDoc;
|
||||
if (typeof arg === 'string') ddoc = await get(arg);
|
||||
else ddoc = arg;
|
||||
const roles = [];
|
||||
const r = [];
|
||||
for (const role in ddoc.roles) {
|
||||
roles.push({ _id: role, perm: ddoc.roles[role] });
|
||||
roles.push({ _id: role, perm: BigInt(ddoc.roles[role]) });
|
||||
r.push(role);
|
||||
}
|
||||
for (const role in BUILTIN_ROLES) {
|
||||
@@ -68,18 +68,18 @@ export async function getRoles(arg: string | any) {
|
||||
return roles;
|
||||
}
|
||||
|
||||
export async function setRoles(domainId: string, roles: any) {
|
||||
export async function setRoles(domainId: string, roles: Dictionary<bigint>) {
|
||||
const current = await get(domainId);
|
||||
for (const role in roles) {
|
||||
current.roles[role] = roles[role];
|
||||
current.roles[role] = roles[role].toString();
|
||||
}
|
||||
return await coll.updateOne({ _id: domainId }, { $set: { roles: current } });
|
||||
return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } });
|
||||
}
|
||||
|
||||
export async function addRole(domainId: string, name: string, permission: string) {
|
||||
export async function addRole(domainId: string, name: string, permission: bigint) {
|
||||
const current = await get(domainId);
|
||||
current.roles[name] = permission;
|
||||
return await coll.updateOne({ _id: domainId }, { $set: { roles: current } });
|
||||
current.roles[name] = permission.toString();
|
||||
return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } });
|
||||
}
|
||||
|
||||
export async function deleteRoles(domainId: string, roles: string[]) {
|
||||
@@ -98,7 +98,9 @@ export async function getDomainUser(domainId: string, udoc: Udoc) {
|
||||
if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'admin';
|
||||
dudoc.role = dudoc.role || 'default';
|
||||
const ddoc = await get(domainId);
|
||||
dudoc.perm = ddoc.roles[dudoc.role] || BUILTIN_ROLES[dudoc.role];
|
||||
dudoc.perm = ddoc.roles[dudoc.role]
|
||||
? BigInt(ddoc.roles[dudoc.role])
|
||||
: BUILTIN_ROLES[dudoc.role];
|
||||
return dudoc;
|
||||
}
|
||||
|
||||
|
||||
+15
-16
@@ -6,19 +6,21 @@ import { BUILTIN_USERS, PRIV } from './builtin';
|
||||
import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error';
|
||||
import pwhash from '../lib/hash.hydro';
|
||||
import * as db from '../service/db';
|
||||
import { Udoc, Udict } from '../interface';
|
||||
import { User as _User, Udoc, Udict } from '../interface';
|
||||
|
||||
const coll = db.collection('user');
|
||||
|
||||
export function setPassword(uid: number, password: string) {
|
||||
export async function setPassword(uid: number, password: string): Promise<Udoc> {
|
||||
const salt = String.random();
|
||||
return coll.findOneAndUpdate(
|
||||
const res = await coll.findOneAndUpdate(
|
||||
{ _id: uid },
|
||||
{ $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } },
|
||||
{ returnOriginal: false },
|
||||
);
|
||||
return res.value;
|
||||
}
|
||||
|
||||
export class User implements Udoc {
|
||||
export class User implements _User {
|
||||
udoc: () => any;
|
||||
|
||||
dudoc: () => any;
|
||||
@@ -41,7 +43,7 @@ export class User implements Udoc {
|
||||
|
||||
loginat: Date;
|
||||
|
||||
perm: string;
|
||||
perm: bigint;
|
||||
|
||||
role: string;
|
||||
|
||||
@@ -49,6 +51,8 @@ export class User implements Udoc {
|
||||
|
||||
loginip: () => string;
|
||||
|
||||
[key: string]: any;
|
||||
|
||||
constructor(udoc, dudoc) {
|
||||
this.udoc = () => udoc;
|
||||
this.dudoc = () => dudoc;
|
||||
@@ -81,8 +85,8 @@ export class User implements Udoc {
|
||||
}
|
||||
}
|
||||
|
||||
hasPerm(p: string) {
|
||||
return this.perm.includes(p);
|
||||
hasPerm(p: bigint) {
|
||||
return (this.perm & p) === p;
|
||||
}
|
||||
|
||||
hasPriv(p: number) {
|
||||
@@ -100,7 +104,7 @@ export class User implements Udoc {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getById(domainId: string, _id: number): Promise<Udoc | null> {
|
||||
export async function getById(domainId: string, _id: number): Promise<User | null> {
|
||||
const udoc = _id === 0 || _id === 1
|
||||
? BUILTIN_USERS[_id]
|
||||
: await coll.findOne({ _id });
|
||||
@@ -117,24 +121,19 @@ export async function getList(domainId: string, uids: number[]): Promise<Udict>
|
||||
return r;
|
||||
}
|
||||
|
||||
export async function getByUname(
|
||||
domainId: string, uname: string, ignoreMissing = false,
|
||||
): Promise<Udoc> {
|
||||
export async function getByUname(domainId: string, uname: string): Promise<User | null> {
|
||||
const unameLower = uname.trim().toLowerCase();
|
||||
const udoc = (unameLower === 'guest')
|
||||
? BUILTIN_USERS[0]
|
||||
: unameLower === 'hydro'
|
||||
? BUILTIN_USERS[1]
|
||||
: await coll.findOne({ unameLower });
|
||||
if (!udoc) {
|
||||
if (ignoreMissing) return null;
|
||||
throw new UserNotFoundError(uname);
|
||||
}
|
||||
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<Udoc | null> {
|
||||
export async function getByEmail(domainId: string, mail: string): Promise<User | null> {
|
||||
const mailLower = mail.trim().toLowerCase();
|
||||
const udoc = (mailLower === 'guest@hydro.local')
|
||||
? BUILTIN_USERS[0]
|
||||
|
||||
+35
-20
@@ -12,12 +12,14 @@ import Body from 'koa-body';
|
||||
import Router from 'koa-router';
|
||||
import cache from 'koa-static-cache';
|
||||
import sockjs from 'sockjs';
|
||||
import serialize from 'serialize-javascript';
|
||||
import { lrucache } from '../utils';
|
||||
import { Udoc, DomainDoc } from '../interface';
|
||||
import { User, DomainDoc } from '../interface';
|
||||
import {
|
||||
UserNotFoundError, BlacklistedError, PermissionError,
|
||||
UserFacingError, ValidationError, PrivilegeError,
|
||||
CsrfTokenError, InvalidOperationError, MethodNotAllowedError, NotFoundError,
|
||||
CsrfTokenError, InvalidOperationError, MethodNotAllowedError,
|
||||
NotFoundError,
|
||||
} from '../error';
|
||||
import { render } from '../lib/template';
|
||||
import * as misc from '../lib/misc';
|
||||
@@ -222,7 +224,7 @@ export class Handler {
|
||||
|
||||
csrfToken: string;
|
||||
|
||||
user: Udoc;
|
||||
user: User;
|
||||
|
||||
domain: DomainDoc;
|
||||
|
||||
@@ -266,7 +268,11 @@ export class Handler {
|
||||
|
||||
async renderHTML(name: string, context: any): Promise<string> {
|
||||
if (enableLog) console.time(name);
|
||||
const UserContext = { ...this.user, gravatar: misc.gravatar(this.user.gravatar || '', 128) };
|
||||
const UserContext = {
|
||||
...this.user,
|
||||
gravatar: misc.gravatar(this.user.gravatar || '', 128),
|
||||
perm: this.user.perm.toString(),
|
||||
};
|
||||
const res = await render(name, {
|
||||
handler: this,
|
||||
UserContext,
|
||||
@@ -291,7 +297,7 @@ export class Handler {
|
||||
return `${this.translate(str)} - Hydro`;
|
||||
}
|
||||
|
||||
checkPerm(...args) {
|
||||
checkPerm(...args: Array<bigint[] | bigint>) {
|
||||
for (const i in args) {
|
||||
if (args[i] instanceof Array) {
|
||||
let p = false;
|
||||
@@ -302,13 +308,14 @@ export class Handler {
|
||||
}
|
||||
}
|
||||
if (!p) throw new PermissionError([args[i]]);
|
||||
// @ts-ignore
|
||||
} else if (!this.user.hasPerm(args[i])) {
|
||||
throw new PermissionError([[args[i]]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkPriv(...args) {
|
||||
checkPriv(...args: Array<number[] | number>) {
|
||||
for (const i in args) {
|
||||
if (args[i] instanceof Array) {
|
||||
let p = false;
|
||||
@@ -319,6 +326,7 @@ export class Handler {
|
||||
}
|
||||
}
|
||||
if (!p) throw new PrivilegeError([args[i]]);
|
||||
// @ts-ignore
|
||||
} else if (!this.user.hasPriv(args[i])) {
|
||||
throw new PrivilegeError([[args[i]]]);
|
||||
}
|
||||
@@ -412,14 +420,16 @@ export class Handler {
|
||||
}
|
||||
|
||||
async renderBody() {
|
||||
if (!this.response.redirect && !this.request.json) {
|
||||
if (this.response.body || this.response.template) {
|
||||
if (this.request.query.noTemplate || this.request.json) return;
|
||||
const templateName = this.request.query.template || this.response.template;
|
||||
if (templateName) {
|
||||
this.response.body = this.response.body || {};
|
||||
await this.render(templateName, this.response.body);
|
||||
}
|
||||
if (this.response.type) return;
|
||||
if (this.request.json || this.response.redirect || !this.response.template) {
|
||||
this.response.body = serialize(this.response.body);
|
||||
this.response.type = 'application/json';
|
||||
} else if (this.response.body || this.response.template) {
|
||||
if (this.request.query.noTemplate || this.request.json) return;
|
||||
const templateName = this.request.query.template || this.response.template;
|
||||
if (templateName) {
|
||||
this.response.body = this.response.body || {};
|
||||
await this.render(templateName, this.response.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -535,7 +545,12 @@ async function handle(ctx, HandlerClass, checker) {
|
||||
if (h.cleanup) await h.cleanup(args);
|
||||
if (h.finish) await h.finish(args);
|
||||
} catch (e) {
|
||||
await h.onerror(e);
|
||||
try {
|
||||
await h.onerror(e);
|
||||
} catch (err) {
|
||||
h.response.code = 500;
|
||||
h.response.body = `${err.message}\n${err.stack}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,12 +564,12 @@ const Checker = (permPrivChecker) => {
|
||||
checker = item;
|
||||
} if (typeof item[0] === 'number') {
|
||||
priv = item;
|
||||
} else if (typeof item[0] === 'string') {
|
||||
} else if (typeof item[0] === 'bigint') {
|
||||
perm = item;
|
||||
}
|
||||
} else if (typeof item === 'number') {
|
||||
priv = item;
|
||||
} else if (typeof item === 'string') {
|
||||
} else if (typeof item === 'bigint') {
|
||||
perm = item;
|
||||
}
|
||||
}
|
||||
@@ -620,7 +635,7 @@ export class ConnectionHandler {
|
||||
return `${this.translate(str)} - Hydro`;
|
||||
}
|
||||
|
||||
checkPerm(...args) {
|
||||
checkPerm(...args: Array<bigint[] | bigint>) {
|
||||
for (const i in args) {
|
||||
if (args[i] instanceof Array) {
|
||||
let p = false;
|
||||
@@ -637,7 +652,7 @@ export class ConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
checkPriv(...args) {
|
||||
checkPriv(...args: Array<number[] | number>) {
|
||||
for (const i in args) {
|
||||
if (args[i] instanceof Array) {
|
||||
let p = false;
|
||||
@@ -703,7 +718,7 @@ export class ConnectionHandler {
|
||||
export function Connection(
|
||||
name: string, prefix: string,
|
||||
RouteConnHandler: any,
|
||||
...permPrivChecker: Array<number | string | Function>
|
||||
...permPrivChecker: Array<number | bigint | Function>
|
||||
) {
|
||||
const sock = sockjs.createServer({ prefix });
|
||||
const checker = Checker(permPrivChecker);
|
||||
|
||||
+2
-1
@@ -32,6 +32,7 @@
|
||||
"nunjucks": "^3.2.1",
|
||||
"prismjs": "^1.20.0",
|
||||
"saslprep": "^1.0.3",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"sockjs": "^0.3.20",
|
||||
"superagent": "^5.2.2",
|
||||
"superagent-proxy": "^2.0.0",
|
||||
@@ -88,4 +89,4 @@
|
||||
]
|
||||
},
|
||||
"snyk": true
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
{% for role in roles %}
|
||||
<td class="col--p">
|
||||
<label class="compact checkbox">
|
||||
<input name="{{ role._id }}" data-checkbox-group="{{ role._id }}" data-checkbox-range value="{{ perm.key }}" type="checkbox"{% if role.perm.includes(perm.key) %} checked{% endif %}{% if role._id == 'admin' %} disabled{% endif %}>
|
||||
<input name="{{ role._id }}" data-checkbox-group="{{ role._id }}" data-checkbox-range value="{{ log2(perm.key) }}" type="checkbox"{% if role.perm|bitand(perm.key) %} checked{% endif %}{% if role._id == 'admin' %} disabled{% endif %}>
|
||||
</label>
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/ {% if loop.index < path.length %}<a href="{{ url(c[1], c[2])|default('#') }}">{{ c[0] if c[3] else _(c[0]) }}</a>{% endif %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
<h1 class="location-current" data-emoji-enabled>{{ path[path.length - 1][0] if path[path.length - 1][2] else _(path[path.length - 1][0]) }}</h1>
|
||||
<h1 class="location-current" data-emoji-enabled>{{ path[path.length - 1][0] if path[path.length - 1][3] else _(path[path.length - 1][0]) }}</h1>
|
||||
</div>
|
||||
<div class="media__right">
|
||||
{% include "partials/hamburger.html" %}
|
||||
|
||||
@@ -5642,6 +5642,13 @@ serialize-javascript@^3.1.0:
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
||||
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
set-immediate-shim@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
|
||||
|
||||
Reference in new issue
Block a user