From 60319183318ea9e4aa700826cc05e0820ae2954d Mon Sep 17 00:00:00 2001 From: maslow Date: Fri, 24 Dec 2021 18:22:59 +0800 Subject: [PATCH] feat(system): add system server app; impl init system server app; --- docker-compose.yml | 3 +- .../system-server/apps/system-server.lapp | Bin 0 -> 6895 bytes packages/system-server/init/README.md | 2 - packages/system-server/init/basic_app.json | 1 - packages/system-server/init/export.js | 59 ------- packages/system-server/init/func-loader.js | 77 --------- .../init/functions/admin-create/index.ts | 135 --------------- .../init/functions/admin-create/meta.json | 8 - .../init/functions/admin-edit/index.ts | 162 ------------------ .../init/functions/admin-edit/meta.json | 8 - .../init/functions/admin-getinfo/index.ts | 67 -------- .../init/functions/admin-getinfo/meta.json | 8 - .../init/functions/admin-login/index.ts | 57 ------ .../init/functions/admin-login/meta.json | 8 - .../functions/aliyun-sms-service/index.ts | 113 ------------ .../functions/aliyun-sms-service/meta.json | 9 - .../functions/email-sender-service/index.ts | 34 ---- .../functions/email-sender-service/meta.json | 8 - .../init/functions/exec_shell/index.ts | 15 -- .../init/functions/exec_shell/meta.json | 8 - .../init/functions/file-token/index.ts | 30 ---- .../init/functions/file-token/meta.json | 8 - .../init/functions/init-app-rbac/index.ts | 159 ----------------- .../init/functions/init-app-rbac/meta.json | 8 - .../init/functions/initializer/index.ts | 22 --- .../init/functions/initializer/meta.json | 18 -- .../init/functions/injector-admin/index.ts | 103 ----------- .../init/functions/injector-admin/meta.json | 8 - .../functions/send-login-smscode/index.ts | 41 ----- .../functions/send-login-smscode/meta.json | 9 - .../init/functions/test/index.ts | 7 - .../init/functions/test/meta.json | 8 - .../init/functions/user-passwd-login/index.ts | 64 ------- .../functions/user-passwd-login/meta.json | 9 - .../functions/user-password-register/index.ts | 75 -------- .../user-password-register/meta.json | 9 - .../init/functions/wx-mp-login/index.ts | 81 --------- .../init/functions/wx-mp-login/meta.json | 8 - .../init/policies/app-admin.json | 37 ---- .../system-server/init/policies/app-user.json | 19 -- packages/system-server/src/api/init.ts | 138 +++++++++++++-- packages/system-server/src/api/service.ts | 88 ++++++++++ packages/system-server/src/config.ts | 20 +++ packages/system-server/src/index.ts | 10 +- packages/system-server/src/init.ts | 51 ++++++ .../src/router/application/service.ts | 39 +---- packages/system-server/src/utils/lang.ts | 6 +- packages/system-server/start.sh | 9 + 48 files changed, 307 insertions(+), 1559 deletions(-) create mode 100644 packages/system-server/apps/system-server.lapp delete mode 100644 packages/system-server/init/README.md delete mode 100644 packages/system-server/init/basic_app.json delete mode 100644 packages/system-server/init/export.js delete mode 100644 packages/system-server/init/func-loader.js delete mode 100644 packages/system-server/init/functions/admin-create/index.ts delete mode 100644 packages/system-server/init/functions/admin-create/meta.json delete mode 100644 packages/system-server/init/functions/admin-edit/index.ts delete mode 100644 packages/system-server/init/functions/admin-edit/meta.json delete mode 100644 packages/system-server/init/functions/admin-getinfo/index.ts delete mode 100644 packages/system-server/init/functions/admin-getinfo/meta.json delete mode 100644 packages/system-server/init/functions/admin-login/index.ts delete mode 100644 packages/system-server/init/functions/admin-login/meta.json delete mode 100644 packages/system-server/init/functions/aliyun-sms-service/index.ts delete mode 100644 packages/system-server/init/functions/aliyun-sms-service/meta.json delete mode 100644 packages/system-server/init/functions/email-sender-service/index.ts delete mode 100644 packages/system-server/init/functions/email-sender-service/meta.json delete mode 100644 packages/system-server/init/functions/exec_shell/index.ts delete mode 100644 packages/system-server/init/functions/exec_shell/meta.json delete mode 100644 packages/system-server/init/functions/file-token/index.ts delete mode 100644 packages/system-server/init/functions/file-token/meta.json delete mode 100644 packages/system-server/init/functions/init-app-rbac/index.ts delete mode 100644 packages/system-server/init/functions/init-app-rbac/meta.json delete mode 100644 packages/system-server/init/functions/initializer/index.ts delete mode 100644 packages/system-server/init/functions/initializer/meta.json delete mode 100644 packages/system-server/init/functions/injector-admin/index.ts delete mode 100644 packages/system-server/init/functions/injector-admin/meta.json delete mode 100644 packages/system-server/init/functions/send-login-smscode/index.ts delete mode 100644 packages/system-server/init/functions/send-login-smscode/meta.json delete mode 100644 packages/system-server/init/functions/test/index.ts delete mode 100644 packages/system-server/init/functions/test/meta.json delete mode 100644 packages/system-server/init/functions/user-passwd-login/index.ts delete mode 100644 packages/system-server/init/functions/user-passwd-login/meta.json delete mode 100644 packages/system-server/init/functions/user-password-register/index.ts delete mode 100644 packages/system-server/init/functions/user-password-register/meta.json delete mode 100644 packages/system-server/init/functions/wx-mp-login/index.ts delete mode 100644 packages/system-server/init/functions/wx-mp-login/meta.json delete mode 100644 packages/system-server/init/policies/app-admin.json delete mode 100644 packages/system-server/init/policies/app-user.json create mode 100644 packages/system-server/src/api/service.ts create mode 100644 packages/system-server/src/init.ts create mode 100644 packages/system-server/start.sh diff --git a/docker-compose.yml b/docker-compose.yml index 2c6c4f2244..34e393aa41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,7 +38,8 @@ services: STORAGE_SERVICE_API_ENTRYPOINT: http://storage_service:9010 STORAGE_SERVICE_SECRET: Rewrite_Your_Own_Secret_Salt_abcdefg1234567 DEBUG_BIND_HOST_APP_PATH: '${PWD}/packages/app-service' - command: npx nodemon + INIT_ROOT_ACCOUNT_PASSWORD: abc123 + command: sh /app/start.sh volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./packages/system-server:/app diff --git a/packages/system-server/apps/system-server.lapp b/packages/system-server/apps/system-server.lapp new file mode 100644 index 0000000000000000000000000000000000000000..72615cfd5d406624f4b9884a5cee2817d94cbaf7 GIT binary patch literal 6895 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 @Deprecated 此目录中的脚本原为初始化应用,现在多租户版中已废弃,待清理。 \ No newline at end of file diff --git a/packages/system-server/init/basic_app.json b/packages/system-server/init/basic_app.json deleted file mode 100644 index a367eb4902..0000000000 --- a/packages/system-server/init/basic_app.json +++ /dev/null @@ -1 +0,0 @@ -{"meta":{"name":"basic_template"},"functions":[{"label":"后台管理-创建管理员","name":"admin-create","description":"","enableHTTP":true,"status":1,"tags":["后台管理","预置"],"code":"import cloud from '@/cloud-sdk'\nimport * as crypto from 'crypto'\nconst db = cloud.database()\n\nexports.main = async function (ctx) {\n const uid = ctx.auth?.uid\n if (!uid) {\n return 'Unauthorized'\n }\n\n // 权限验证\n const code = await checkPermission(uid, 'admin.create')\n if (code) {\n return 'Permission denied'\n }\n\n const { username, password, avatar, name, roles } = ctx.body\n if (!username || !password) {\n return 'username or password cannot be empty'\n }\n\n // 验证用户是否已存在\n const { total } = await db.collection('admins').where({ username }).count()\n if (total > 0) {\n return 'username already exists'\n }\n\n // 验证 roles 是否合法\n const { total: valid_count } = await db.collection('roles')\n .where({\n name: db.command.in(roles)\n }).count()\n\n if (valid_count !== roles.length) {\n return 'invalid roles'\n }\n\n // add admin\n const r = await db.collection('admins')\n .add({\n username,\n name: name ?? null,\n avatar: avatar ?? null,\n roles: roles ?? [],\n created_at: Date.now(),\n updated_at: Date.now()\n })\n\n // add admin password\n await db.collection('password')\n .add({\n uid: r.id,\n password: hashPassword(password),\n type: 'login',\n created_at: Date.now(),\n updated_at: Date.now()\n })\n\n return {\n ...r,\n uid: r.id\n }\n}\n\n\n/**\n * 通过 user id 获取权限列表\n * @param role_ids \n * @returns \n */\nasync function getPermissions(uid: string) {\n const db = cloud.database()\n // 查用户\n const { data: admin } = await db.collection('admins')\n .where({ _id: uid })\n .getOne()\n\n\n // 查角色\n const { data: roles } = await db.collection('roles')\n .where({\n name: {\n $in: admin.roles ?? []\n }\n })\n .get()\n\n if (!roles) {\n return { permissions: [], roles: [], user: admin }\n }\n\n const permissions = []\n for (const role of roles) {\n const perms = role.permissions ?? []\n permissions.push(...perms)\n }\n\n return {\n permissions,\n roles: roles.map(role => role.name),\n user: admin\n }\n}\n\n\n\n/**\n * 判断用户是否有权限\n * @param uid 用户ID\n * @param permission 权限名\n * @returns 0 表示用户有权限, 401 表示用户未登录, 403 表示用户未授权\n */\nasync function checkPermission(uid: string, permission: string): Promise {\n if (!uid) {\n return 401\n }\n const { permissions } = await getPermissions(uid)\n\n if (!permissions.includes(permission)) {\n return 403\n }\n return 0\n}\n\n/**\n * @param {string} content\n * @return {string}\n */\nfunction hashPassword(content: string): string {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n}\n\n"},{"label":"后台管理-编辑管理员","name":"admin-edit","description":"","enableHTTP":true,"status":1,"tags":["后台管理","预置"],"code":"import cloud from '@/cloud-sdk'\nimport * as crypto from 'crypto'\n\nconst db = cloud.database()\n\nexports.main = async function (ctx) {\n console.log(ctx)\n\n const uid = ctx.auth?.uid\n if (!uid) {\n return 'Unauthorized'\n }\n\n // 权限验证\n const code = await checkPermission(uid, 'admin.edit')\n if (code) {\n return 'Permission denied'\n }\n\n // 参数验证\n const { _id, username, password, avatar, name, roles } = ctx.body\n if (!_id) {\n return 'admin id cannot be empty'\n }\n\n // 验证 user _id 是否合法\n const { data: admins } = await db.collection('admins').where({ _id: _id }).get()\n if (!admins || !admins.length) {\n return 'user not exists'\n }\n\n // 验证 roles 是否合法\n const { total: valid_count } = await db.collection('roles')\n .where({\n name: db.command.in(roles)\n }).count()\n\n if (valid_count !== roles.length) {\n return 'invalid roles'\n }\n\n // update password\n if (password) {\n await db.collection('password')\n .where({ uid: _id })\n .update({\n password: hashPassword(password),\n updated_at: Date.now()\n })\n }\n\n const old = admins[0]\n\n // update admim\n const data = {\n updated_at: Date.now()\n }\n\n // username\n if (username && username != old.username) {\n const { total } = await db.collection('admins').where({ username }).count()\n if (total) {\n return 'username already exists'\n }\n data['username'] = username\n }\n\n // avatar\n if (avatar && avatar != old.avatar) {\n data['avatar'] = avatar\n }\n\n // name\n if (name && name != old.name) {\n data['name'] = name\n }\n\n // roles\n if (roles) {\n data['roles'] = roles\n }\n\n console.log(_id, data)\n const r = await db.collection('admins')\n .where({ _id: _id })\n .update(data)\n\n return {\n ...r,\n _id\n }\n}\n\n\n/**\n * 通过 user id 获取权限列表\n * @param role_ids \n * @returns \n */\nasync function getPermissions(uid: string) {\n const db = cloud.database()\n // 查用户\n const { data: admin } = await db.collection('admins')\n .where({ _id: uid })\n .getOne()\n\n\n // 查角色\n const { data: roles } = await db.collection('roles')\n .where({\n name: {\n $in: admin.roles ?? []\n }\n })\n .get()\n\n if (!roles) {\n return { permissions: [], roles: [], user: admin }\n }\n\n const permissions = []\n for (const role of roles) {\n const perms = role.permissions ?? []\n permissions.push(...perms)\n }\n\n return {\n permissions,\n roles: roles.map(role => role.name),\n user: admin\n }\n}\n\n/**\n * 判断用户是否有权限\n * @param uid 用户ID\n * @param permission 权限名\n * @returns 0 表示用户有权限, 401 表示用户未登录, 403 表示用户未授权\n */\nasync function checkPermission(uid: string, permission: string): Promise {\n if (!uid) {\n return 401\n }\n const { permissions } = await getPermissions(uid)\n\n if (!permissions.includes(permission)) {\n return 403\n }\n return 0\n}\n\n/**\n * @param {string} content\n * @return {string}\n */\nfunction hashPassword(content: string): string {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n}\n\n"},{"label":"后台管理-获取管理员信息","name":"admin-getinfo","description":"","enableHTTP":true,"status":1,"tags":["后台管理","预置"],"code":"import cloud from '@/cloud-sdk'\n\nexports.main = async function (ctx) {\n const db = cloud.database()\n const uid = ctx.auth?.uid\n if (!uid) {\n return 'Unauthorized'\n }\n\n //\n const ret = await db.collection('admins')\n .where({ _id: uid })\n .get()\n\n if (!ret.ok || !ret.data.length) {\n return 'query admin error'\n }\n\n const admin = ret.data[0]\n\n const { permissions } = await getPermissions(admin._id)\n\n return {\n ...admin,\n permissions\n }\n}\n\n\n/**\n * 通过 user id 获取权限列表\n * @param role_ids \n * @returns \n */\nasync function getPermissions(uid: string) {\n const db = cloud.database()\n // 查用户\n const { data: admin } = await db.collection('admins')\n .where({ _id: uid })\n .getOne()\n\n\n // 查角色\n const { data: roles } = await db.collection('roles')\n .where({\n name: {\n $in: admin.roles ?? []\n }\n })\n .get()\n\n if (!roles) {\n return { permissions: [], roles: [], user: admin }\n }\n\n const permissions = []\n for (const role of roles) {\n const perms = role.permissions ?? []\n permissions.push(...perms)\n }\n\n return {\n permissions,\n roles: roles.map(role => role.name),\n user: admin\n }\n}"},{"label":"后台管理-管理员登陆","name":"admin-login","description":"","enableHTTP":true,"status":1,"tags":["后台管理","预置","登陆"],"code":"import cloud from '@/cloud-sdk'\nimport * as crypto from 'crypto'\n\nexports.main = async function (ctx) {\n const db = cloud.database()\n const { username, password } = ctx.body\n\n if (!username || !password) {\n return '用户密码不正确'\n }\n\n const ret = await db.collection('admins')\n .withOne({\n query: db\n .collection('password')\n .where({ password: hashPassword(password), type: 'login' }),\n localField: '_id',\n foreignField: 'uid'\n })\n .where({ username })\n .merge({ intersection: true })\n\n if (ret.ok && ret.data.length) {\n const admin = ret.data[0]\n\n // 默认 token 有效期为 7 天\n const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7\n const payload = {\n uid: admin._id,\n type: 'admin',\n exp: expire\n }\n const access_token = cloud.getToken(payload)\n\n return {\n access_token,\n username,\n uid: admin._id,\n expire\n }\n }\n\n return 'invalid username or password'\n}\n\n\n/**\n * @param {string} content\n * @return {string}\n */\nfunction hashPassword(content: string): string {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n}\n\n"},{"label":"aliyun-sms-service","name":"aliyun-sms-service","description":"阿里云通信-发短信内部调用服务,不开启外网访问","enableHTTP":false,"status":0,"debugParams":"{\n \"phone\": \"13184211245\",\n \"code\": \"1234\"\n}","tags":["预置","阿里云"],"code":"\nimport cloud from '@/cloud-sdk'\nimport { v4 as uuidv4 } from 'uuid'\nimport * as crypto from 'crypto'\nimport * as querystring from 'querystring'\n\n\n// 你可以让一个云函数监听 `app.ready` 触发器事件,初始化必要的配置信息(cloud.shared)\nconst config = cloud.shared.get('aliyun.sms.config')\n\nconst accessKeyId = config.accessKeyId // 阿里云访问 Key ID\nconst accessKeySecret = config.accessKeySecret // 阿里云访问 Key Secret\nconst api_entrypoint = config.api_entrypoint ?? 'https://dysmsapi.aliyuncs.com'\nconst signName = config.signName // 短信签名,修改为你的签名,如: \"灼灼信息\"\nconst templateCode = config.templateCode // 短信模板ID,修改为你的模板ID,如 'SMS_217850726'\n\n/**\n * @body phone string 手机号\n * @body code string | number 验证码\n */\n exports.main = async function (ctx) {\n const phone = ctx.body?.phone\n if (!phone) {\n return 'error: invalid phone'\n }\n const code = ctx.body?.code\n if (!code) {\n return 'error: invalid code'\n }\n\n const params = sortObjectKeys({\n AccessKeyId: accessKeyId,\n Action: 'SendSms',\n Format: 'json',\n SignatureMethod: 'HMAC-SHA1',\n SignatureNonce: uuidv4(),\n SignatureVersion: '1.0',\n Version: '2017-05-25',\n Timestamp: (new Date()).toISOString(),\n PhoneNumbers: phone,\n SignName: signName,\n TemplateCode: templateCode,\n TemplateParam: `{\"code\": ${code}}`\n })\n\n params['Signature'] = specialEncode(sign(params))\n\n const query = querystring.stringify(params)\n const url = `${api_entrypoint}?${query}`\n\n try {\n const r = await cloud.fetch(url)\n console.log(r.data)\n if(r.data?.Code === 'OK')\n return 'ok'\n else\n return r.data\n\n } catch (err) {\n console.log(err)\n return 'error: ' + err\n }\n}\n// 签名\nfunction sign(raw_params) {\n const params = encode(raw_params)\n\n //拼接strToSign\n let strToSign = '';\n for (let i in params) {\n strToSign += i + '=' + params[i] + '&';\n }\n strToSign = strToSign.substr(0, strToSign.length - 1);\n strToSign = \"GET&\" + encodeURIComponent('/') + '&' + encodeURIComponent(strToSign);\n\n // 阿里云签名是要求 基于 hash 的原始二进制值 进行 base64编码\n const ret = crypto.createHmac('sha1', accessKeySecret + '&')\n .update(strToSign)\n .digest('base64')\n\n return ret\n}\n\n//对各个参数进行字典序升序排序\nfunction sortObjectKeys(obj) {\n const tmp = {};\n Object.keys(obj).sort().forEach(k => tmp[k] = obj[k])\n return tmp;\n}\n\n\n//对排序之后的参数进行 uriencode + POP 编码\nfunction encode(params) {\n const obj = {}\n //对urlencode之后的特殊字符进行替换\n for (let i in params) {\n const str = encodeURIComponent(params[i])\n obj[i] = specialEncode(str)\n }\n return obj\n}\n\n// 阿里云的特殊编码(POP编码)\nfunction specialEncode(encoded) {\n if (encoded.indexOf('+')) {\n encoded.replace(\"+\", \"%20\");\n } else if (encoded.indexOf('*')) {\n encoded.replace(\"*\", \"%2A\");\n } else if (encoded.indexOf('%7E')) {\n encoded.replace(\"%7E\", \"~\");\n }\n return encoded\n}\n"},{"label":"email-sender-service","name":"email-sender-service","description":"","enableHTTP":false,"status":0,"tags":["预置"],"code":"\nimport cloud from '@/cloud-sdk'\nimport * as nodemailer from 'nodemailer'\nimport * as assert from 'assert'\n\nexports.main = async function (ctx) {\n const body = ctx.body\n const db = cloud.database()\n const CONFIG = cloud.shared.get('sys.config.mail')\n assert.ok(CONFIG, 'no mail config found')\n\n const transporter = nodemailer.createTransport({\n host: CONFIG.host,\n port: CONFIG.port,\n secure: true, // true for 465, false for other ports\n auth: {\n user: CONFIG.user,\n pass: CONFIG.pass,\n },\n })\n\n // send mail with defined transport object\n const info = await transporter.sendMail({\n from: CONFIG.sender, // sender address\n to: body.email, // list of receivers\n subject: body.subject, // Subject line\n text: body.text, // plain text body\n // html: \"Hello world?\", // html body\n })\n\n console.log(info)\n\n return info\n}\n"},{"label":"在服务端执行 shell 脚本","name":"exec_shell","description":"","enableHTTP":false,"status":0,"tags":["预置","scripts"],"code":"import * as child_process from 'child_process'\n\n/**\n * 可修改本函数,安装指定的依赖包。\n * 注意:\n * 0. 因客户端请求超时时间限制,函数可能在返回前请求超时,但该命令会继续运行,请知晓\n * 1. 不要开启本函数的 HTTP 访问(安全风险)\n * 2. 通常请保持本函数是停用状态(安全风险)\n */\n\nexports.main = async function (ctx) {\n const r = child_process.execSync(\"npm i @types/nodemailer nodemailer\")\n console.log(r.toString())\n return 'ok'\n}\n"},{"label":"文件访问令牌","name":"file-token","description":"文件访问令牌","enableHTTP":true,"status":0,"tags":["预置"],"code":"import cloud from '@/cloud-sdk'\n\n/**\n * 本函数可发放文件访问令牌,用于下载或上传文件\n * @TODO 你可以修改本函数,以实现符合业务的文件访问授权逻辑\n * \n * @body {string} bucket 文件存储的名字空间,bucket = '*' 时代表可访问所有 bucket\n * @body {string} filename 文件名,可选的,如不提供则代表可访问所有文件\n * @body {string[]} ops 授权的操作权限,取值为之一或多个: \"read\" | \"create\" | \"delete\" | \"list\"\n * @body {number} expire 令牌有效期,单位为秒,默认为一小时,即 3600\n */\n\nexports.main = async function (ctx) {\n const uid = ctx.auth?.uid\n if (!uid) return 'error: unauthorized'\n\n const bucket = ctx.body?.bucket\n const filename = ctx.body?.filename ?? undefined\n const ops = ctx.body?.ops ?? ['read']\n const expire = ctx.body?.expire ?? 3600\n\n if (!bucket) {\n return 'error: invalid bucket'\n }\n\n const exp = Math.floor(Date.now() / 1000) + expire\n const payload = { bucket, filename, exp, ops }\n\n return cloud.getToken(payload)\n}\n"},{"label":"初始化应用 RBAC 数据","name":"init-app-rbac","description":"初始化一个经典的 RBAC,包括基础的权限、角色、初始管理员","enableHTTP":false,"status":1,"tags":["初始化","预置"],"code":"/**\n * 本函数可用于初始化一套 RBAC 必要的数据,通常不需要删除此云函数,也不要开启 HTTP 调用。\n */\n\n import cloud from '@/cloud-sdk'\n import * as assert from 'assert'\n import * as crypto from 'crypto'\n const db = cloud.database()\n \n \n exports.main = async function (ctx) {\n \n // 创建 RBAC 初始权限\n await createInitialPermissions()\n \n // 创建 RBAC 初始角色\n await createFirstRole()\n \n // 创建初始管理员\n await createFirstAdmin(\"admin\", \"123456\")\n \n return 'ok'\n }\n \n \n \n \n /**\n * 预置 RBAC 权限\n */\n const permissions = [\n { name: 'role.create', label: '创建角色' },\n { name: 'role.read', label: '读取角色' },\n { name: 'role.edit', label: '编辑角色' },\n { name: 'role.delete', label: '删除角色' },\n \n { name: 'permission.create', label: '创建权限' },\n { name: 'permission.read', label: '读取权限' },\n { name: 'permission.edit', label: '编辑权限' },\n { name: 'permission.delete', label: '删除权限' },\n \n { name: 'admin.create', label: '创建管理员' },\n { name: 'admin.read', label: '获取管理员' },\n { name: 'admin.edit', label: '编辑管理员' },\n { name: 'admin.delete', label: '删除管理员' }\n ]\n \n \n \n // 创建初始管理员\n async function createFirstAdmin(username: string, password: string) {\n try {\n \n const { total } = await db.collection('admins').count()\n if (total > 0) {\n console.log('admin already exists')\n return\n }\n \n await cloud.mongo.db.collection('admins').createIndex('username', { unique: true })\n \n const { data } = await db.collection('roles').get()\n const roles = data.map(it => it.name)\n \n const r_add = await db.collection('admins').add({\n username,\n avatar: \"https://static.dingtalk.com/media/lALPDe7szaMXyv3NAr3NApw_668_701.png\",\n name: 'Admin',\n roles,\n created_at: Date.now(),\n updated_at: Date.now()\n })\n assert.ok(r_add.id, 'add admin occurs error')\n \n await db.collection('password').add({\n uid: r_add.id,\n password: hashPassword(password),\n type: 'login',\n created_at: Date.now(),\n updated_at: Date.now()\n })\n \n return r_add.id\n } catch (error) {\n console.error(error.message)\n }\n }\n \n // 创建初始角色\n async function createFirstRole() {\n try {\n \n await cloud.mongo.db.collection('roles').createIndex('name', { unique: true })\n \n const r_perm = await db.collection('permissions').get()\n assert(r_perm.ok, 'get permissions failed')\n \n const permissions = r_perm.data.map(it => it.name)\n \n const r_add = await db.collection('roles').add({\n name: 'superadmin',\n label: '超级管理员',\n description: '系统初始化的超级管理员',\n permissions,\n created_at: Date.now(),\n updated_at: Date.now()\n })\n \n assert.ok(r_add.id, 'add role occurs error')\n \n return r_add.id\n } catch (error) {\n if (error.code == 11000) {\n return console.log('permissions already exists')\n }\n \n console.error(error.message)\n }\n }\n \n // 创建初始权限\n async function createInitialPermissions() {\n \n // 创建唯一索引\n await cloud.mongo.db.collection('permissions').createIndex('name', { unique: true })\n \n for (const perm of permissions) {\n try {\n const data = {\n ...perm,\n created_at: Date.now(),\n updated_at: Date.now()\n }\n await db.collection('permissions').add(data)\n } catch (error) {\n if (error.code == 11000) {\n console.log('permissions already exists')\n continue\n }\n console.error(error.message)\n }\n }\n \n return true\n }\n \n \n /**\n * @param {string} content\n * @return {string}\n */\n function hashPassword(content: string): string {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n }\n \n "},{"label":"应用初始化器","name":"initializer","description":"","enableHTTP":false,"reserved":true,"status":1,"tags":["初始化","预置"],"triggers":[{"name":"监听应用启动就绪事件","type":"event","event":"App:ready","desc":"","status":1,"_id":"613a347c1ce6a2f7b5d49c16"}],"code":"/**\n * 本函数会默认创建 'App:ready' 事件触发器,应用启动并初始化完成后被自动调用。\n * \n * 本函数可用于初始化应用必要的一些配置、数据,通常不需要删除此云函数,也不要开启 HTTP 调用。\n */\n\nimport cloud from '@/cloud-sdk'\nconst db = cloud.database()\n\n\nexports.main = async function (ctx) {\n\n const r = await cloud.invoke('init-app-rbac', {})\n console.log(r.logs)\n if (r.data === 'ok') {\n console.log('init rbac: ok')\n }\n\n return 'ok'\n}\n\n\n"},{"label":"injector-admin","name":"injector-admin","description":"","enableHTTP":false,"status":1,"tags":["预置","injector"],"code":"import cloud from '@/cloud-sdk'\nimport * as crypto from 'crypto'\n\n/**\n * 本函数为 policy injector, 当用户请求 proxy/:policy 时,会调用本函数返回的函数获取该策略的 injections。\n * 返回的 injections 会注入到该策略规则中执行。\n * 例如,本例中返回了 $has 和 $is 函数,则在规则中可以这样使用:\n * ```json\n * {\n * \"add\": \"$has('article.create')\",\n * \"remove\": \"$is('admin')\"\n * }\n * ```\n */\n\nexports.main = async function (ctx) {\n\n return async function (payload: any, params: any) {\n const auth = payload || {}\n const { permissions, roles } = await getPermissions(auth.uid)\n return {\n ...auth,\n $has: (permissionName) => {\n return permissions.includes(permissionName)\n },\n $is: (roleName) => {\n return roles.includes(roleName)\n }\n }\n }\n}\n\n\n/**\n * 通过 user id 获取权限列表\n * @param role_ids \n * @returns \n */\nasync function getPermissions(uid: string) {\n const db = cloud.database()\n // 查用户\n const { data: admin } = await db.collection('admins')\n .where({ _id: uid })\n .getOne()\n\n\n // 查角色\n const { data: roles } = await db.collection('roles')\n .where({\n name: {\n $in: admin.roles ?? []\n }\n })\n .get()\n\n if (!roles) {\n return { permissions: [], roles: [], user: admin }\n }\n\n const permissions = []\n for (const role of roles) {\n const perms = role.permissions ?? []\n permissions.push(...perms)\n }\n\n return {\n permissions,\n roles: roles.map(role => role.name),\n user: admin\n }\n}\n\n\n\n/**\n * 判断用户是否有权限\n * @param uid 用户ID\n * @param permission 权限名\n * @returns 0 表示用户有权限, 401 表示用户未登录, 403 表示用户未授权\n */\nasync function checkPermission(uid: string, permission: string): Promise {\n if (!uid) {\n return 401\n }\n const { permissions } = await getPermissions(uid)\n\n if (!permissions.includes(permission)) {\n return 403\n }\n return 0\n}\n\n/**\n * @param {string} content\n * @return {string}\n */\nfunction hashPassword(content: string): string {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n}\n\n"},{"label":"发送登陆验证码","name":"send-login-smscode","description":"发送登陆短信验证码","enableHTTP":true,"status":0,"debugParams":"{\n \"phone\": \"13184211245\"\n}","tags":["预置"],"code":"import cloud from '@/cloud-sdk'\n\n/**\n * @api\n * @body phone string 手机号\n */\n\n// main function\n exports.main = async function (ctx) {\n const db = cloud.database()\n const phone = ctx.body?.phone\n if (!phone) {\n return 'Error: invalid phone'\n }\n\n const code = Math.min(Math.floor(1000 + Math.random() * 9000), 9999)\n\n const r = await sendSMSCode(phone, code)\n if (r.data === 'ok') {\n await db.collection('verify_code').add({\n type: 'sms',\n phone,\n code,\n event: 'login',\n created_at: Date.now()\n })\n }\n return r\n}\n\n/**\n * 发送验证码\n * @return {Promise}\n * @see cloud function: aliyun-sms-service\n */\nasync function sendSMSCode(phone, code) {\n const body = { phone, code }\n const r = await cloud.invoke('aliyun-sms-service', { body })\n return r\n}\n\n"},{"label":"测试","name":"test","description":"","enableHTTP":true,"status":1,"tags":["预置"],"code":"import cloud from '@/cloud-sdk'\n\nexports.main = async function (ctx) {\n console.log(ctx)\n \n return 'ok'\n}\n"},{"label":"用户密码登陆","name":"user-passwd-login","description":"用户密码登陆","enableHTTP":true,"status":1,"debugParams":"{\n \"username\": \"less\",\n \"password\": \"less123\"\n}","tags":["预置"],"code":"import * as crypto from 'crypto'\nimport cloud from '@/cloud-sdk'\n/**\n * @body username string 用户名\n * @body password string 密码\n */\n exports.main = async function (ctx) {\n const db = cloud.database()\n\n // 参数验证\n const { username, password } = ctx.body\n if (!username || !password) {\n return { code: 1, error: 'invalid phone or password' }\n }\n\n // 验证用户名是否存在\n const { data: user } = await db.collection('users')\n .where({ username })\n .getOne()\n\n if (!user) {\n return {code: 1, error: 'invalid username or password'}\n }\n\n // 验证密码是否正确\n const ret = await db.collection('password')\n .where({ uid: user._id, password: hashPassword(password), type: 'login' })\n .count()\n\n if (ret.total > 0) {\n\n // 默认 token 有效期为 7 天\n const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7\n const payload = {\n uid: user._id,\n type: 'user',\n exp: expire\n }\n const access_token = cloud.getToken(payload)\n return {\n code: 0,\n data: {\n access_token,\n user,\n uid: user._id,\n expire\n }\n }\n }\n\n return { code: 1, error: 'invalid username or password' }\n}\n\n/**\n * @param {string} content\n * @return {string}\n */\nfunction hashPassword(content) {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n}\n\n"},{"label":"用户密码注册","name":"user-password-register","description":"用户密码注册","enableHTTP":true,"status":1,"debugParams":"{\"username\":\"less\",\"password\":\"less123\"}","tags":["预置"],"code":"import * as crypto from 'crypto'\nimport cloud from '@/cloud-sdk'\n/**\n * @body username string 用户名\n * @body password string 密码\n */\n exports.main = async function (ctx) {\n const db = cloud.database()\n\n // 参数验证\n const { username, password } = ctx.body\n if (!username|| !password) {\n return { code: 1, error: 'invalid username or password' }\n }\n\n // 检查用户名是否已存在\n const r_count = await db.collection('users')\n .where({ username })\n .count()\n\n if (r_count.total > 0) {\n return { code: 1, error: `${username} already exists` }\n }\n\n // 创建 user\n const r = await db.collection('users').add({\n username,\n created_at: Date.now(),\n updated_at: Date.now()\n })\n\n // 创建 user password\n await db.collection('password').add({\n uid: r.id,\n password: hashPassword(password),\n type: \"login\",\n created_at: Date.now(),\n updated_at: Date.now()\n })\n\n\n // 注册完成后自动登录,生成 token: 默认 token 有效期为 7 天\n const expire = Math.floor(Date.now()) + 60 * 60 * 24 * 7\n const payload = {\n uid: r.id,\n type: 'user',\n exp: expire\n }\n\n const access_token = cloud.getToken(payload)\n return {\n code: 0,\n data: {\n access_token,\n username,\n uid: r.id,\n expire\n }\n }\n}\n\n\n/**\n * @param {string} content\n * @return {string}\n */\nfunction hashPassword(content) {\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n}\n\n\n\n"},{"label":"微信小程序登陆","name":"wx-mp-login","description":"微信小程序登陆","enableHTTP":true,"status":0,"tags":["预置"],"code":"import cloud from '@/cloud-sdk'\n\n// 你可以让一个云函数监听 `App:ready` 触发器事件,初始化必要的配置信息(cloud.shared)\nconst appid = cloud.shared.get('settings.wxmp.appid') // 微信小程序 AppId\nconst appsecret = cloud.shared.get('settings.wxmp.appsecret') // 微信小程序 AppSecret\n\n/**\n * @body string code\n * @returns \n */\nexports.main = async function (ctx) {\n const db = cloud.database()\n\n\n const { body } = ctx\n const code = body.code\n\n // 获取 openid\n const openid = await getOpenId(code)\n if (!openid) {\n return 'invalid code'\n }\n\n // 根据 openid 获取新用户\n let { data } = await db.collection('users')\n .where({ openid })\n .getOne()\n\n\n // 如果用户不存在\n if (!data) {\n // 添加新用户\n await db.collection('users')\n .add({\n openid,\n created_at: Date.now(),\n updated_at: Date.now()\n })\n\n const r = await db.collection('users')\n .where({ openid })\n .getOne()\n\n data = r.data\n }\n\n // 生成 token\n const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7\n const token = cloud.getToken({ uid: data._id, exp: expire })\n return {\n uid: data._id,\n access_token: token,\n openid,\n expire\n }\n}\n\n/**\n * 获取 openid\n * @param {string} code Then auth code\n * @return {Promise}\n */\nasync function getOpenId(code) {\n\n const api_url = `https://api.weixin.qq.com/sns/jscode2session`\n const param = `appid=${appid}&secret=${appsecret}&js_code=${code}&grant_type=authorization_code`\n\n console.log('request url: ', `${api_url}?${param}`)\n\n const res = await cloud.fetch(`${api_url}?${param}`)\n\n console.log(res.data)\n // { session_key: string; openid: string } \n\n if (res.data.errcode > 0) {\n return null\n }\n\n return res?.data?.openid\n}\n\n"}],"policies":[{"name":"admin","rules":{"admins":{"read":"$has('admin.read')","update":"$has('admin.edit')","add":"$has('admin.create')","remove":"$has('admin.delete')"},"permissions":{"read":"$has('permission.read')","update":"$has('permission.edit')","add":"$has('permission.create')","remove":{"condition":"$has('permission.delete')","query":{"name":{"required":true,"notExists":"/roles/permissions"}}},"count":"$has('permission.read')"},"roles":{"read":"$has('role.read')","update":"$has('role.edit')","add":"$has('role.create')","remove":{"condition":"$has('role.delete')","query":{"name":{"required":true,"notExists":"/admins/roles"}}}}},"status":1,"injector":"injector-admin"},{"name":"user","rules":{"users":{"read":"query._id === uid","update":"query._id === uid","add":"data.created_by === uid","count":"true"},"categories":{"read":true,"count":"true"},"articles":{"read":"query._id === uid","update":"query._id === uid","add":"data.created_by === uid","remove":"query._id === uid","count":"true"}},"status":1,"injector":null}]} diff --git a/packages/system-server/init/export.js b/packages/system-server/init/export.js deleted file mode 100644 index 1d59ed44fc..0000000000 --- a/packages/system-server/init/export.js +++ /dev/null @@ -1,59 +0,0 @@ -#! /usr/bin/env node - -const { FunctionLoader } = require('./func-loader') -const adminRules = require('./policies/app-admin.json') -const userRules = require('./policies/app-user.json') -const { ObjectId } = require('mongodb') - -async function main() { - const functions = await loadBuiltinFunctions() - - const adminPolicy = getInitialPolicy('admin', adminRules, 'injector-admin') - const userPolicy = getInitialPolicy('user', userRules, null) - - const policies = [adminPolicy, userPolicy] - - const app = { - meta: { - name: 'basic_template' - }, - functions, - policies - } - - console.log(JSON.stringify(app)) -} - -main() - -/** - * get initial policies - * @param {string} name policy name - * @param {string} rules policy rules - * @param {string} injector cloud function id - * @returns - */ -function getInitialPolicy(name, rules, injector) { - return { - name: name, - rules: rules, - status: 1, - injector: injector, - } -} - -/** - * get built-in functions - * @returns - */ -async function loadBuiltinFunctions() { - const loader = new FunctionLoader() - const funcs = await loader.getFunctions() - for (const func of funcs) { - if (!func.triggers?.length) continue - for (const tri of func.triggers) { - tri._id = (new ObjectId()).toHexString() - } - } - return funcs -} \ No newline at end of file diff --git a/packages/system-server/init/func-loader.js b/packages/system-server/init/func-loader.js deleted file mode 100644 index 250528b656..0000000000 --- a/packages/system-server/init/func-loader.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * @Author: Maslow - * @Date: 2021-07-30 10:30:29 - * @LastEditTime: 2021-08-17 17:34:58 - * @Description: - */ - -const fs = require('fs/promises') -const path = require('path') - -class FunctionLoader { - rootPath = path.join(__dirname, 'functions') - - /** - * Get directory list of functions - * @returns {Promise} - */ - async getFunctionDirectoryList() { - const dirs = await fs.readdir(this.rootPath) - return dirs ?? [] - } - - /** - * Load functions - * @returns {Promise} - */ - async getFunctions() { - const dirs = await this.getFunctionDirectoryList() - const funcPaths = dirs.map(dir => path.join(this.rootPath, dir)) - const results = [] - for (const fp of funcPaths) { - const r = await this.loadFunction(fp) - results.push(r) - } - return results - } - - /** - * Load a function - * @param {string}} func_path - * @returns - */ - async loadFunction(func_path) { - const codePath = path.join(func_path, 'index.ts') - const code = await this.loadFunctionCode(codePath) - - const metaPath = path.join(func_path, 'meta.json') - const meta = await this.loadFunctionMeta(metaPath) - - if (!meta['status']) { - meta['status'] = 0 - } - return { ...meta, code } - } - - /** - * Load function's code - * @param {Promise} file_path - */ - async loadFunctionCode(file_path) { - const data = await fs.readFile(file_path, 'utf-8') - return data - } - - /** - * Load meta of function - * @param {string} file_path - */ - async loadFunctionMeta(file_path) { - const data = await fs.readFile(file_path, 'utf-8') - return JSON.parse(data) - } -} - -module.exports = { - FunctionLoader -} \ No newline at end of file diff --git a/packages/system-server/init/functions/admin-create/index.ts b/packages/system-server/init/functions/admin-create/index.ts deleted file mode 100644 index 64dbc89305..0000000000 --- a/packages/system-server/init/functions/admin-create/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -import cloud from '@/cloud-sdk' -import * as crypto from 'crypto' -const db = cloud.database() - -exports.main = async function (ctx) { - const uid = ctx.auth?.uid - if (!uid) { - return 'Unauthorized' - } - - // 权限验证 - const code = await checkPermission(uid, 'admin.create') - if (code) { - return 'Permission denied' - } - - const { username, password, avatar, name, roles } = ctx.body - if (!username || !password) { - return 'username or password cannot be empty' - } - - // 验证用户是否已存在 - const { total } = await db.collection('admins').where({ username }).count() - if (total > 0) { - return 'username already exists' - } - - // 验证 roles 是否合法 - const { total: valid_count } = await db.collection('roles') - .where({ - name: db.command.in(roles) - }).count() - - if (valid_count !== roles.length) { - return 'invalid roles' - } - - // add admin - const r = await db.collection('admins') - .add({ - username, - name: name ?? null, - avatar: avatar ?? null, - roles: roles ?? [], - created_at: Date.now(), - updated_at: Date.now() - }) - - // add admin password - await db.collection('password') - .add({ - uid: r.id, - password: hashPassword(password), - type: 'login', - created_at: Date.now(), - updated_at: Date.now() - }) - - return { - ...r, - uid: r.id - } -} - - -/** - * 通过 user id 获取权限列表 - * @param role_ids - * @returns - */ -async function getPermissions(uid: string) { - const db = cloud.database() - // 查用户 - const { data: admin } = await db.collection('admins') - .where({ _id: uid }) - .getOne() - - - // 查角色 - const { data: roles } = await db.collection('roles') - .where({ - name: { - $in: admin.roles ?? [] - } - }) - .get() - - if (!roles) { - return { permissions: [], roles: [], user: admin } - } - - const permissions = [] - for (const role of roles) { - const perms = role.permissions ?? [] - permissions.push(...perms) - } - - return { - permissions, - roles: roles.map(role => role.name), - user: admin - } -} - - - -/** - * 判断用户是否有权限 - * @param uid 用户ID - * @param permission 权限名 - * @returns 0 表示用户有权限, 401 表示用户未登录, 403 表示用户未授权 - */ -async function checkPermission(uid: string, permission: string): Promise { - if (!uid) { - return 401 - } - const { permissions } = await getPermissions(uid) - - if (!permissions.includes(permission)) { - return 403 - } - return 0 -} - -/** - * @param {string} content - * @return {string} - */ -function hashPassword(content: string): string { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') -} - diff --git a/packages/system-server/init/functions/admin-create/meta.json b/packages/system-server/init/functions/admin-create/meta.json deleted file mode 100644 index be442b1bad..0000000000 --- a/packages/system-server/init/functions/admin-create/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "后台管理-创建管理员", - "name": "admin-create", - "description": "", - "enableHTTP": true, - "status": 1, - "tags": ["后台管理", "预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/admin-edit/index.ts b/packages/system-server/init/functions/admin-edit/index.ts deleted file mode 100644 index 303df78fbb..0000000000 --- a/packages/system-server/init/functions/admin-edit/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -import cloud from '@/cloud-sdk' -import * as crypto from 'crypto' - -const db = cloud.database() - -exports.main = async function (ctx) { - console.log(ctx) - - const uid = ctx.auth?.uid - if (!uid) { - return 'Unauthorized' - } - - // 权限验证 - const code = await checkPermission(uid, 'admin.edit') - if (code) { - return 'Permission denied' - } - - // 参数验证 - const { _id, username, password, avatar, name, roles } = ctx.body - if (!_id) { - return 'admin id cannot be empty' - } - - // 验证 user _id 是否合法 - const { data: admins } = await db.collection('admins').where({ _id: _id }).get() - if (!admins || !admins.length) { - return 'user not exists' - } - - // 验证 roles 是否合法 - const { total: valid_count } = await db.collection('roles') - .where({ - name: db.command.in(roles) - }).count() - - if (valid_count !== roles.length) { - return 'invalid roles' - } - - // update password - if (password) { - await db.collection('password') - .where({ uid: _id }) - .update({ - password: hashPassword(password), - updated_at: Date.now() - }) - } - - const old = admins[0] - - // update admim - const data = { - updated_at: Date.now() - } - - // username - if (username && username != old.username) { - const { total } = await db.collection('admins').where({ username }).count() - if (total) { - return 'username already exists' - } - data['username'] = username - } - - // avatar - if (avatar && avatar != old.avatar) { - data['avatar'] = avatar - } - - // name - if (name && name != old.name) { - data['name'] = name - } - - // roles - if (roles) { - data['roles'] = roles - } - - console.log(_id, data) - const r = await db.collection('admins') - .where({ _id: _id }) - .update(data) - - return { - ...r, - _id - } -} - - -/** - * 通过 user id 获取权限列表 - * @param role_ids - * @returns - */ -async function getPermissions(uid: string) { - const db = cloud.database() - // 查用户 - const { data: admin } = await db.collection('admins') - .where({ _id: uid }) - .getOne() - - - // 查角色 - const { data: roles } = await db.collection('roles') - .where({ - name: { - $in: admin.roles ?? [] - } - }) - .get() - - if (!roles) { - return { permissions: [], roles: [], user: admin } - } - - const permissions = [] - for (const role of roles) { - const perms = role.permissions ?? [] - permissions.push(...perms) - } - - return { - permissions, - roles: roles.map(role => role.name), - user: admin - } -} - -/** - * 判断用户是否有权限 - * @param uid 用户ID - * @param permission 权限名 - * @returns 0 表示用户有权限, 401 表示用户未登录, 403 表示用户未授权 - */ -async function checkPermission(uid: string, permission: string): Promise { - if (!uid) { - return 401 - } - const { permissions } = await getPermissions(uid) - - if (!permissions.includes(permission)) { - return 403 - } - return 0 -} - -/** - * @param {string} content - * @return {string} - */ -function hashPassword(content: string): string { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') -} - diff --git a/packages/system-server/init/functions/admin-edit/meta.json b/packages/system-server/init/functions/admin-edit/meta.json deleted file mode 100644 index 133439e3b1..0000000000 --- a/packages/system-server/init/functions/admin-edit/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "后台管理-编辑管理员", - "name": "admin-edit", - "description": "", - "enableHTTP": true, - "status": 1, - "tags": ["后台管理", "预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/admin-getinfo/index.ts b/packages/system-server/init/functions/admin-getinfo/index.ts deleted file mode 100644 index 2fd3abf2bf..0000000000 --- a/packages/system-server/init/functions/admin-getinfo/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import cloud from '@/cloud-sdk' - -exports.main = async function (ctx) { - const db = cloud.database() - const uid = ctx.auth?.uid - if (!uid) { - return 'Unauthorized' - } - - // - const ret = await db.collection('admins') - .where({ _id: uid }) - .get() - - if (!ret.ok || !ret.data.length) { - return 'query admin error' - } - - const admin = ret.data[0] - - const { permissions } = await getPermissions(admin._id) - - return { - ...admin, - permissions - } -} - - -/** - * 通过 user id 获取权限列表 - * @param role_ids - * @returns - */ -async function getPermissions(uid: string) { - const db = cloud.database() - // 查用户 - const { data: admin } = await db.collection('admins') - .where({ _id: uid }) - .getOne() - - - // 查角色 - const { data: roles } = await db.collection('roles') - .where({ - name: { - $in: admin.roles ?? [] - } - }) - .get() - - if (!roles) { - return { permissions: [], roles: [], user: admin } - } - - const permissions = [] - for (const role of roles) { - const perms = role.permissions ?? [] - permissions.push(...perms) - } - - return { - permissions, - roles: roles.map(role => role.name), - user: admin - } -} \ No newline at end of file diff --git a/packages/system-server/init/functions/admin-getinfo/meta.json b/packages/system-server/init/functions/admin-getinfo/meta.json deleted file mode 100644 index 32932bc7eb..0000000000 --- a/packages/system-server/init/functions/admin-getinfo/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "后台管理-获取管理员信息", - "name": "admin-getinfo", - "description": "", - "enableHTTP": true, - "status": 1, - "tags": ["后台管理", "预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/admin-login/index.ts b/packages/system-server/init/functions/admin-login/index.ts deleted file mode 100644 index 2890d9ba91..0000000000 --- a/packages/system-server/init/functions/admin-login/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import cloud from '@/cloud-sdk' -import * as crypto from 'crypto' - -exports.main = async function (ctx) { - const db = cloud.database() - const { username, password } = ctx.body - - if (!username || !password) { - return '用户密码不正确' - } - - const ret = await db.collection('admins') - .withOne({ - query: db - .collection('password') - .where({ password: hashPassword(password), type: 'login' }), - localField: '_id', - foreignField: 'uid' - }) - .where({ username }) - .merge({ intersection: true }) - - if (ret.ok && ret.data.length) { - const admin = ret.data[0] - - // 默认 token 有效期为 7 天 - const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 - const payload = { - uid: admin._id, - type: 'admin', - exp: expire - } - const access_token = cloud.getToken(payload) - - return { - access_token, - username, - uid: admin._id, - expire - } - } - - return 'invalid username or password' -} - - -/** - * @param {string} content - * @return {string} - */ -function hashPassword(content: string): string { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') -} - diff --git a/packages/system-server/init/functions/admin-login/meta.json b/packages/system-server/init/functions/admin-login/meta.json deleted file mode 100644 index c99fb51fb4..0000000000 --- a/packages/system-server/init/functions/admin-login/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "后台管理-管理员登陆", - "name": "admin-login", - "description": "", - "enableHTTP": true, - "status": 1, - "tags": ["后台管理", "预置", "登陆"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/aliyun-sms-service/index.ts b/packages/system-server/init/functions/aliyun-sms-service/index.ts deleted file mode 100644 index 56d1d8b10b..0000000000 --- a/packages/system-server/init/functions/aliyun-sms-service/index.ts +++ /dev/null @@ -1,113 +0,0 @@ - -import cloud from '@/cloud-sdk' -import { v4 as uuidv4 } from 'uuid' -import * as crypto from 'crypto' -import * as querystring from 'querystring' - - -// 你可以让一个云函数监听 `app.ready` 触发器事件,初始化必要的配置信息(cloud.shared) -const config = cloud.shared.get('aliyun.sms.config') - -const accessKeyId = config.accessKeyId // 阿里云访问 Key ID -const accessKeySecret = config.accessKeySecret // 阿里云访问 Key Secret -const api_entrypoint = config.api_entrypoint ?? 'https://dysmsapi.aliyuncs.com' -const signName = config.signName // 短信签名,修改为你的签名,如: "灼灼信息" -const templateCode = config.templateCode // 短信模板ID,修改为你的模板ID,如 'SMS_217850726' - -/** - * @body phone string 手机号 - * @body code string | number 验证码 - */ - exports.main = async function (ctx) { - const phone = ctx.body?.phone - if (!phone) { - return 'error: invalid phone' - } - const code = ctx.body?.code - if (!code) { - return 'error: invalid code' - } - - const params = sortObjectKeys({ - AccessKeyId: accessKeyId, - Action: 'SendSms', - Format: 'json', - SignatureMethod: 'HMAC-SHA1', - SignatureNonce: uuidv4(), - SignatureVersion: '1.0', - Version: '2017-05-25', - Timestamp: (new Date()).toISOString(), - PhoneNumbers: phone, - SignName: signName, - TemplateCode: templateCode, - TemplateParam: `{"code": ${code}}` - }) - - params['Signature'] = specialEncode(sign(params)) - - const query = querystring.stringify(params) - const url = `${api_entrypoint}?${query}` - - try { - const r = await cloud.fetch(url) - console.log(r.data) - if(r.data?.Code === 'OK') - return 'ok' - else - return r.data - - } catch (err) { - console.log(err) - return 'error: ' + err - } -} -// 签名 -function sign(raw_params) { - const params = encode(raw_params) - - //拼接strToSign - let strToSign = ''; - for (let i in params) { - strToSign += i + '=' + params[i] + '&'; - } - strToSign = strToSign.substr(0, strToSign.length - 1); - strToSign = "GET&" + encodeURIComponent('/') + '&' + encodeURIComponent(strToSign); - - // 阿里云签名是要求 基于 hash 的原始二进制值 进行 base64编码 - const ret = crypto.createHmac('sha1', accessKeySecret + '&') - .update(strToSign) - .digest('base64') - - return ret -} - -//对各个参数进行字典序升序排序 -function sortObjectKeys(obj) { - const tmp = {}; - Object.keys(obj).sort().forEach(k => tmp[k] = obj[k]) - return tmp; -} - - -//对排序之后的参数进行 uriencode + POP 编码 -function encode(params) { - const obj = {} - //对urlencode之后的特殊字符进行替换 - for (let i in params) { - const str = encodeURIComponent(params[i]) - obj[i] = specialEncode(str) - } - return obj -} - -// 阿里云的特殊编码(POP编码) -function specialEncode(encoded) { - if (encoded.indexOf('+')) { - encoded.replace("+", "%20"); - } else if (encoded.indexOf('*')) { - encoded.replace("*", "%2A"); - } else if (encoded.indexOf('%7E')) { - encoded.replace("%7E", "~"); - } - return encoded -} diff --git a/packages/system-server/init/functions/aliyun-sms-service/meta.json b/packages/system-server/init/functions/aliyun-sms-service/meta.json deleted file mode 100644 index 41a7170dba..0000000000 --- a/packages/system-server/init/functions/aliyun-sms-service/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "aliyun-sms-service", - "name": "aliyun-sms-service", - "description": "阿里云通信-发短信内部调用服务,不开启外网访问", - "enableHTTP": false, - "status": 0, - "debugParams": "{\n \"phone\": \"13184211245\",\n \"code\": \"1234\"\n}", - "tags": ["预置", "阿里云"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/email-sender-service/index.ts b/packages/system-server/init/functions/email-sender-service/index.ts deleted file mode 100644 index 2461307dd7..0000000000 --- a/packages/system-server/init/functions/email-sender-service/index.ts +++ /dev/null @@ -1,34 +0,0 @@ - -import cloud from '@/cloud-sdk' -import * as nodemailer from 'nodemailer' -import * as assert from 'assert' - -exports.main = async function (ctx) { - const body = ctx.body - const db = cloud.database() - const CONFIG = cloud.shared.get('sys.config.mail') - assert.ok(CONFIG, 'no mail config found') - - const transporter = nodemailer.createTransport({ - host: CONFIG.host, - port: CONFIG.port, - secure: true, // true for 465, false for other ports - auth: { - user: CONFIG.user, - pass: CONFIG.pass, - }, - }) - - // send mail with defined transport object - const info = await transporter.sendMail({ - from: CONFIG.sender, // sender address - to: body.email, // list of receivers - subject: body.subject, // Subject line - text: body.text, // plain text body - // html: "Hello world?", // html body - }) - - console.log(info) - - return info -} diff --git a/packages/system-server/init/functions/email-sender-service/meta.json b/packages/system-server/init/functions/email-sender-service/meta.json deleted file mode 100644 index c4583e8cb3..0000000000 --- a/packages/system-server/init/functions/email-sender-service/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "email-sender-service", - "name": "email-sender-service", - "description": "", - "enableHTTP": false, - "status": 0, - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/exec_shell/index.ts b/packages/system-server/init/functions/exec_shell/index.ts deleted file mode 100644 index 15d919d25f..0000000000 --- a/packages/system-server/init/functions/exec_shell/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as child_process from 'child_process' - -/** - * 可修改本函数,安装指定的依赖包。 - * 注意: - * 0. 因客户端请求超时时间限制,函数可能在返回前请求超时,但该命令会继续运行,请知晓 - * 1. 不要开启本函数的 HTTP 访问(安全风险) - * 2. 通常请保持本函数是停用状态(安全风险) - */ - -exports.main = async function (ctx) { - const r = child_process.execSync("npm i @types/nodemailer nodemailer") - console.log(r.toString()) - return 'ok' -} diff --git a/packages/system-server/init/functions/exec_shell/meta.json b/packages/system-server/init/functions/exec_shell/meta.json deleted file mode 100644 index 4c7f76cab4..0000000000 --- a/packages/system-server/init/functions/exec_shell/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "在服务端执行 shell 脚本", - "name": "exec_shell", - "description": "", - "enableHTTP": false, - "status": 0, - "tags": ["预置", "scripts"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/file-token/index.ts b/packages/system-server/init/functions/file-token/index.ts deleted file mode 100644 index 8b5d086dd0..0000000000 --- a/packages/system-server/init/functions/file-token/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import cloud from '@/cloud-sdk' - -/** - * 本函数可发放文件访问令牌,用于下载或上传文件 - * @TODO 你可以修改本函数,以实现符合业务的文件访问授权逻辑 - * - * @body {string} bucket 文件存储的名字空间,bucket = '*' 时代表可访问所有 bucket - * @body {string} filename 文件名,可选的,如不提供则代表可访问所有文件 - * @body {string[]} ops 授权的操作权限,取值为之一或多个: "read" | "create" | "delete" | "list" - * @body {number} expire 令牌有效期,单位为秒,默认为一小时,即 3600 - */ - -exports.main = async function (ctx) { - const uid = ctx.auth?.uid - if (!uid) return 'error: unauthorized' - - const bucket = ctx.body?.bucket - const filename = ctx.body?.filename ?? undefined - const ops = ctx.body?.ops ?? ['read'] - const expire = ctx.body?.expire ?? 3600 - - if (!bucket) { - return 'error: invalid bucket' - } - - const exp = Math.floor(Date.now() / 1000) + expire - const payload = { bucket, filename, exp, ops } - - return cloud.getToken(payload) -} diff --git a/packages/system-server/init/functions/file-token/meta.json b/packages/system-server/init/functions/file-token/meta.json deleted file mode 100644 index a97b48ba74..0000000000 --- a/packages/system-server/init/functions/file-token/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "文件访问令牌", - "name": "file-token", - "description": "文件访问令牌", - "enableHTTP": true, - "status": 0, - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/init-app-rbac/index.ts b/packages/system-server/init/functions/init-app-rbac/index.ts deleted file mode 100644 index 334d748ef0..0000000000 --- a/packages/system-server/init/functions/init-app-rbac/index.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * 本函数可用于初始化一套 RBAC 必要的数据,通常不需要删除此云函数,也不要开启 HTTP 调用。 - */ - - import cloud from '@/cloud-sdk' - import * as assert from 'assert' - import * as crypto from 'crypto' - const db = cloud.database() - - - exports.main = async function (ctx) { - - // 创建 RBAC 初始权限 - await createInitialPermissions() - - // 创建 RBAC 初始角色 - await createFirstRole() - - // 创建初始管理员 - await createFirstAdmin("admin", "123456") - - return 'ok' - } - - - - - /** - * 预置 RBAC 权限 - */ - const permissions = [ - { name: 'role.create', label: '创建角色' }, - { name: 'role.read', label: '读取角色' }, - { name: 'role.edit', label: '编辑角色' }, - { name: 'role.delete', label: '删除角色' }, - - { name: 'permission.create', label: '创建权限' }, - { name: 'permission.read', label: '读取权限' }, - { name: 'permission.edit', label: '编辑权限' }, - { name: 'permission.delete', label: '删除权限' }, - - { name: 'admin.create', label: '创建管理员' }, - { name: 'admin.read', label: '获取管理员' }, - { name: 'admin.edit', label: '编辑管理员' }, - { name: 'admin.delete', label: '删除管理员' } - ] - - - - // 创建初始管理员 - async function createFirstAdmin(username: string, password: string) { - try { - - const { total } = await db.collection('admins').count() - if (total > 0) { - console.log('admin already exists') - return - } - - await cloud.mongo.db.collection('admins').createIndex('username', { unique: true }) - - const { data } = await db.collection('roles').get() - const roles = data.map(it => it.name) - - const r_add = await db.collection('admins').add({ - username, - avatar: "https://static.dingtalk.com/media/lALPDe7szaMXyv3NAr3NApw_668_701.png", - name: 'Admin', - roles, - created_at: Date.now(), - updated_at: Date.now() - }) - assert.ok(r_add.id, 'add admin occurs error') - - await db.collection('password').add({ - uid: r_add.id, - password: hashPassword(password), - type: 'login', - created_at: Date.now(), - updated_at: Date.now() - }) - - return r_add.id - } catch (error) { - console.error(error.message) - } - } - - // 创建初始角色 - async function createFirstRole() { - try { - - await cloud.mongo.db.collection('roles').createIndex('name', { unique: true }) - - const r_perm = await db.collection('permissions').get() - assert(r_perm.ok, 'get permissions failed') - - const permissions = r_perm.data.map(it => it.name) - - const r_add = await db.collection('roles').add({ - name: 'superadmin', - label: '超级管理员', - description: '系统初始化的超级管理员', - permissions, - created_at: Date.now(), - updated_at: Date.now() - }) - - assert.ok(r_add.id, 'add role occurs error') - - return r_add.id - } catch (error) { - if (error.code == 11000) { - return console.log('permissions already exists') - } - - console.error(error.message) - } - } - - // 创建初始权限 - async function createInitialPermissions() { - - // 创建唯一索引 - await cloud.mongo.db.collection('permissions').createIndex('name', { unique: true }) - - for (const perm of permissions) { - try { - const data = { - ...perm, - created_at: Date.now(), - updated_at: Date.now() - } - await db.collection('permissions').add(data) - } catch (error) { - if (error.code == 11000) { - console.log('permissions already exists') - continue - } - console.error(error.message) - } - } - - return true - } - - - /** - * @param {string} content - * @return {string} - */ - function hashPassword(content: string): string { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') - } - - \ No newline at end of file diff --git a/packages/system-server/init/functions/init-app-rbac/meta.json b/packages/system-server/init/functions/init-app-rbac/meta.json deleted file mode 100644 index 63b34ae962..0000000000 --- a/packages/system-server/init/functions/init-app-rbac/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "初始化应用 RBAC 数据", - "name": "init-app-rbac", - "description": "初始化一个经典的 RBAC,包括基础的权限、角色、初始管理员", - "enableHTTP": false, - "status": 1, - "tags": ["初始化", "预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/initializer/index.ts b/packages/system-server/init/functions/initializer/index.ts deleted file mode 100644 index 37c632bd37..0000000000 --- a/packages/system-server/init/functions/initializer/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 本函数会默认创建 'App:ready' 事件触发器,应用启动并初始化完成后被自动调用。 - * - * 本函数可用于初始化应用必要的一些配置、数据,通常不需要删除此云函数,也不要开启 HTTP 调用。 - */ - -import cloud from '@/cloud-sdk' -const db = cloud.database() - - -exports.main = async function (ctx) { - - const r = await cloud.invoke('init-app-rbac', {}) - console.log(r.logs) - if (r.data === 'ok') { - console.log('init rbac: ok') - } - - return 'ok' -} - - diff --git a/packages/system-server/init/functions/initializer/meta.json b/packages/system-server/init/functions/initializer/meta.json deleted file mode 100644 index dc9aef656a..0000000000 --- a/packages/system-server/init/functions/initializer/meta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "label": "应用初始化器", - "name": "initializer", - "description": "", - "enableHTTP": false, - "reserved": true, - "status": 1, - "tags": ["初始化", "预置"], - "triggers": [ - { - "name": "监听应用启动就绪事件", - "type": "event", - "event": "App:ready", - "desc": "", - "status": 1 - } - ] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/injector-admin/index.ts b/packages/system-server/init/functions/injector-admin/index.ts deleted file mode 100644 index 9bad9f4c45..0000000000 --- a/packages/system-server/init/functions/injector-admin/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -import cloud from '@/cloud-sdk' -import * as crypto from 'crypto' - -/** - * 本函数为 policy injector, 当用户请求 proxy/:policy 时,会调用本函数返回的函数获取该策略的 injections。 - * 返回的 injections 会注入到该策略规则中执行。 - * 例如,本例中返回了 $has 和 $is 函数,则在规则中可以这样使用: - * ```json - * { - * "add": "$has('article.create')", - * "remove": "$is('admin')" - * } - * ``` - */ - -exports.main = async function (ctx) { - - return async function (payload: any, params: any) { - const auth = payload || {} - const { permissions, roles } = await getPermissions(auth.uid) - return { - ...auth, - $has: (permissionName) => { - return permissions.includes(permissionName) - }, - $is: (roleName) => { - return roles.includes(roleName) - } - } - } -} - - -/** - * 通过 user id 获取权限列表 - * @param role_ids - * @returns - */ -async function getPermissions(uid: string) { - const db = cloud.database() - // 查用户 - const { data: admin } = await db.collection('admins') - .where({ _id: uid }) - .getOne() - - - // 查角色 - const { data: roles } = await db.collection('roles') - .where({ - name: { - $in: admin.roles ?? [] - } - }) - .get() - - if (!roles) { - return { permissions: [], roles: [], user: admin } - } - - const permissions = [] - for (const role of roles) { - const perms = role.permissions ?? [] - permissions.push(...perms) - } - - return { - permissions, - roles: roles.map(role => role.name), - user: admin - } -} - - - -/** - * 判断用户是否有权限 - * @param uid 用户ID - * @param permission 权限名 - * @returns 0 表示用户有权限, 401 表示用户未登录, 403 表示用户未授权 - */ -async function checkPermission(uid: string, permission: string): Promise { - if (!uid) { - return 401 - } - const { permissions } = await getPermissions(uid) - - if (!permissions.includes(permission)) { - return 403 - } - return 0 -} - -/** - * @param {string} content - * @return {string} - */ -function hashPassword(content: string): string { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') -} - diff --git a/packages/system-server/init/functions/injector-admin/meta.json b/packages/system-server/init/functions/injector-admin/meta.json deleted file mode 100644 index 14fd426f93..0000000000 --- a/packages/system-server/init/functions/injector-admin/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "injector-admin", - "name": "injector-admin", - "description": "", - "enableHTTP": false, - "status": 1, - "tags": ["预置", "injector"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/send-login-smscode/index.ts b/packages/system-server/init/functions/send-login-smscode/index.ts deleted file mode 100644 index c5f9d6a635..0000000000 --- a/packages/system-server/init/functions/send-login-smscode/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import cloud from '@/cloud-sdk' - -/** - * @api - * @body phone string 手机号 - */ - -// main function - exports.main = async function (ctx) { - const db = cloud.database() - const phone = ctx.body?.phone - if (!phone) { - return 'Error: invalid phone' - } - - const code = Math.min(Math.floor(1000 + Math.random() * 9000), 9999) - - const r = await sendSMSCode(phone, code) - if (r.data === 'ok') { - await db.collection('verify_code').add({ - type: 'sms', - phone, - code, - event: 'login', - created_at: Date.now() - }) - } - return r -} - -/** - * 发送验证码 - * @return {Promise} - * @see cloud function: aliyun-sms-service - */ -async function sendSMSCode(phone, code) { - const body = { phone, code } - const r = await cloud.invoke('aliyun-sms-service', { body }) - return r -} - diff --git a/packages/system-server/init/functions/send-login-smscode/meta.json b/packages/system-server/init/functions/send-login-smscode/meta.json deleted file mode 100644 index 1922cfc0b0..0000000000 --- a/packages/system-server/init/functions/send-login-smscode/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "发送登陆验证码", - "name": "send-login-smscode", - "description": "发送登陆短信验证码", - "enableHTTP": true, - "status": 0, - "debugParams": "{\n \"phone\": \"13184211245\"\n}", - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/test/index.ts b/packages/system-server/init/functions/test/index.ts deleted file mode 100644 index ed48071e22..0000000000 --- a/packages/system-server/init/functions/test/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import cloud from '@/cloud-sdk' - -exports.main = async function (ctx) { - console.log(ctx) - - return 'ok' -} diff --git a/packages/system-server/init/functions/test/meta.json b/packages/system-server/init/functions/test/meta.json deleted file mode 100644 index ab3811bb85..0000000000 --- a/packages/system-server/init/functions/test/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "测试", - "name": "test", - "description": "", - "enableHTTP": true, - "status": 1, - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/user-passwd-login/index.ts b/packages/system-server/init/functions/user-passwd-login/index.ts deleted file mode 100644 index 0b311688e8..0000000000 --- a/packages/system-server/init/functions/user-passwd-login/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as crypto from 'crypto' -import cloud from '@/cloud-sdk' -/** - * @body username string 用户名 - * @body password string 密码 - */ - exports.main = async function (ctx) { - const db = cloud.database() - - // 参数验证 - const { username, password } = ctx.body - if (!username || !password) { - return { code: 1, error: 'invalid phone or password' } - } - - // 验证用户名是否存在 - const { data: user } = await db.collection('users') - .where({ username }) - .getOne() - - if (!user) { - return {code: 1, error: 'invalid username or password'} - } - - // 验证密码是否正确 - const ret = await db.collection('password') - .where({ uid: user._id, password: hashPassword(password), type: 'login' }) - .count() - - if (ret.total > 0) { - - // 默认 token 有效期为 7 天 - const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 - const payload = { - uid: user._id, - type: 'user', - exp: expire - } - const access_token = cloud.getToken(payload) - return { - code: 0, - data: { - access_token, - user, - uid: user._id, - expire - } - } - } - - return { code: 1, error: 'invalid username or password' } -} - -/** - * @param {string} content - * @return {string} - */ -function hashPassword(content) { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') -} - diff --git a/packages/system-server/init/functions/user-passwd-login/meta.json b/packages/system-server/init/functions/user-passwd-login/meta.json deleted file mode 100644 index d1579be94b..0000000000 --- a/packages/system-server/init/functions/user-passwd-login/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "用户密码登陆", - "name": "user-passwd-login", - "description": "用户密码登陆", - "enableHTTP": true, - "status": 1, - "debugParams": "{\n \"username\": \"less\",\n \"password\": \"less123\"\n}", - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/user-password-register/index.ts b/packages/system-server/init/functions/user-password-register/index.ts deleted file mode 100644 index a93b776aed..0000000000 --- a/packages/system-server/init/functions/user-password-register/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as crypto from 'crypto' -import cloud from '@/cloud-sdk' -/** - * @body username string 用户名 - * @body password string 密码 - */ - exports.main = async function (ctx) { - const db = cloud.database() - - // 参数验证 - const { username, password } = ctx.body - if (!username|| !password) { - return { code: 1, error: 'invalid username or password' } - } - - // 检查用户名是否已存在 - const r_count = await db.collection('users') - .where({ username }) - .count() - - if (r_count.total > 0) { - return { code: 1, error: `${username} already exists` } - } - - // 创建 user - const r = await db.collection('users').add({ - username, - created_at: Date.now(), - updated_at: Date.now() - }) - - // 创建 user password - await db.collection('password').add({ - uid: r.id, - password: hashPassword(password), - type: "login", - created_at: Date.now(), - updated_at: Date.now() - }) - - - // 注册完成后自动登录,生成 token: 默认 token 有效期为 7 天 - const expire = Math.floor(Date.now()) + 60 * 60 * 24 * 7 - const payload = { - uid: r.id, - type: 'user', - exp: expire - } - - const access_token = cloud.getToken(payload) - return { - code: 0, - data: { - access_token, - username, - uid: r.id, - expire - } - } -} - - -/** - * @param {string} content - * @return {string} - */ -function hashPassword(content) { - return crypto - .createHash('sha256') - .update(content) - .digest('hex') -} - - - diff --git a/packages/system-server/init/functions/user-password-register/meta.json b/packages/system-server/init/functions/user-password-register/meta.json deleted file mode 100644 index 3f120abbad..0000000000 --- a/packages/system-server/init/functions/user-password-register/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "用户密码注册", - "name": "user-password-register", - "description": "用户密码注册", - "enableHTTP": true, - "status": 1, - "debugParams": "{\"username\":\"less\",\"password\":\"less123\"}", - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/functions/wx-mp-login/index.ts b/packages/system-server/init/functions/wx-mp-login/index.ts deleted file mode 100644 index 9727944881..0000000000 --- a/packages/system-server/init/functions/wx-mp-login/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import cloud from '@/cloud-sdk' - -// 你可以让一个云函数监听 `App:ready` 触发器事件,初始化必要的配置信息(cloud.shared) -const appid = cloud.shared.get('settings.wxmp.appid') // 微信小程序 AppId -const appsecret = cloud.shared.get('settings.wxmp.appsecret') // 微信小程序 AppSecret - -/** - * @body string code - * @returns - */ -exports.main = async function (ctx) { - const db = cloud.database() - - - const { body } = ctx - const code = body.code - - // 获取 openid - const openid = await getOpenId(code) - if (!openid) { - return 'invalid code' - } - - // 根据 openid 获取新用户 - let { data } = await db.collection('users') - .where({ openid }) - .getOne() - - - // 如果用户不存在 - if (!data) { - // 添加新用户 - await db.collection('users') - .add({ - openid, - created_at: Date.now(), - updated_at: Date.now() - }) - - const r = await db.collection('users') - .where({ openid }) - .getOne() - - data = r.data - } - - // 生成 token - const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 - const token = cloud.getToken({ uid: data._id, exp: expire }) - return { - uid: data._id, - access_token: token, - openid, - expire - } -} - -/** - * 获取 openid - * @param {string} code Then auth code - * @return {Promise} - */ -async function getOpenId(code) { - - const api_url = `https://api.weixin.qq.com/sns/jscode2session` - const param = `appid=${appid}&secret=${appsecret}&js_code=${code}&grant_type=authorization_code` - - console.log('request url: ', `${api_url}?${param}`) - - const res = await cloud.fetch(`${api_url}?${param}`) - - console.log(res.data) - // { session_key: string; openid: string } - - if (res.data.errcode > 0) { - return null - } - - return res?.data?.openid -} - diff --git a/packages/system-server/init/functions/wx-mp-login/meta.json b/packages/system-server/init/functions/wx-mp-login/meta.json deleted file mode 100644 index 05fd748f34..0000000000 --- a/packages/system-server/init/functions/wx-mp-login/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "微信小程序登陆", - "name": "wx-mp-login", - "description": "微信小程序登陆", - "enableHTTP": true, - "status": 0, - "tags": ["预置"] -} \ No newline at end of file diff --git a/packages/system-server/init/policies/app-admin.json b/packages/system-server/init/policies/app-admin.json deleted file mode 100644 index 5fcdc9c869..0000000000 --- a/packages/system-server/init/policies/app-admin.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "admins": { - "read": "$has('admin.read')", - "update": "$has('admin.edit')", - "add": "$has('admin.create')", - "remove": "$has('admin.delete')" - }, - "permissions": { - "read": "$has('permission.read')", - "update": "$has('permission.edit')", - "add": "$has('permission.create')", - "remove": { - "condition": "$has('permission.delete')", - "query": { - "name": { - "required": true, - "notExists": "/roles/permissions" - } - } - }, - "count": "$has('permission.read')" - }, - "roles": { - "read": "$has('role.read')", - "update": "$has('role.edit')", - "add": "$has('role.create')", - "remove": { - "condition": "$has('role.delete')", - "query": { - "name": { - "required": true, - "notExists": "/admins/roles" - } - } - } - } -} \ No newline at end of file diff --git a/packages/system-server/init/policies/app-user.json b/packages/system-server/init/policies/app-user.json deleted file mode 100644 index 0b3f408d0c..0000000000 --- a/packages/system-server/init/policies/app-user.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "users": { - "read": "query._id === uid", - "update": "query._id === uid", - "add": "data.created_by === uid", - "count": "true" - }, - "categories": { - "read": true, - "count": "true" - }, - "articles": { - "read": "query._id === uid", - "update": "query._id === uid", - "add": "data.created_by === uid", - "remove": "query._id === uid", - "count": "true" - } -} \ No newline at end of file diff --git a/packages/system-server/src/api/init.ts b/packages/system-server/src/api/init.ts index 6dd14880cc..d6fab7c7b0 100644 --- a/packages/system-server/src/api/init.ts +++ b/packages/system-server/src/api/init.ts @@ -1,15 +1,133 @@ +import { ObjectId } from "mongodb" +import Config from "../config" import { Constants } from "../constants" import { DatabaseAgent } from "../lib/db-agent" - +import { hashPassword } from "../utils/hash" +import { ApplicationStruct, getApplicationByAppid, publishApplicationPackages } from "./application" +import { ApplicationService } from "./service" +import * as fs from "fs" +import { ApplicationImporter } from "../lib/importer" +import { publishFunctions } from "./function" +import { publishAccessPolicies } from "./policy" /** - * Create system db collection indexes + * Initialize APIs */ -export async function createSystemCollectionIndexes() { - const sys_accessor = DatabaseAgent.sys_accessor - await sys_accessor.ready - await sys_accessor.db.collection(Constants.cn.accounts).createIndex('username', { unique: true }) - await sys_accessor.db.collection(Constants.cn.applications).createIndex('appid', { unique: true }) - await sys_accessor.db.collection(Constants.cn.functions).createIndex({ appid: 1, name: 1 }, { unique: true }) - await sys_accessor.db.collection(Constants.cn.policies).createIndex({ appid: 1, name: 1 }, { unique: true }) -} \ No newline at end of file +export class InitializerApi { + + static ready() { + const sys_accessor = DatabaseAgent.sys_accessor + return sys_accessor.ready + } + + /** + * Create system db collection indexes + */ + static async createSystemCollectionIndexes() { + const db = DatabaseAgent.db + await db.collection(Constants.cn.accounts).createIndex('username', { unique: true }) + await db.collection(Constants.cn.applications).createIndex('appid', { unique: true }) + await db.collection(Constants.cn.functions).createIndex({ appid: 1, name: 1 }, { unique: true }) + await db.collection(Constants.cn.policies).createIndex({ appid: 1, name: 1 }, { unique: true }) + } + + + /** + * create root account + * @returns account id + */ + static async createRootAccount() { + const db = DatabaseAgent.db + const username = Config.INIT_ROOT_ACCOUNT + const password = Config.INIT_ROOT_ACCOUNT_PASSWORD + + // add account + const r = await db.collection(Constants.cn.accounts) + .insertOne({ + username, + quota: { + app_count: Config.ACCOUNT_DEFAULT_APP_QUOTA + }, + name: 'root', + type: 'root', + password: hashPassword(password), + created_at: new Date(), + updated_at: new Date() + }) + + return r.insertedId + } + + /** + * create system server app + * @param account_id + * @param appid + * @returns app _id + */ + static async createSystemApp(account_id: ObjectId, appid: string) { + const SYSTEM_APP_NAME = 'laf.js system server app' + const db = DatabaseAgent.db + const db_config = DatabaseAgent.parseConnectionUri(Config.sys_db_uri) + + const data: ApplicationStruct = { + name: SYSTEM_APP_NAME, + created_by: account_id, + appid: appid, + status: 'created', + collaborators: [], + config: { + db_name: db_config.database, + db_user: db_config.username, + db_password: db_config.password, + server_secret_salt: Config.SYS_SERVER_SECRET_SALT + }, + runtime: { + image: Config.APP_SERVICE_IMAGE, + metrics: { + cpu_shares: 2048, + memory: 1024 + } + }, + buckets: [], + packages: [], + created_at: new Date(), + updated_at: new Date() + } + + // save it + const ret = await db.collection(Constants.cn.applications) + .insertOne(data as any) + + return ret.insertedId + } + + /** + * init system server app + */ + static async initSystemApp(appid: string) { + const app = await getApplicationByAppid(appid) + const data = fs.readFileSync(Config.SYSTEM_SERVER_APP_PACKAGE) + + const importer = new ApplicationImporter(app, data) + + importer.parse() + + await importer.import() + + await publishFunctions(app) + await publishAccessPolicies(app) + await publishApplicationPackages(app.appid) + } + + /** + * start system server app + * @param appid + * @returns container id + */ + static async startSystemApp(appid: string) { + const app = await getApplicationByAppid(appid) + const container_id = await ApplicationService.start(app) + + return container_id + } +} diff --git a/packages/system-server/src/api/service.ts b/packages/system-server/src/api/service.ts new file mode 100644 index 0000000000..5b0490547c --- /dev/null +++ b/packages/system-server/src/api/service.ts @@ -0,0 +1,88 @@ +import { Constants } from "../constants" +import { DatabaseAgent } from "../lib/db-agent" +import { DockerContainerServiceDriver } from "../lib/service-driver/container" +import { ApplicationStruct } from "./application" + + +export class ApplicationService { + /** + * start app service + * @param app + * @returns + */ + static async start(app: ApplicationStruct) { + const db = DatabaseAgent.db + const dockerService = new DockerContainerServiceDriver() + const container_id = await dockerService.startService(app) + + await db.collection(Constants.cn.applications) + .updateOne( + { appid: app.appid }, + { + $set: { status: 'running' } + }) + + return container_id + } + + /** + * stop app service + * @param app + * @returns + */ + static async stop(app: ApplicationStruct) { + const dockerService = new DockerContainerServiceDriver() + const container_id = await dockerService.stopService(app) + const db = DatabaseAgent.db + + await db.collection(Constants.cn.applications) + .updateOne( + { appid: app.appid }, + { + $set: { status: 'stopped' } + }) + + return container_id + } + + /** + * remove app service + * @param app + * @returns + */ + static async remove(app: ApplicationStruct) { + const dockerService = new DockerContainerServiceDriver() + const container_id = await dockerService.removeService(app) + const db = DatabaseAgent.db + + await db.collection(Constants.cn.applications) + .updateOne( + { appid: app.appid }, + { $set: { status: 'cleared' } }) + + return container_id + } + + /** + * restart app service + * @param app + * @returns + */ + static async restart(app: ApplicationStruct) { + await this.stop(app) + const container_id = await this.start(app) + return container_id + } + + /** + * if app is running + * @param app + * @returns + */ + static async isRunning(app: ApplicationStruct) { + const dockerService = new DockerContainerServiceDriver() + const info = await dockerService.info(app) + + return info.State.Running + } +} \ No newline at end of file diff --git a/packages/system-server/src/config.ts b/packages/system-server/src/config.ts index 8e213ee49f..b81a37ed76 100644 --- a/packages/system-server/src/config.ts +++ b/packages/system-server/src/config.ts @@ -1,4 +1,5 @@ import * as dotenv from 'dotenv' +import * as path from 'path' dotenv.config() /** @@ -144,4 +145,23 @@ export default class Config { const entrypoint: string = process.env.STORAGE_SERVICE_API_ENTRYPOINT return { secret, entrypoint } } + + static get INIT_ROOT_ACCOUNT() { + const account: string = process.env.INIT_ROOT_ACCOUNT || 'root' + return account + } + + static get INIT_ROOT_ACCOUNT_PASSWORD() { + const password: string = process.env.INIT_ROOT_ACCOUNT_PASSWORD + if (!password) { + throw new Error('env: `INIT_ROOT_ACCOUNT_PASSWORD` is missing') + } + return password + } + + static get SYSTEM_SERVER_APP_PACKAGE() { + const default_ = path.resolve(__dirname, '../apps/system-server.lapp') + const package_: string = process.env.SYSTEM_SERVER_APP_PACKAGE || default_ + return package_ + } } \ No newline at end of file diff --git a/packages/system-server/src/index.ts b/packages/system-server/src/index.ts index 21dc468180..428ed38ac0 100644 --- a/packages/system-server/src/index.ts +++ b/packages/system-server/src/index.ts @@ -1,7 +1,7 @@ /* * @Author: Maslow * @Date: 2021-07-30 10:30:29 - * @LastEditTime: 2021-11-15 16:00:03 + * @LastEditTime: 2021-12-24 13:34:48 * @Description: */ @@ -11,21 +11,13 @@ import { v4 as uuidv4 } from 'uuid' import Config from './config' import { router } from './router/index' import { logger } from './lib/logger' -import { createSystemCollectionIndexes } from './api/init' const server = express() server.use(express.json({ limit: '10000kb' }) as any) -createSystemCollectionIndexes() - .then(() => { - logger.info('system db indexes created') - }) -/** - * Allow CORS by default - */ server.all('*', function (_req, res, next) { res.header('X-Powered-By', 'LaF Server') next() diff --git a/packages/system-server/src/init.ts b/packages/system-server/src/init.ts new file mode 100644 index 0000000000..d69d2b4f73 --- /dev/null +++ b/packages/system-server/src/init.ts @@ -0,0 +1,51 @@ +import { ObjectId } from "bson" +import { getAccountByUsername } from "./api/account" +import { getApplicationByAppid } from "./api/application" +import { InitializerApi } from "./api/init" +import Config from "./config" +import { logger } from "./lib/logger" + +const SYSTEM_APPID = `00000000-0000-0000-0000-000000000000` + +/** + * x. create collection indexes + * a. create root account if not exists + * b. create & init`system-server` for root account if not exists + * c. start system server app if not running + */ +async function main() { + await InitializerApi.ready() + + // create indexes + await InitializerApi.createSystemCollectionIndexes() + logger.info('create system collection indexes') + + // create root account + let account_id: ObjectId + const account = await getAccountByUsername(Config.INIT_ROOT_ACCOUNT) + if (!account) { + account_id = await InitializerApi.createRootAccount() + logger.info('create root account') + } else { + account_id = account._id + } + + // create system server app + const app = await getApplicationByAppid(SYSTEM_APPID) + if (!app) { + await InitializerApi.createSystemApp(account_id, SYSTEM_APPID) + logger.info('create system server app') + + await InitializerApi.initSystemApp(SYSTEM_APPID) + logger.info('init system server app') + } + + // run system server app + await InitializerApi.startSystemApp(SYSTEM_APPID) + logger.info('start system server app') +} + + +main().then(() => { + process.exit(0) +}) \ No newline at end of file diff --git a/packages/system-server/src/router/application/service.ts b/packages/system-server/src/router/application/service.ts index c62b54c26f..cd175ac9e5 100644 --- a/packages/system-server/src/router/application/service.ts +++ b/packages/system-server/src/router/application/service.ts @@ -1,17 +1,15 @@ /* * @Author: Maslow * @Date: 2021-08-31 15:00:04 - * @LastEditTime: 2021-10-08 01:29:21 + * @LastEditTime: 2021-12-24 11:57:45 * @Description: */ import { Request, Response } from 'express' import { getApplicationByAppid } from '../../api/application' import { checkPermission } from '../../api/permission' -import { Constants } from '../../constants' -import { DatabaseAgent } from '../../lib/db-agent' import { permissions } from '../../constants/permissions' -import { DockerContainerServiceDriver } from '../../lib/service-driver/container' +import { ApplicationService } from '../../api/service' const { APPLICATION_UPDATE } = permissions /** @@ -19,7 +17,6 @@ const { APPLICATION_UPDATE } = permissions */ export async function handleStartApplicationService(req: Request, res: Response) { const uid = req['auth']?.uid - const db = DatabaseAgent.db const appid = req.params.appid const app = await getApplicationByAppid(appid) @@ -32,16 +29,7 @@ export async function handleStartApplicationService(req: Request, res: Response) return res.status(code).send() } - const dockerService = new DockerContainerServiceDriver() - const container_id = await dockerService.startService(app) - - await db.collection(Constants.cn.applications) - .updateOne( - { appid: app.appid }, - { - $set: { status: 'running' } - } - ) + const container_id = await ApplicationService.start(app) return res.send({ data: { @@ -57,7 +45,6 @@ export async function handleStartApplicationService(req: Request, res: Response) */ export async function handleStopApplicationService(req: Request, res: Response) { const uid = req['auth']?.uid - const db = DatabaseAgent.db const appid = req.params.appid const app = await getApplicationByAppid(appid) @@ -70,15 +57,7 @@ export async function handleStopApplicationService(req: Request, res: Response) return res.status(code).send() } - const dockerService = new DockerContainerServiceDriver() - const container_id = await dockerService.stopService(app) - - await db.collection(Constants.cn.applications) - .updateOne( - { appid: app.appid }, - { - $set: { status: 'stopped' } - }) + const container_id = await ApplicationService.stop(app) return res.send({ data: { @@ -94,7 +73,6 @@ export async function handleStopApplicationService(req: Request, res: Response) */ export async function handleRemoveApplicationService(req: Request, res: Response) { const uid = req['auth']?.uid - const db = DatabaseAgent.db const appid = req.params.appid const app = await getApplicationByAppid(appid) @@ -107,14 +85,7 @@ export async function handleRemoveApplicationService(req: Request, res: Response return res.status(code).send() } - const dockerService = new DockerContainerServiceDriver() - const container_id = await dockerService.removeService(app) - - await db.collection(Constants.cn.applications) - .updateOne( - { appid: app.appid }, - { $set: { status: 'cleared' } } - ) + const container_id = await ApplicationService.remove(app) return res.send({ data: { diff --git a/packages/system-server/src/utils/lang.ts b/packages/system-server/src/utils/lang.ts index f67c11d46e..3f66b5443b 100644 --- a/packages/system-server/src/utils/lang.ts +++ b/packages/system-server/src/utils/lang.ts @@ -1,7 +1,7 @@ /* * @Author: Maslow * @Date: 2021-07-30 10:30:29 - * @LastEditTime: 2021-10-06 21:36:32 + * @LastEditTime: 2021-12-24 13:42:15 * @Description: */ @@ -31,8 +31,8 @@ export function deepFreeze(object: Object) { /** - * 编译 Typescript 代码到 js - * @param source Typescript 源代码 + * compile typescript code to javascript + * @param source typescript source code */ export function compileTs2js(source: string) { const jscode = ts.transpile(source, { diff --git a/packages/system-server/start.sh b/packages/system-server/start.sh new file mode 100644 index 0000000000..a56f59e38c --- /dev/null +++ b/packages/system-server/start.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +echo "****** init start ******" +node ./dist/init.js +echo "****** init end *******" + +# source .env +echo "****** start service: node ./dist/index.js *******" +exec node ./dist/index.js \ No newline at end of file