使用 BigInt 进行权限管理

This commit is contained in:
undefined committed 2020-07-14 22:03:54 +08:00
1 parent eaed0e84f5
commit 8aef05a375
17 files changed
+252 -197

No files matched your search

+122 -118
View File
@@ -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
View File
@@ -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
View File
@@ -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]