diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/package.json b/packages/plugins/@tachybase/plugin-wechat-official-account/package.json
index 00a75d336..0563d4395 100644
--- a/packages/plugins/@tachybase/plugin-wechat-official-account/package.json
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/package.json
@@ -1,12 +1,21 @@
{
"name": "@tachybase/plugin-wechat-official-account",
- "version": "0.21.79",
+ "version": "0.21.73",
+ "keywords": [
+ "Authentication"
+ ],
"main": "dist/server/index.js",
"dependencies": {
+ "@ant-design/icons": "^5.3.7",
"@types/xml2js": "^0.4.14",
"antd": "^5.18.3",
+ "axios": "^1.7.2",
+ "bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
+ "crypto": "^1.0.1",
+ "jsonwebtoken": "^8.5.1",
"koa-bodyparser": "^4.3.0",
+ "qrcode.react": "^3.1.0",
"react-router-dom": "^6.11.2",
"xml2js": "^0.6.2"
},
diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/components.tsx b/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/components.tsx
new file mode 100644
index 000000000..ec735dab4
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/components.tsx
@@ -0,0 +1,139 @@
+// --4.1 直接插入缓存
+import React, { useEffect, useState } from 'react';
+import { SchemaComponent, useAPIClient, useApp } from '@tachybase/client';
+
+import { CopyOutlined, LoginOutlined } from '@ant-design/icons';
+import { Button, Input, message, Modal, Spin, Typography } from 'antd';
+import { useLocation } from 'react-router-dom';
+
+import { useTranslation } from './locale';
+
+export const AdminSettingsForm = () => {
+ const { t } = useTranslation();
+ const app = useApp();
+ const redirectUrl = 'https://lu.dev.daoyoucloud.com/api/wechat@handleOAuthCallback'; // 设置回调url
+
+ const onCopy = (text) => {
+ navigator.clipboard.writeText(text);
+ message.success(t('Copied'));
+ };
+
+ return (
+
+
+
+
+ {t('Redirect URL')}:
+
+ onCopy(redirectUrl)} />} />
+
+
+ );
+};
+
+export const SignInButton = ({ authenticator }) => {
+ const [href, setHref] = useState();
+ const [qrCodeVisible, setQrCodeVisible] = useState(false);
+ const [qrCodeUrl, setQrCodeUrl] = useState('');
+ const [loading, setLoading] = useState(false);
+ const { t } = useTranslation();
+ const api = useAPIClient();
+ const location = useLocation();
+ const urlSearchParams = new URLSearchParams(location.search);
+ const redirect = urlSearchParams.get('redirect');
+
+ useEffect(() => {
+ if (href) window.location.href = href;
+ }, [href]);
+
+ useEffect(() => {
+ const authenticatorName = urlSearchParams.get('authenticator');
+ const error = urlSearchParams.get('error');
+ if (authenticatorName === authenticator.name && error) {
+ message.error(t(error));
+ }
+ }, [authenticator.name, t, urlSearchParams]);
+
+ const onClick = async () => {
+ setLoading(true);
+ try {
+ const { data } = await api.request({
+ method: 'post',
+ url: 'https://lu.dev.daoyoucloud.com/api/wechat@generateQrCode', // 生成二维码
+ headers: { 'X-Authenticator': authenticator.name },
+ data: { redirect },
+ });
+
+ console.log('data.data.data.url:', data.data.data.url);
+
+ const url = data.data.data.url;
+ if (url) {
+ setQrCodeUrl(url);
+ setQrCodeVisible(true);
+ } else {
+ console.error('未能生成二维码URL');
+ }
+ } catch (error) {
+ message.error(t('Failed to generate QR code'));
+ console.error('二维码生成失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 显示二维码
+ return (
+ <>
+ } shape="round" onClick={onClick} loading={loading}>
+ {authenticator.title || 'WeChat 公众号登录'}
+
+ setQrCodeVisible(false)}
+ >
+ {loading ? : }
+
+ >
+ );
+};
diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/index.tsx b/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/index.tsx
index 3d701fd59..09108a1e5 100644
--- a/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/index.tsx
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/index.tsx
@@ -1,17 +1,13 @@
-import React from 'react';
-import { Plugin, useRequest } from '@tachybase/client';
+import { Plugin } from '@tachybase/client';
+import PluginAuthClient from '@tachybase/plugin-auth/client';
-import { CustomAuthLayout } from './AuthLayout';
+import { authType } from '../constants';
+import { AdminSettingsForm, SignInButton } from './components';
-export class PluginReplacePageClient extends Plugin {
- async afterAdd() {
- // await this.app.pm.add()
+export class PluginWeCahtOfficialAccount extends Plugin {
+ async load() {
+ this.app.pm.get(PluginAuthClient).registerType(authType, { components: { SignInButton, AdminSettingsForm } });
}
-
- async beforeLoad() {}
-
- // You can get and modify the app instance here
- async load() {}
}
-export default PluginReplacePageClient;
+export default PluginWeCahtOfficialAccount;
diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/locale.tsx b/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/locale.tsx
new file mode 100644
index 000000000..e30c0c782
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/src/client/locale.tsx
@@ -0,0 +1,7 @@
+import { useTranslation as useT } from '@tachybase/client';
+
+import { namespace } from '../constants';
+
+export function useTranslation() {
+ return useT([namespace, 'client'], { nsMode: 'fallback' });
+}
diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/src/constants.ts b/packages/plugins/@tachybase/plugin-wechat-official-account/src/constants.ts
new file mode 100644
index 000000000..5bdeb8283
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/src/constants.ts
@@ -0,0 +1,2 @@
+export const authType = '微信公众号';
+export const namespace = '微信公众号';
diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/actions/work.ts b/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/actions/work.ts
new file mode 100644
index 000000000..a7241b2c5
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/actions/work.ts
@@ -0,0 +1,39 @@
+import { Context, Next } from '@tachybase/actions';
+import { AppSupervisor } from '@tachybase/server';
+
+export const getAuthUrl = async (ctx: Context, next: Next) => {
+ const { redirect } = ctx.action.params.values;
+ const url = await ctx.auth.getAuthUrl(redirect);
+ ctx.body = { url };
+ await next();
+};
+
+export const redirect = async (ctx: Context, next: Next) => {
+ const { code, state } = ctx.request.query;
+
+ const stateString = Array.isArray(state) ? state[0] : state;
+ const search = new URLSearchParams(stateString);
+
+ const authenticator = search.get('name');
+ const appName = search.get('app');
+ const redirect = search.get('redirect') || '/admin';
+ let prefix = process.env.APP_PUBLIC_PATH || '';
+ if (appName && appName !== 'main') {
+ const appSupervisor = AppSupervisor.getInstance();
+ if (appSupervisor?.runningMode !== 'single') {
+ prefix += `apps/${appName}`;
+ }
+ }
+ const auth = await ctx.app.authManager.get(authenticator, ctx);
+ if (prefix.endsWith('/')) {
+ prefix = prefix.slice(0, -1);
+ }
+ try {
+ const { token } = await auth.signIn();
+ ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
+ } catch (error) {
+ ctx.logger.error('Work auth error', { error });
+ ctx.redirect(`${prefix}/signin?redirect=${redirect}&authenticator=${authenticator}&error=${error.message}`);
+ }
+ await next();
+};
diff --git a/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/plugin.ts b/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/plugin.ts
index 383a7dd3d..b02c5ee98 100644
--- a/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/plugin.ts
+++ b/packages/plugins/@tachybase/plugin-wechat-official-account/src/server/plugin.ts
@@ -1,22 +1,78 @@
+// --10、解决数据库注册插入问题
import crypto from 'crypto';
+import { BasicAuth } from '@tachybase/plugin-auth';
import { Plugin } from '@tachybase/server';
import axios from 'axios';
import xml2js from 'xml2js';
+import { authType } from '../constants';
+
export class PluginReplacePageServer extends Plugin {
- // 从数据库查询cofig表
- async config(): Promise {
+ // 从数据库查询config表
+ async config() {
const repo = this.app.db.getRepository('config');
- const config = (await repo.findById(1)) as any;
- return config.get(); // get()返回具体的data
+ const config = await repo.findById(1);
+ return config.get();
}
// 从数据库查询message表
- async message(): Promise {
+ async message() {
const repo = this.app.db.getRepository('message');
- const messages = (await repo.find()) as any;
- return messages; // 返回数组Type
+ const messages = await repo.find();
+ return messages;
+ }
+
+ // 从数据库查询menu表
+ async menu() {
+ const repo = this.app.db.getRepository('menu');
+ const menus = await repo.find();
+
+ // 将数据库中的数据构建成菜单结构
+ const menuStructure = { button: [] };
+
+ menus.forEach((menu) => {
+ if (!menu.button) {
+ // 一级菜单
+ if (menuStructure.button.length < 3) {
+ if (Buffer.byteLength(menu.name, 'utf8') <= 16) {
+ menuStructure.button.push({
+ type: menu.type,
+ name: menu.name,
+ key: menu.key || undefined,
+ url: menu.url || undefined,
+ sub_button: [],
+ });
+ } else {
+ console.warn(`一级菜单名称 "${menu.name}" 超过了 16 个字节的限制`);
+ }
+ } else {
+ console.warn('超过了一级菜单的数量限制');
+ }
+ } else {
+ // 二级菜单
+ const parentMenu = menuStructure.button.find((btn) => btn.name === menu.button);
+ if (parentMenu) {
+ if (parentMenu.sub_button.length < 5) {
+ if (Buffer.byteLength(menu.name, 'utf8') <= 40) {
+ parentMenu.sub_button.push({
+ type: menu.type,
+ name: menu.name,
+ key: menu.key || undefined,
+ url: menu.url || undefined,
+ });
+ } else {
+ console.warn(`二级菜单名称 "${menu.name}" 超过了 40 个字节的限制`);
+ }
+ } else {
+ console.warn('超过了二级菜单的数量限制');
+ }
+ }
+ }
+ });
+
+ console.log('-------------', JSON.stringify(menuStructure, null, 2));
+ return menuStructure;
}
// 验证Wechat平台的请求签名
@@ -35,8 +91,12 @@ export class PluginReplacePageServer extends Plugin {
const { appid, appsecret } = await this.config(); // 使用config方法 查询config表
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${appsecret}`;
+ console.log(`Fetching access token from URL: ${url}`);
+ console.log(`App ID: ${appid}, App Secret: ${appsecret}`);
+
try {
const response = await axios.get(url);
+ console.log('Access token response:', response.data);
return response.data.access_token;
} catch (error) {
console.error('Error getting access token:', error);
@@ -44,9 +104,33 @@ export class PluginReplacePageServer extends Plugin {
}
}
+ // 生成OAuth授权URL
+ async generateAuthUrl() {
+ const { appid } = await this.config(); // 获取appid
+ const redirectUri = encodeURIComponent('https://lu.dev.daoyoucloud.com/api/wechat@handleOAuthCallback');
+ const scope = 'snsapi_userinfo'; // 获取用户信息的权限
+ const state = 'STATE'; // 自定义的state参数
+
+ const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
+ return authUrl;
+ }
+
+ // 生成二维码URL
+ async generateQrCode() {
+ const authUrl = await this.generateAuthUrl();
+ if (!authUrl) {
+ console.error('Failed to generate OAuth URL');
+ return null;
+ }
+
+ const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(authUrl)}&size=300x300`;
+ return qrCodeUrl;
+ }
+
// 创建菜单
async createCustomMenu() {
const accessToken = await this.getAccessToken(); // 获取Wechat Token
+ const menu = await this.menu(); // 获取菜单数据
if (!accessToken) {
console.error('Failed to get access token');
@@ -54,35 +138,6 @@ export class PluginReplacePageServer extends Plugin {
}
const url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${accessToken}`;
- const menu = {
- button: [
- {
- type: 'view',
- name: '音乐',
- url: 'https://music.163.com/', // 确保这个 URL 不超过 256 个字符
- },
- {
- type: 'view',
- name: '官网',
- url: 'https://daoyoucloud.com/',
- },
- {
- name: '服务',
- sub_button: [
- {
- type: 'view',
- name: '正在开发中...',
- url: 'https://daoyoucloud.com/coming-soon',
- },
- {
- type: 'view',
- name: '开发者管理',
- url: 'https://lu.dev.daoyoucloud.com/',
- },
- ],
- },
- ],
- };
// 若通过accessToken验证,则发送Wechat请求,创建菜单
try {
@@ -93,7 +148,7 @@ export class PluginReplacePageServer extends Plugin {
}
}
- // Wechat从用户这里获取签名, 若通过则允许后续交互
+ // Wechat从用户这里获取签名,若通过则允许后续交互
async get(ctx) {
const { query } = ctx.request;
if (await this.checkSignature(query)) {
@@ -114,10 +169,9 @@ export class PluginReplacePageServer extends Plugin {
}
const messages = await this.message(); // 调用当前对象的 message方法
- // const keywordsList = keywords.map(keyword => keyword.keywords).join(`${keywords}`);
- const messagesList = messages.map((message, index) => `${index + 1}、 ${message.key}`).join('\n');
+ const messagesList = messages.map((message) => `${message.id}、 ${message.key}`).join('\n');
- let replyContent = `您好,欢迎关注道有云网络科技有限公司!您可以输入下面关键字来获取您要了解的信息!\n(输入序号即可):\n${messagesList}`; // 默认回复内容
+ let replyContent = `您好,欢迎关注道有云网络科技有限公司!您可以输入下面关键字来获取您要了解的信息!\n(输入序号或关键字):\n${messagesList}`;
// 解析 POST 请求的 XML 格式消息体
xml2js.parseString(body, { explicitArray: false }, (err, result) => {
@@ -131,14 +185,28 @@ export class PluginReplacePageServer extends Plugin {
const msg = result.xml;
let replyMessage; // 处理用户输入后的回复结果
- if (msg.MsgType === 'text') {
+ if (msg.MsgType === 'event' && msg.Event === 'SCAN') {
+ // 处理扫码事件
+ replyContent = '扫码成功,欢迎您!';
+ replyMessage = `
+
+
+
+ ${Math.floor(Date.now() / 1000)}
+
+
+
+ `;
+ } else if (msg.MsgType === 'text') {
// 处理文本信息
+ const valueById = messages.find((message) => msg.Content.includes(message.id.toString()));
+ const valueByKey = messages.find((message) => msg.Content.includes(message.key));
- // 匹配用户输入的关键字和message表里的messages字段是否一致,一致则 filter()出一个数组
- const values = messages.filter((message) => msg.Content.includes(message.id));
- if (values?.length > 0) {
- // 问号? 异常处理
- replyContent = values[0].value;
+ // 异常处理
+ if (valueById) {
+ replyContent = valueById.value;
+ } else if (valueByKey) {
+ replyContent = valueByKey.value;
}
replyMessage = `
@@ -180,36 +248,213 @@ export class PluginReplacePageServer extends Plugin {
});
}
- // 加载
+ // 处理OAuth回调
+ async handleOAuthCallback(ctx) {
+ const { code } = ctx.query;
+ console.log('code---:', code);
+
+ if (!code) {
+ ctx.status = 400;
+ ctx.body = 'Authorization code not found';
+ return;
+ }
+
+ try {
+ const { appid, appsecret } = await this.config();
+ console.log('appid---:', appid);
+ console.log('appsecret---:', appsecret);
+ // 获取 access_token 和 openid
+ const tokenUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${appsecret}&code=${code}&grant_type=authorization_code`;
+
+ const tokenResponse = await axios.get(tokenUrl);
+ const { access_token, openid } = tokenResponse.data;
+ console.log('access_token---:', access_token, '\n openid---:', openid);
+
+ if (!access_token || !openid) {
+ throw new Error('Failed to get access token or openid');
+ }
+
+ const userInfoUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`;
+ // 通过tokenUrl 获取用户message
+ const userInfoResponse = await axios.get(userInfoUrl);
+
+ // 用户信息
+ const userInfo = userInfoResponse.data;
+ console.log('用户信息:', userInfo);
+
+ // 检查数据库中是否存在该用户
+ const userRepo = this.app.db.getRepository('users');
+ const existingUser = await userRepo.findOne({
+ filter: {
+ username: openid,
+ },
+ });
+
+ if (!existingUser) {
+ // 如果用户不存在,则插入新用户
+ await userRepo.create({
+ values: {
+ username: openid.substring(0, 6),
+ password: openid,
+ nickname: userInfo.nickname,
+ },
+ });
+ console.log('新用户已创建:', userInfo.nickname);
+ } else {
+ // 如果用户存在,直接登录
+ console.log('用户已存在:', existingUser.nickname);
+ }
+
+ // 这里可以设置登录态或返回登录成功的信息
+ ctx.body = '完成注册,登录成功!';
+ // 返回手机端页面
+ // ctx.redirect('https://lu.dev.daoyoucloud.com/admin/4tr7gnzlwni');
+ } catch (error) {
+ console.error('Error handling OAuth callback:', error);
+ ctx.status = 500;
+ ctx.body = 'Server Error';
+ // ctx.body = '您已存在账户,登录成功!';
+ }
+ }
+
+ // load() 生命周期函数,在插件加载时执行
async load() {
- // load() 生命周期函数,在插件加载时执行
+ // 监听 message 表的 afterCreate 事件
+ this.app.db.on('message.afterCreate', this.handleMessageCreate.bind(this));
+ // 监听 menu 表的 afterUpdate 事件
+ this.app.db.on('menu.afterUpdate', this.handleMenuUpdate.bind(this));
+
+ // 注册认证(前端显示按钮)
+ this.app.authManager.registerTypes(authType, {
+ auth: BasicAuth,
+ });
+
// 定义资源动作
this.app.resourcer.define({
name: 'wechat',
actions: {
+ // 测试接口
test: async (ctx) => {
ctx.withoutDataWrapping = true;
ctx.body = 'Hello World!';
console.log('Hello World!');
},
+ // 创建菜单接口
createMenu: async (ctx) => {
await this.createCustomMenu();
console.log('Custom menu created successfully.');
ctx.body = 'success';
},
+ // Wechat 消息处理接口/服务器URL配置接口
handler: async (ctx) => {
ctx.withoutDataWrapping = true;
if (ctx.request.method.toLowerCase() === 'get') {
- //toLowerCase()转换字符串为小写
await this.get(ctx);
} else {
await this.post(ctx);
}
},
+ // 生成二维码接口
+ generateQrCode: async (ctx) => {
+ const qrCodeUrl = await this.generateQrCode();
+ if (qrCodeUrl) {
+ ctx.body = { data: { url: qrCodeUrl } }; // 确保返回的数据格式正确
+ console.log(`我是生成二维码接口的URL: ${qrCodeUrl}`);
+ } else {
+ ctx.status = 500;
+ ctx.body = 'Failed to generate QR code';
+ }
+ },
+ // 处理OAuth回调接口
+ handleOAuthCallback: async (ctx) => {
+ await this.handleOAuthCallback(ctx);
+ },
},
});
- this.app.acl.allow('wechat', '*', 'public'); // 实体名称,允许所有资源,公共级别访问
+ this.app.acl.allow('wechat', '*', 'public');
+ }
+
+ // 处理 message 表的 afterCreate 事件
+ async handleMessageCreate(model, options) {
+ const { id, key, value } = model.get();
+ console.log(`New message created: ${key} - ${value}`);
+
+ // 获取所有关注公众号的用户 ID
+ const userIds = await this.getAllFollowers();
+ console.log('Follower IDs:', userIds);
+
+ // 推送消息content
+ await this.pushMessageToFollowers(userIds, `目前系统提示已经更新,大家可以使用新的关键字 "${key}" 来获取信息!`);
+ }
+
+ // 处理 menu 表的 afterUpdate 事件
+ async handleMenuUpdate(model, options) {
+ const { id, name } = model.get();
+ console.log(`Menu item updated: ${name} (ID: ${id})`);
+
+ // 获取所有关注公众号的用户 ID
+ const userIds = await this.getAllFollowers();
+ console.log('Follower IDs:', userIds);
+
+ // 推送消息content
+ await this.pushMessageToFollowers(userIds, '用户你好,菜单项已经更新');
+ }
+
+ // 获取用户列表
+ async getAllFollowers() {
+ const accessToken = await this.getAccessToken();
+ if (!accessToken) {
+ console.error('Failed to get access token');
+ return [];
+ }
+
+ const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${accessToken}`;
+ try {
+ const response = await axios.get(url);
+ if (response.data.errcode) {
+ console.error(`Error getting followers: ${response.data.errmsg}`);
+ return [];
+ }
+ console.log('返回用户ID数组----');
+ return response.data.data.openid; // 返回关注者的OpenID数组
+ } catch (error) {
+ console.error('Error getting followers:', error);
+ return [];
+ }
+ }
+
+ // 给用户发送消息
+ async pushMessageToFollowers(userIds, message) {
+ const accessToken = await this.getAccessToken();
+ if (!accessToken) {
+ console.error('Failed to get access token');
+ return;
+ }
+
+ const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`;
+
+ for (const userId of userIds) {
+ const data = {
+ touser: userId,
+ msgtype: 'text',
+ text: {
+ content: message,
+ },
+ };
+
+ // 调用Wechat API对用户发送消息
+ try {
+ const response = await axios.post(url, data);
+ if (response.data.errcode) {
+ console.error(`Error pushing message to user ${userId}: ${response.data.errmsg}`);
+ } else {
+ console.log(`Message pushed to user ${userId} successfully.`);
+ }
+ } catch (error) {
+ console.error(`Error pushing message to user ${userId}:`, error);
+ }
+ }
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9effdc3e2..eed7aa34c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4045,6 +4045,9 @@ importers:
packages/plugins/@tachybase/plugin-wechat-official-account:
dependencies:
+ '@ant-design/icons':
+ specifier: ^5.3.7
+ version: 5.3.7(react-dom@18.3.1)(react@18.3.1)
'@tachybase/client':
specifier: workspace:*
version: link:../../../core/client
@@ -4063,12 +4066,27 @@ importers:
antd:
specifier: 5.19.1
version: 5.19.1(react-dom@18.3.1)(react@18.3.1)
+ axios:
+ specifier: ^1.7.2
+ version: 1.7.2
+ bcrypt:
+ specifier: ^5.1.1
+ version: 5.1.1
body-parser:
specifier: ^1.20.2
version: 1.20.2
+ crypto:
+ specifier: ^1.0.1
+ version: 1.0.1
+ jsonwebtoken:
+ specifier: ^8.5.1
+ version: 8.5.1
koa-bodyparser:
specifier: ^4.3.0
version: 4.4.1
+ qrcode.react:
+ specifier: ^3.1.0
+ version: 3.1.0(react@18.3.1)
react-router-dom:
specifier: ^6.11.2
version: 6.25.1(react-dom@18.3.1)(react@18.3.1)
@@ -14770,7 +14788,6 @@ packages:
/are-we-there-yet@2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
- deprecated: This package is no longer supported.
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
@@ -15303,6 +15320,18 @@ packages:
tweetnacl: 0.14.5
dev: true
+ /bcrypt@5.1.1:
+ resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
+ engines: {node: '>= 10.0.0'}
+ requiresBuild: true
+ dependencies:
+ '@mapbox/node-pre-gyp': 1.0.11
+ node-addon-api: 5.1.0
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
/bessel@1.0.2:
resolution: {integrity: sha512-Al3nHGQGqDYqqinXhQzmwmcRToe/3WyBv4N8aZc5Pef8xw2neZlR9VPi84Sa23JtgWcucu18HxVZrnI0fn2etw==}
engines: {node: '>=0.8'}
@@ -15795,7 +15824,7 @@ packages:
dev: false
/call-bind@1.0.7:
- resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+ resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==, tarball: https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz}
engines: {node: '>= 0.4'}
dependencies:
es-define-property: 1.0.0
@@ -16214,7 +16243,7 @@ packages:
engines: {node: '>=0.10.0'}
/co-body@6.1.0:
- resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==}
+ resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==, tarball: https://registry.npmmirror.com/co-body/-/co-body-6.1.0.tgz}
dependencies:
inflation: 2.1.0
qs: 6.12.3
@@ -16747,7 +16776,7 @@ packages:
toggle-selection: 1.0.6
/copy-to@2.0.1:
- resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==}
+ resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==, tarball: https://registry.npmmirror.com/copy-to/-/copy-to-2.0.1.tgz}
/core-js-compat@3.34.0:
resolution: {integrity: sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==}
@@ -16987,6 +17016,11 @@ packages:
engines: {node: '>=8'}
dev: false
+ /crypto@1.0.1:
+ resolution: {integrity: sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==}
+ deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.
+ dev: false
+
/css-blank-pseudo@3.0.3(postcss@8.4.39):
resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==}
engines: {node: ^12 || ^14 || >=16}
@@ -17743,7 +17777,7 @@ packages:
dev: false
/define-data-property@1.1.4:
- resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz}
engines: {node: '>= 0.4'}
dependencies:
es-define-property: 1.0.0
@@ -18356,13 +18390,13 @@ packages:
which-typed-array: 1.1.15
/es-define-property@1.0.0:
- resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+ resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==, tarball: https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.2.4
/es-errors@1.3.0:
- resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, tarball: https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz}
engines: {node: '>= 0.4'}
/es-get-iterator@1.1.3:
@@ -19762,7 +19796,7 @@ packages:
dev: true
/function-bind@1.1.2:
- resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, tarball: https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz}
/function.prototype.name@1.1.6:
resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
@@ -19779,7 +19813,6 @@ packages:
/gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
- deprecated: This package is no longer supported.
dependencies:
aproba: 2.0.0
color-support: 1.1.3
@@ -19829,7 +19862,7 @@ packages:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
/get-intrinsic@1.2.4:
- resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+ resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz}
engines: {node: '>= 0.4'}
dependencies:
es-errors: 1.3.0
@@ -20132,7 +20165,7 @@ packages:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
/gopd@1.0.1:
- resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+ resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==, tarball: https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz}
dependencies:
get-intrinsic: 1.2.4
@@ -20229,16 +20262,16 @@ packages:
engines: {node: '>=8'}
/has-property-descriptors@1.0.2:
- resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, tarball: https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz}
dependencies:
es-define-property: 1.0.0
/has-proto@1.0.3:
- resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+ resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==, tarball: https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz}
engines: {node: '>= 0.4'}
/has-symbols@1.0.3:
- resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==, tarball: https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz}
engines: {node: '>= 0.4'}
/has-tostringtag@1.0.2:
@@ -20288,7 +20321,7 @@ packages:
dev: false
/hasown@2.0.2:
- resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, tarball: https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz}
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
@@ -20688,7 +20721,7 @@ packages:
optional: true
/inflation@2.1.0:
- resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}
+ resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==, tarball: https://registry.npmmirror.com/inflation/-/inflation-2.1.0.tgz}
engines: {node: '>= 0.8.0'}
/inflection@1.13.4:
@@ -22672,7 +22705,7 @@ packages:
dev: true
/media-typer@0.3.0:
- resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==, tarball: https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz}
engines: {node: '>= 0.6'}
/mem@8.1.1:
@@ -23372,6 +23405,10 @@ packages:
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
dev: false
+ /node-addon-api@5.1.0:
+ resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
+ dev: false
+
/node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
@@ -23609,7 +23646,6 @@ packages:
/npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
- deprecated: This package is no longer supported.
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
@@ -23658,14 +23694,14 @@ packages:
engines: {node: '>= 6'}
/object-inspect@1.12.3:
- resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
+ resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz}
/object-inspect@1.13.1:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: false
/object-inspect@1.13.2:
- resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
+ resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.2.tgz}
engines: {node: '>= 0.4'}
/object-is@1.1.5:
@@ -25427,7 +25463,6 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.3.1
- dev: true
/qrcode@1.5.3:
resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
@@ -27564,7 +27599,7 @@ packages:
engines: {node: '>=10'}
/safer-buffer@2.1.2:
- resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, tarball: https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz}
/sanitize-html@2.10.0:
resolution: {integrity: sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==}
@@ -27831,7 +27866,7 @@ packages:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
/set-function-length@1.2.2:
- resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, tarball: https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz}
engines: {node: '>= 0.4'}
dependencies:
define-data-property: 1.1.4
@@ -27865,7 +27900,7 @@ packages:
resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
/setprototypeof@1.2.0:
- resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, tarball: https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz}
/sha.js@2.4.11:
resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==}
@@ -27914,7 +27949,7 @@ packages:
dev: false
/side-channel@1.0.6:
- resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+ resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==, tarball: https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.7
@@ -29210,7 +29245,7 @@ packages:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
/toidentifier@1.0.1:
- resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, tarball: https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz}
engines: {node: '>=0.6'}
/toposort-class@1.0.1:
@@ -29601,7 +29636,7 @@ packages:
dev: false
/type-is@1.6.18:
- resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==, tarball: https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz}
engines: {node: '>= 0.6'}
dependencies:
media-typer: 0.3.0