From bd13c57d826953d1667363a7765a870da2671e1a Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 24 Feb 2023 14:58:22 +0800 Subject: [PATCH] core: user: private and public projection (#507) --- packages/hydrooj/src/lib/serializer.ts | 16 ++++++++++++++++ packages/hydrooj/src/model/user.ts | 17 +++++++++++++++++ packages/hydrooj/src/service/layers/renderer.ts | 13 ++++--------- packages/hydrooj/src/service/layers/response.ts | 11 ++--------- packages/hydrooj/src/service/layers/user.ts | 2 +- packages/hydrooj/src/service/server.ts | 11 ++++------- 6 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 packages/hydrooj/src/lib/serializer.ts diff --git a/packages/hydrooj/src/lib/serializer.ts b/packages/hydrooj/src/lib/serializer.ts new file mode 100644 index 000000000..61f300478 --- /dev/null +++ b/packages/hydrooj/src/lib/serializer.ts @@ -0,0 +1,16 @@ +interface SerializeOptions { + showDisplayName: boolean; + currentSerializer?: (k: string, v: any) => any; +} + +export default function serializer(options: SerializeOptions, ignoreSerializeFunction = false) { + options.currentSerializer = (k: string, v: any) => { + if (typeof k.startsWith !== 'function') console.log(k, v); + if (k.startsWith('_') && k !== '_id') return undefined; + if (typeof v === 'bigint') return `BigInt::${v.toString()}`; + if (!ignoreSerializeFunction && v && typeof v === 'object' + && 'serialize' in v && typeof v.serialize === 'function') return v.serialize(options); + return v; + }; + return options.currentSerializer; +} diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 78b9fca71..d2f199eb4 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -7,6 +7,7 @@ import { ownerInfo, Udict, Udoc, VUdoc, } from '../interface'; import pwhash from '../lib/hash.hydro'; +import serializer from '../lib/serializer'; import * as bus from '../service/bus'; import db from '../service/db'; import { Value } from '../typeutils'; @@ -47,6 +48,7 @@ bus.on('user/delcache', (content) => deleteUserCache(typeof content === 'string' export class User { _id: number; + _isPrivate = false; _udoc: Udoc; _dudoc: any; @@ -149,6 +151,21 @@ export class User { UserModel.setPassword(this._id, password); } } + + async private() { + const user = await new User(this._udoc, this._dudoc, this.scope).init(); + user._isPrivate = true; + return user; + } + + serialize(options) { + if (!this._isPrivate) { + const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn']; + if (options.showDisplayName) fields.push('displayName'); + return pick(this, fields); + } + return JSON.stringify(this, serializer({ showDisplayName: options.showDisplayName }, true)); + } } function handleMailLower(mail: string) { diff --git a/packages/hydrooj/src/service/layers/renderer.ts b/packages/hydrooj/src/service/layers/renderer.ts index 658a5624f..307ae4214 100644 --- a/packages/hydrooj/src/service/layers/renderer.ts +++ b/packages/hydrooj/src/service/layers/renderer.ts @@ -1,17 +1,10 @@ import { ObjectID } from 'mongodb'; import avatar from '../../lib/avatar'; +import serializer from '../../lib/serializer'; import { PERM } from '../../model/builtin'; import * as system from '../../model/system'; -import { User } from '../../model/user'; import type { KoaContext } from '../server'; -const serializer = (showDisplayName = false) => (k: string, v: any) => { - if (k.startsWith('_') && k !== '_id') return undefined; - if (typeof v === 'bigint') return `BigInt::${v.toString()}`; - if (v instanceof User && !showDisplayName) delete v.displayName; - return v; -}; - export default (router, logger) => async (ctx: KoaContext, next) => { const { request, response } = ctx.HydroContext; ctx.renderHTML = (templateName, args) => { @@ -22,7 +15,9 @@ export default (router, logger) => async (ctx: KoaContext, next) => { viewLang: ctx.translate('__id'), }; const engine = global.Hydro.lib.template?.render - || (() => JSON.stringify(args, serializer(user?.hasPerm(PERM.PREM_VIEW_DISPLAYNAME)))); + || (() => JSON.stringify(args, serializer({ + showDisplayName: user?.hasPerm(PERM.PREM_VIEW_DISPLAYNAME), + }))); return engine(templateName, { handler: ctx.handler, UserContext, diff --git a/packages/hydrooj/src/service/layers/response.ts b/packages/hydrooj/src/service/layers/response.ts index d65ab0e18..53f27d3ef 100644 --- a/packages/hydrooj/src/service/layers/response.ts +++ b/packages/hydrooj/src/service/layers/response.ts @@ -1,16 +1,9 @@ import { errorMessage } from '@hydrooj/utils/lib/utils'; import { SystemError, UserFacingError } from '../../error'; +import serializer from '../../lib/serializer'; import { PERM } from '../../model/builtin'; -import { User } from '../../model/user'; import type { KoaContext } from '../server'; -const serializer = (showDisplayName = false) => (k: string, v: any) => { - if (k.startsWith('_') && k !== '_id') return undefined; - if (typeof v === 'bigint') return `BigInt::${v.toString()}`; - if (v instanceof User && !showDisplayName) delete v.displayName; - return v; -}; - export default (logger) => async (ctx: KoaContext, next) => { const { request, response } = ctx.HydroContext; try { @@ -34,7 +27,7 @@ export default (logger) => async (ctx: KoaContext, next) => { response.body.UiContext = UiContext; response.body.UserContext = user; } - response.body = JSON.stringify(response.body, serializer(user?.hasPerm(PERM.PREM_VIEW_DISPLAYNAME))); + response.body = JSON.stringify(response.body, serializer({ showDisplayName: user?.hasPerm(PERM.PREM_VIEW_DISPLAYNAME) })); } catch (e) { response.body = new SystemError('Serialize failure', e.message); } diff --git a/packages/hydrooj/src/service/layers/user.ts b/packages/hydrooj/src/service/layers/user.ts index b5ad57310..3559068b9 100644 --- a/packages/hydrooj/src/service/layers/user.ts +++ b/packages/hydrooj/src/service/layers/user.ts @@ -16,7 +16,7 @@ export default async (ctx: KoaContext, next) => { } if (user._id === 0) delete user.viewLang; user.avatarUrl = avatar(user.avatar, 128); - ctx.HydroContext.user = user; + ctx.HydroContext.user = await user.private(); if (!domain) { ctx.pendingError = new NotFoundError(args.domainId); args.domainId = 'system'; diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 85dd5e189..783e28a8f 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -17,6 +17,7 @@ import { PrivilegeError, UserFacingError, } from '../error'; import { DomainDoc } from '../interface'; +import serializer from '../lib/serializer'; import { Types } from '../lib/validator'; import { Logger } from '../logger'; import { PERM, PRIV } from '../model/builtin'; @@ -109,12 +110,6 @@ wsServer.on('error', (error) => { }); const ignoredLimit = `,${argv.options.ignoredLimit},`; -const serializer = (showDisplayName = false) => (k: string, v: any) => { - if (k.startsWith('_') && k !== '_id') return undefined; - if (typeof v === 'bigint') return `BigInt::${v.toString()}`; - if (v instanceof User && !showDisplayName) delete v.displayName; - return v; -}; export class HandlerCommon { render: (name: string, args?: any) => Promise; @@ -341,7 +336,9 @@ export class ConnectionHandler extends HandlerCommon { conn: WebSocket; send(data: any) { - this.conn.send(JSON.stringify(data, serializer(this.user?.hasPerm(PERM.PREM_VIEW_DISPLAYNAME)))); + this.conn.send(JSON.stringify(data, serializer({ + showDisplayName: this.user?.hasPerm(PERM.PREM_VIEW_DISPLAYNAME), + }))); } close(code: number, reason: string) {