mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
修改认证信息存储方式,避免认证信息异常
This commit is contained in:
parent
75f91e1473
commit
678e3e2dc6
|
@ -2,22 +2,22 @@
|
||||||
**/*.svg
|
**/*.svg
|
||||||
**/*.ejs
|
**/*.ejs
|
||||||
**/*.html
|
**/*.html
|
||||||
.umi
|
/.umi
|
||||||
.umi-production
|
/.umi-production
|
||||||
.umi-test
|
/.umi-test
|
||||||
.history
|
/.history
|
||||||
.tmp
|
/.tmp
|
||||||
node_modules
|
/node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
static
|
/static
|
||||||
data
|
/data
|
||||||
DS_Store
|
DS_Store
|
||||||
src/.umi
|
/src/.umi
|
||||||
src/.umi-production
|
/src/.umi-production
|
||||||
src/.umi-test
|
/src/.umi-test
|
||||||
.env.local
|
.env.local
|
||||||
.env
|
.env
|
||||||
version.ts
|
version.ts
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default (app: Router) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
const authInfo = await userService.getUserInfo();
|
const authInfo = await userService.getAuthInfo();
|
||||||
const { version, changeLog, changeLogLink, publishTime } =
|
const { version, changeLog, changeLogLink, publishTime } =
|
||||||
await parseVersion(config.versionFile);
|
await parseVersion(config.versionFile);
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ export default (app: Router) => {
|
||||||
const logger: Logger = Container.get('logger');
|
const logger: Logger = Container.get('logger');
|
||||||
try {
|
try {
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
const authInfo = await userService.getUserInfo();
|
const authInfo = await userService.getAuthInfo();
|
||||||
res.send({
|
res.send({
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -27,6 +27,7 @@ export enum AuthDataType {
|
||||||
'notification' = 'notification',
|
'notification' = 'notification',
|
||||||
'removeLogFrequency' = 'removeLogFrequency',
|
'removeLogFrequency' = 'removeLogFrequency',
|
||||||
'systemConfig' = 'systemConfig',
|
'systemConfig' = 'systemConfig',
|
||||||
|
'authConfig' = 'authConfig',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemConfigInfo {
|
export interface SystemConfigInfo {
|
||||||
|
@ -46,11 +47,31 @@ export interface LoginLogInfo {
|
||||||
status?: LoginStatus;
|
status?: LoginStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AuthInfo {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
retries: number;
|
||||||
|
lastlogon: number;
|
||||||
|
lastip: string;
|
||||||
|
lastaddr: string;
|
||||||
|
platform: string;
|
||||||
|
isTwoFactorChecking: boolean;
|
||||||
|
token: string;
|
||||||
|
tokens: Record<string, string>;
|
||||||
|
twoFactorActivated: boolean;
|
||||||
|
twoFactorActived: boolean;
|
||||||
|
twoFactorSecret: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type SystemModelInfo = SystemConfigInfo &
|
export type SystemModelInfo = SystemConfigInfo &
|
||||||
Partial<NotificationInfo> &
|
Partial<NotificationInfo> &
|
||||||
LoginLogInfo;
|
LoginLogInfo &
|
||||||
|
Partial<AuthInfo>;
|
||||||
|
|
||||||
export interface SystemInstance extends Model<SystemInfo, SystemInfo>, SystemInfo { }
|
export interface SystemInstance
|
||||||
|
extends Model<SystemInfo, SystemInfo>,
|
||||||
|
SystemInfo {}
|
||||||
export const SystemModel = sequelize.define<SystemInstance>('Auth', {
|
export const SystemModel = sequelize.define<SystemInstance>('Auth', {
|
||||||
ip: DataTypes.STRING,
|
ip: DataTypes.STRING,
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
|
|
|
@ -4,18 +4,14 @@ import cors from 'cors';
|
||||||
import routes from '../api';
|
import routes from '../api';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { UnauthorizedError, expressjwt } from 'express-jwt';
|
import { UnauthorizedError, expressjwt } from 'express-jwt';
|
||||||
import fs from 'fs/promises';
|
import { getPlatform, getToken } from '../config/util';
|
||||||
import { getPlatform, getToken, safeJSONParse } from '../config/util';
|
|
||||||
import Container from 'typedi';
|
|
||||||
import OpenService from '../services/open';
|
|
||||||
import rewrite from 'express-urlrewrite';
|
import rewrite from 'express-urlrewrite';
|
||||||
import UserService from '../services/user';
|
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { EnvModel } from '../data/env';
|
|
||||||
import { errors } from 'celebrate';
|
import { errors } from 'celebrate';
|
||||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
import { serveEnv } from '../config/serverEnv';
|
import { serveEnv } from '../config/serverEnv';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
|
import { IKeyvStore, shareStore } from '../shared/store';
|
||||||
|
|
||||||
export default ({ app }: { app: Application }) => {
|
export default ({ app }: { app: Application }) => {
|
||||||
app.set('trust proxy', 'loopback');
|
app.set('trust proxy', 'loopback');
|
||||||
|
@ -58,8 +54,10 @@ export default ({ app }: { app: Application }) => {
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
const headerToken = getToken(req);
|
const headerToken = getToken(req);
|
||||||
if (req.path.startsWith('/open/')) {
|
if (req.path.startsWith('/open/')) {
|
||||||
const openService = Container.get(OpenService);
|
const apps = await shareStore.getApps();
|
||||||
const doc = await openService.findTokenByValue(headerToken);
|
const doc = apps?.filter((x) =>
|
||||||
|
x.tokens?.find((y) => y.value === headerToken),
|
||||||
|
)?.[0];
|
||||||
if (doc && doc.tokens && doc.tokens.length > 0) {
|
if (doc && doc.tokens && doc.tokens.length > 0) {
|
||||||
const currentToken = doc.tokens.find((x) => x.value === headerToken);
|
const currentToken = doc.tokens.find((x) => x.value === headerToken);
|
||||||
const keyMatch = req.path.match(/\/open\/([a-z]+)\/*/);
|
const keyMatch = req.path.match(/\/open\/([a-z]+)\/*/);
|
||||||
|
@ -83,9 +81,9 @@ export default ({ app }: { app: Application }) => {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fs.readFile(config.authConfigFile, 'utf8');
|
const authInfo = await shareStore.getAuthInfo();
|
||||||
if (data && headerToken) {
|
if (authInfo && headerToken) {
|
||||||
const { token = '', tokens = {} } = safeJSONParse(data);
|
const { token = '', tokens = {} } = authInfo;
|
||||||
if (headerToken === token || tokens[req.platform] === headerToken) {
|
if (headerToken === token || tokens[req.platform] === headerToken) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -103,8 +101,8 @@ export default ({ app }: { app: Application }) => {
|
||||||
if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
|
if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
const userService = Container.get(UserService);
|
const authInfo =
|
||||||
const authInfo = await userService.getUserInfo();
|
(await shareStore.getAuthInfo()) || ({} as IKeyvStore['authInfo']);
|
||||||
|
|
||||||
let isInitialized = true;
|
let isInitialized = true;
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -12,7 +12,10 @@ import { initPosition } from '../data/env';
|
||||||
import { AuthDataType, SystemModel } from '../data/system';
|
import { AuthDataType, SystemModel } from '../data/system';
|
||||||
import SystemService from '../services/system';
|
import SystemService from '../services/system';
|
||||||
import UserService from '../services/user';
|
import UserService from '../services/user';
|
||||||
import { writeFile } from 'fs/promises';
|
import { writeFile, readFile } from 'fs/promises';
|
||||||
|
import { safeJSONParse } from '../config/util';
|
||||||
|
import OpenService from '../services/open';
|
||||||
|
import { shareStore } from '../shared/store';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
|
@ -20,10 +23,38 @@ export default async () => {
|
||||||
const dependenceService = Container.get(DependenceService);
|
const dependenceService = Container.get(DependenceService);
|
||||||
const systemService = Container.get(SystemService);
|
const systemService = Container.get(SystemService);
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
|
const openService = Container.get(OpenService);
|
||||||
|
|
||||||
// 初始化增加系统配置
|
// 初始化增加系统配置
|
||||||
await SystemModel.upsert({ type: AuthDataType.systemConfig });
|
await SystemModel.upsert({ type: AuthDataType.systemConfig });
|
||||||
await SystemModel.upsert({ type: AuthDataType.notification });
|
await SystemModel.upsert({ type: AuthDataType.notification });
|
||||||
|
await SystemModel.upsert({ type: AuthDataType.authConfig });
|
||||||
|
const authConfig = await SystemModel.findOne({
|
||||||
|
where: { type: AuthDataType.authConfig },
|
||||||
|
});
|
||||||
|
if (!authConfig?.info) {
|
||||||
|
let authInfo = {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const content = await readFile(config.authConfigFile, 'utf8');
|
||||||
|
authInfo = safeJSONParse(content);
|
||||||
|
} catch (error) {}
|
||||||
|
if (authConfig?.id) {
|
||||||
|
await SystemModel.update(
|
||||||
|
{ info: authInfo },
|
||||||
|
{
|
||||||
|
where: { id: authConfig.id },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await SystemModel.create({
|
||||||
|
info: authInfo,
|
||||||
|
type: AuthDataType.authConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化通知配置
|
// 初始化通知配置
|
||||||
const notifyConfig = await userService.getNotificationMode();
|
const notifyConfig = await userService.getNotificationMode();
|
||||||
|
@ -169,4 +200,11 @@ export default async () => {
|
||||||
// 初始化保存一次ck和定时任务数据
|
// 初始化保存一次ck和定时任务数据
|
||||||
await cronService.autosave_crontab();
|
await cronService.autosave_crontab();
|
||||||
await envService.set_envs();
|
await envService.set_envs();
|
||||||
|
|
||||||
|
const authInfo = await userService.getAuthInfo();
|
||||||
|
const apps = await openService.findApps();
|
||||||
|
await shareStore.updateAuthInfo(authInfo);
|
||||||
|
if (apps?.length) {
|
||||||
|
await shareStore.updateApps(apps);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,9 +20,7 @@ const bakPath = path.join(dataPath, 'bak/');
|
||||||
const samplePath = path.join(rootPath, 'sample/');
|
const samplePath = path.join(rootPath, 'sample/');
|
||||||
const tmpPath = path.join(logPath, '.tmp/');
|
const tmpPath = path.join(logPath, '.tmp/');
|
||||||
const confFile = path.join(configPath, 'config.sh');
|
const confFile = path.join(configPath, 'config.sh');
|
||||||
const authConfigFile = path.join(configPath, 'auth.json');
|
|
||||||
const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
|
const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
|
||||||
const sampleAuthFile = path.join(samplePath, 'auth.sample.json');
|
|
||||||
const sampleTaskShellFile = path.join(samplePath, 'task.sample.sh');
|
const sampleTaskShellFile = path.join(samplePath, 'task.sample.sh');
|
||||||
const sampleNotifyJsFile = path.join(samplePath, 'notify.js');
|
const sampleNotifyJsFile = path.join(samplePath, 'notify.js');
|
||||||
const sampleNotifyPyFile = path.join(samplePath, 'notify.py');
|
const sampleNotifyPyFile = path.join(samplePath, 'notify.py');
|
||||||
|
@ -40,7 +38,6 @@ const sshdPath = path.join(dataPath, 'ssh.d');
|
||||||
const systemLogPath = path.join(dataPath, 'syslog');
|
const systemLogPath = path.join(dataPath, 'syslog');
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const authFileExist = await fileExist(authConfigFile);
|
|
||||||
const confFileExist = await fileExist(confFile);
|
const confFileExist = await fileExist(confFile);
|
||||||
const scriptDirExist = await fileExist(scriptPath);
|
const scriptDirExist = await fileExist(scriptPath);
|
||||||
const preloadDirExist = await fileExist(preloadPath);
|
const preloadDirExist = await fileExist(preloadPath);
|
||||||
|
@ -100,9 +97,6 @@ export default async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化文件
|
// 初始化文件
|
||||||
if (!authFileExist) {
|
|
||||||
await fs.writeFile(authConfigFile, await fs.readFile(sampleAuthFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confFileExist) {
|
if (!confFileExist) {
|
||||||
await fs.writeFile(confFile, await fs.readFile(sampleConfigFile));
|
await fs.writeFile(confFile, await fs.readFile(sampleConfigFile));
|
||||||
|
|
|
@ -2,9 +2,8 @@ import sockJs from 'sockjs';
|
||||||
import { Server } from 'http';
|
import { Server } from 'http';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import SockService from '../services/sock';
|
import SockService from '../services/sock';
|
||||||
import config from '../config/index';
|
import { getPlatform } from '../config/util';
|
||||||
import fs from 'fs/promises';
|
import { shareStore } from '../shared/store';
|
||||||
import { getPlatform, safeJSONParse } from '../config/util';
|
|
||||||
|
|
||||||
export default async ({ server }: { server: Server }) => {
|
export default async ({ server }: { server: Server }) => {
|
||||||
const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} });
|
const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} });
|
||||||
|
@ -15,11 +14,11 @@ export default async ({ server }: { server: Server }) => {
|
||||||
conn.close('404');
|
conn.close('404');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fs.readFile(config.authConfigFile, 'utf8');
|
const authInfo = await shareStore.getAuthInfo();
|
||||||
const platform = getPlatform(conn.headers['user-agent'] || '') || 'desktop';
|
const platform = getPlatform(conn.headers['user-agent'] || '') || 'desktop';
|
||||||
const headerToken = conn.url.replace(`${conn.pathname}?token=`, '');
|
const headerToken = conn.url.replace(`${conn.pathname}?token=`, '');
|
||||||
if (data) {
|
if (authInfo) {
|
||||||
const { token = '', tokens = {} } = safeJSONParse(data);
|
const { token = '', tokens = {} } = authInfo;
|
||||||
if (headerToken === token || tokens[platform] === headerToken) {
|
if (headerToken === token || tokens[platform] === headerToken) {
|
||||||
sockService.addClient(conn);
|
sockService.addClient(conn);
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { createRandomString } from '../config/util';
|
import { createRandomString } from '../config/util';
|
||||||
import config from '../config';
|
|
||||||
import { App, AppModel } from '../data/open';
|
import { App, AppModel } from '../data/open';
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
import sequelize, { Op } from 'sequelize';
|
import sequelize, { Op } from 'sequelize';
|
||||||
|
import { shareStore } from '../shared/store';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class OpenService {
|
export default class OpenService {
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
public async findTokenByValue(token: string): Promise<App | null> {
|
public async findApps(): Promise<App[] | null> {
|
||||||
const docs = await this.find({});
|
const docs = await this.find({});
|
||||||
const doc = docs.filter((x) => x.tokens?.find((y) => y.value === token));
|
return docs;
|
||||||
return doc[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create(payload: App): Promise<App> {
|
public async create(payload: App): Promise<App> {
|
||||||
|
@ -34,17 +33,19 @@ export default class OpenService {
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
scopes: payload.scopes,
|
scopes: payload.scopes,
|
||||||
id: payload.id,
|
id: payload.id,
|
||||||
} as any);
|
} as App);
|
||||||
return { ...newDoc, tokens: [] };
|
return { ...newDoc, tokens: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateDb(payload: App): Promise<App> {
|
private async updateDb(payload: Partial<App>): Promise<App> {
|
||||||
await AppModel.update(payload, { where: { id: payload.id } });
|
await AppModel.update(payload, { where: { id: payload.id } });
|
||||||
return await this.getDb({ id: payload.id });
|
const apps = await this.find({});
|
||||||
|
await shareStore.updateApps(apps);
|
||||||
|
return apps?.find((x) => x.id === payload.id) as App;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDb(query: any): Promise<App> {
|
public async getDb(query: Record<string, any>): Promise<App> {
|
||||||
const doc: any = await AppModel.findOne({ where: query });
|
const doc = await AppModel.findOne({ where: query });
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
throw new Error(`App ${JSON.stringify(query)} not found`);
|
throw new Error(`App ${JSON.stringify(query)} not found`);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +57,7 @@ export default class OpenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resetSecret(id: number): Promise<App> {
|
public async resetSecret(id: number): Promise<App> {
|
||||||
const tab: any = {
|
const tab: Partial<App> = {
|
||||||
client_secret: createRandomString(24, 24),
|
client_secret: createRandomString(24, 24),
|
||||||
tokens: [],
|
tokens: [],
|
||||||
id,
|
id,
|
||||||
|
@ -74,7 +75,7 @@ export default class OpenService {
|
||||||
public async list(
|
public async list(
|
||||||
searchText: string = '',
|
searchText: string = '',
|
||||||
sort: any = {},
|
sort: any = {},
|
||||||
query: any = {},
|
query: Record<string, any> = {},
|
||||||
): Promise<App[]> {
|
): Promise<App[]> {
|
||||||
let condition = { ...query };
|
let condition = { ...query };
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
|
@ -101,7 +102,7 @@ export default class OpenService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async find(query: any, sort?: any): Promise<App[]> {
|
private async find(query: Record<string, any>, sort?: any): Promise<App[]> {
|
||||||
const docs = await AppModel.findAll({ where: { ...query } });
|
const docs = await AppModel.findAll({ where: { ...query } });
|
||||||
return docs.map((x) => x.get({ plain: true }));
|
return docs.map((x) => x.get({ plain: true }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
SystemModel,
|
SystemModel,
|
||||||
SystemModelInfo,
|
SystemModelInfo,
|
||||||
LoginStatus,
|
LoginStatus,
|
||||||
|
AuthInfo,
|
||||||
} from '../data/system';
|
} from '../data/system';
|
||||||
import { NotificationInfo } from '../data/notify';
|
import { NotificationInfo } from '../data/notify';
|
||||||
import NotificationService from './notify';
|
import NotificationService from './notify';
|
||||||
|
@ -28,6 +29,7 @@ import dayjs from 'dayjs';
|
||||||
import IP2Region from 'ip2region';
|
import IP2Region from 'ip2region';
|
||||||
import requestIp from 'request-ip';
|
import requestIp from 'request-ip';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
|
import { shareStore } from '../shared/store';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
|
@ -48,161 +50,142 @@ export default class UserService {
|
||||||
req: Request,
|
req: Request,
|
||||||
needTwoFactor = true,
|
needTwoFactor = true,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const _exist = await fileExist(config.authConfigFile);
|
|
||||||
if (!_exist) {
|
|
||||||
return this.initAuthInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
let { username, password } = payloads;
|
let { username, password } = payloads;
|
||||||
const content = await this.getAuthInfo();
|
const content = await this.getAuthInfo();
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
if (content) {
|
let {
|
||||||
let {
|
username: cUsername,
|
||||||
username: cUsername,
|
password: cPassword,
|
||||||
password: cPassword,
|
retries = 0,
|
||||||
retries = 0,
|
lastlogon,
|
||||||
lastlogon,
|
lastip,
|
||||||
lastip,
|
lastaddr,
|
||||||
lastaddr,
|
twoFactorActivated,
|
||||||
twoFactorActivated,
|
twoFactorActived,
|
||||||
twoFactorActived,
|
tokens = {},
|
||||||
tokens = {},
|
platform,
|
||||||
platform,
|
} = content;
|
||||||
} = content;
|
// patch old field
|
||||||
// patch old field
|
twoFactorActivated = twoFactorActivated || twoFactorActived;
|
||||||
twoFactorActivated = twoFactorActivated || twoFactorActived;
|
|
||||||
|
|
||||||
if (
|
const retriesTime = Math.pow(3, retries) * 1000;
|
||||||
(cUsername === 'admin' && cPassword === 'admin') ||
|
if (retries > 2 && timestamp - lastlogon < retriesTime) {
|
||||||
!cUsername ||
|
const waitTime = Math.ceil(
|
||||||
!cPassword
|
(retriesTime - (timestamp - lastlogon)) / 1000,
|
||||||
) {
|
);
|
||||||
return this.initAuthInfo();
|
return {
|
||||||
}
|
code: 410,
|
||||||
|
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||||
|
data: waitTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const retriesTime = Math.pow(3, retries) * 1000;
|
if (
|
||||||
if (retries > 2 && timestamp - lastlogon < retriesTime) {
|
username === cUsername &&
|
||||||
const waitTime = Math.ceil(
|
password === cPassword &&
|
||||||
(retriesTime - (timestamp - lastlogon)) / 1000,
|
twoFactorActivated &&
|
||||||
);
|
needTwoFactor
|
||||||
|
) {
|
||||||
|
await this.updateAuthInfo(content, {
|
||||||
|
isTwoFactorChecking: true,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 420,
|
||||||
|
message: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ip = requestIp.getClientIp(req) || '';
|
||||||
|
const query = new IP2Region();
|
||||||
|
const ipAddress = query.search(ip);
|
||||||
|
let address = '';
|
||||||
|
if (ipAddress) {
|
||||||
|
const { country, province, city, isp } = ipAddress;
|
||||||
|
address = uniq([country, province, city, isp]).filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
if (username === cUsername && password === cPassword) {
|
||||||
|
const data = createRandomString(50, 100);
|
||||||
|
const expiration = twoFactorActivated ? 60 : 20;
|
||||||
|
let token = jwt.sign({ data }, config.secret as any, {
|
||||||
|
expiresIn: 60 * 60 * 24 * expiration,
|
||||||
|
algorithm: 'HS384',
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.updateAuthInfo(content, {
|
||||||
|
token,
|
||||||
|
tokens: {
|
||||||
|
...tokens,
|
||||||
|
[req.platform]: token,
|
||||||
|
},
|
||||||
|
lastlogon: timestamp,
|
||||||
|
retries: 0,
|
||||||
|
lastip: ip,
|
||||||
|
lastaddr: address,
|
||||||
|
platform: req.platform,
|
||||||
|
isTwoFactorChecking: false,
|
||||||
|
});
|
||||||
|
this.notificationService.notify(
|
||||||
|
'登录通知',
|
||||||
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
|
req.platform
|
||||||
|
}端 登录成功,ip地址 ${ip}`,
|
||||||
|
);
|
||||||
|
await this.insertDb({
|
||||||
|
type: AuthDataType.loginLog,
|
||||||
|
info: {
|
||||||
|
timestamp,
|
||||||
|
address,
|
||||||
|
ip,
|
||||||
|
platform: req.platform,
|
||||||
|
status: LoginStatus.success,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.getLoginLog();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: { token, lastip, lastaddr, lastlogon, retries, platform },
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await this.updateAuthInfo(content, {
|
||||||
|
retries: retries + 1,
|
||||||
|
lastlogon: timestamp,
|
||||||
|
lastip: ip,
|
||||||
|
lastaddr: address,
|
||||||
|
platform: req.platform,
|
||||||
|
});
|
||||||
|
this.notificationService.notify(
|
||||||
|
'登录通知',
|
||||||
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
|
req.platform
|
||||||
|
}端 登录失败,ip地址 ${ip}`,
|
||||||
|
);
|
||||||
|
await this.insertDb({
|
||||||
|
type: AuthDataType.loginLog,
|
||||||
|
info: {
|
||||||
|
timestamp,
|
||||||
|
address,
|
||||||
|
ip,
|
||||||
|
platform: req.platform,
|
||||||
|
status: LoginStatus.fail,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.getLoginLog();
|
||||||
|
if (retries > 2) {
|
||||||
|
const waitTime = Math.round(Math.pow(3, retries + 1));
|
||||||
return {
|
return {
|
||||||
code: 410,
|
code: 410,
|
||||||
message: `失败次数过多,请${waitTime}秒后重试`,
|
message: `失败次数过多,请${waitTime}秒后重试`,
|
||||||
data: waitTime,
|
data: waitTime,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
username === cUsername &&
|
|
||||||
password === cPassword &&
|
|
||||||
twoFactorActivated &&
|
|
||||||
needTwoFactor
|
|
||||||
) {
|
|
||||||
this.updateAuthInfo(content, {
|
|
||||||
isTwoFactorChecking: true,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
code: 420,
|
|
||||||
message: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ip = requestIp.getClientIp(req) || '';
|
|
||||||
const query = new IP2Region();
|
|
||||||
const ipAddress = query.search(ip);
|
|
||||||
let address = '';
|
|
||||||
if (ipAddress) {
|
|
||||||
const { country, province, city, isp } = ipAddress;
|
|
||||||
address = uniq([country, province, city, isp])
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
if (username === cUsername && password === cPassword) {
|
|
||||||
const data = createRandomString(50, 100);
|
|
||||||
const expiration = twoFactorActivated ? 60 : 20;
|
|
||||||
let token = jwt.sign({ data }, config.secret as any, {
|
|
||||||
expiresIn: 60 * 60 * 24 * expiration,
|
|
||||||
algorithm: 'HS384',
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateAuthInfo(content, {
|
|
||||||
token,
|
|
||||||
tokens: {
|
|
||||||
...tokens,
|
|
||||||
[req.platform]: token,
|
|
||||||
},
|
|
||||||
lastlogon: timestamp,
|
|
||||||
retries: 0,
|
|
||||||
lastip: ip,
|
|
||||||
lastaddr: address,
|
|
||||||
platform: req.platform,
|
|
||||||
isTwoFactorChecking: false,
|
|
||||||
});
|
|
||||||
this.notificationService.notify(
|
|
||||||
'登录通知',
|
|
||||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
|
||||||
req.platform
|
|
||||||
}端 登录成功,ip地址 ${ip}`,
|
|
||||||
);
|
|
||||||
await this.insertDb({
|
|
||||||
type: AuthDataType.loginLog,
|
|
||||||
info: {
|
|
||||||
timestamp,
|
|
||||||
address,
|
|
||||||
ip,
|
|
||||||
platform: req.platform,
|
|
||||||
status: LoginStatus.success,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.getLoginLog();
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
data: { token, lastip, lastaddr, lastlogon, retries, platform },
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
this.updateAuthInfo(content, {
|
return { code: 400, message: config.authError };
|
||||||
retries: retries + 1,
|
|
||||||
lastlogon: timestamp,
|
|
||||||
lastip: ip,
|
|
||||||
lastaddr: address,
|
|
||||||
platform: req.platform,
|
|
||||||
});
|
|
||||||
this.notificationService.notify(
|
|
||||||
'登录通知',
|
|
||||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
|
||||||
req.platform
|
|
||||||
}端 登录失败,ip地址 ${ip}`,
|
|
||||||
);
|
|
||||||
await this.insertDb({
|
|
||||||
type: AuthDataType.loginLog,
|
|
||||||
info: {
|
|
||||||
timestamp,
|
|
||||||
address,
|
|
||||||
ip,
|
|
||||||
platform: req.platform,
|
|
||||||
status: LoginStatus.fail,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.getLoginLog();
|
|
||||||
if (retries > 2) {
|
|
||||||
const waitTime = Math.round(Math.pow(3, retries + 1));
|
|
||||||
return {
|
|
||||||
code: 410,
|
|
||||||
message: `失败次数过多,请${waitTime}秒后重试`,
|
|
||||||
data: waitTime,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return { code: 400, message: config.authError };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return this.initAuthInfo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async logout(platform: string): Promise<any> {
|
public async logout(platform: string): Promise<any> {
|
||||||
const authInfo = await this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, {
|
await this.updateAuthInfo(authInfo, {
|
||||||
token: '',
|
token: '',
|
||||||
tokens: { ...authInfo.tokens, [platform]: '' },
|
tokens: { ...authInfo.tokens, [platform]: '' },
|
||||||
});
|
});
|
||||||
|
@ -257,35 +240,21 @@ export default class UserService {
|
||||||
return { code: 400, message: '密码不能设置为admin' };
|
return { code: 400, message: '密码不能设置为admin' };
|
||||||
}
|
}
|
||||||
const authInfo = await this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, { username, password });
|
await this.updateAuthInfo(authInfo, { username, password });
|
||||||
return { code: 200, message: '更新成功' };
|
return { code: 200, message: '更新成功' };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAvatar(avatar: string) {
|
public async updateAvatar(avatar: string) {
|
||||||
const authInfo = await this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, { avatar });
|
await this.updateAuthInfo(authInfo, { avatar });
|
||||||
return { code: 200, data: avatar, message: '更新成功' };
|
return { code: 200, data: avatar, message: '更新成功' };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserInfo(): Promise<any> {
|
|
||||||
const authFileExist = await fileExist(config.authConfigFile);
|
|
||||||
if (!authFileExist) {
|
|
||||||
await createFile(
|
|
||||||
config.authConfigFile,
|
|
||||||
JSON.stringify({
|
|
||||||
username: 'admin',
|
|
||||||
password: 'admin',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return await this.getAuthInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async initTwoFactor() {
|
public async initTwoFactor() {
|
||||||
const secret = authenticator.generateSecret();
|
const secret = authenticator.generateSecret();
|
||||||
const authInfo = await this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
const otpauth = authenticator.keyuri(authInfo.username, 'qinglong', secret);
|
const otpauth = authenticator.keyuri(authInfo.username, 'qinglong', secret);
|
||||||
this.updateAuthInfo(authInfo, { twoFactorSecret: secret });
|
await this.updateAuthInfo(authInfo, { twoFactorSecret: secret });
|
||||||
return { secret, url: otpauth };
|
return { secret, url: otpauth };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +265,7 @@ export default class UserService {
|
||||||
secret: authInfo.twoFactorSecret,
|
secret: authInfo.twoFactorSecret,
|
||||||
});
|
});
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
this.updateAuthInfo(authInfo, { twoFactorActivated: true });
|
await this.updateAuthInfo(authInfo, { twoFactorActivated: true });
|
||||||
}
|
}
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
@ -322,7 +291,7 @@ export default class UserService {
|
||||||
return this.login({ username, password }, req, false);
|
return this.login({ username, password }, req, false);
|
||||||
} else {
|
} else {
|
||||||
const { ip, address } = await getNetIp(req);
|
const { ip, address } = await getNetIp(req);
|
||||||
this.updateAuthInfo(authInfo, {
|
await this.updateAuthInfo(authInfo, {
|
||||||
lastip: ip,
|
lastip: ip,
|
||||||
lastaddr: address,
|
lastaddr: address,
|
||||||
platform: req.platform,
|
platform: req.platform,
|
||||||
|
@ -333,7 +302,7 @@ export default class UserService {
|
||||||
|
|
||||||
public async deactiveTwoFactor() {
|
public async deactiveTwoFactor() {
|
||||||
const authInfo = await this.getAuthInfo();
|
const authInfo = await this.getAuthInfo();
|
||||||
this.updateAuthInfo(authInfo, {
|
await this.updateAuthInfo(authInfo, {
|
||||||
twoFactorActivated: false,
|
twoFactorActivated: false,
|
||||||
twoFactorActived: false,
|
twoFactorActived: false,
|
||||||
twoFactorSecret: '',
|
twoFactorSecret: '',
|
||||||
|
@ -341,16 +310,22 @@ export default class UserService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getAuthInfo() {
|
public async getAuthInfo() {
|
||||||
const content = await fs.readFile(config.authConfigFile, 'utf8');
|
const authInfo = await shareStore.getAuthInfo();
|
||||||
return safeJSONParse(content);
|
if (authInfo) {
|
||||||
|
return authInfo;
|
||||||
|
}
|
||||||
|
const doc = await this.getDb({ type: AuthDataType.authConfig });
|
||||||
|
return (doc.info || {}) as AuthInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAuthInfo(authInfo: any, info: any) {
|
private async updateAuthInfo(authInfo: any, info: any) {
|
||||||
await fs.writeFile(
|
const result = { ...authInfo, ...info };
|
||||||
config.authConfigFile,
|
await shareStore.updateAuthInfo(result);
|
||||||
JSON.stringify({ ...authInfo, ...info }),
|
await this.updateAuthDb({
|
||||||
);
|
type: AuthDataType.authConfig,
|
||||||
|
info: result,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNotificationMode(): Promise<NotificationInfo> {
|
public async getNotificationMode(): Promise<NotificationInfo> {
|
||||||
|
|
34
back/shared/store.ts
Normal file
34
back/shared/store.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { AuthInfo } from '../data/system';
|
||||||
|
import { App } from '../data/open';
|
||||||
|
import Keyv from 'keyv';
|
||||||
|
import KeyvSqlite from '@keyv/sqlite';
|
||||||
|
import config from '../config';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export enum EKeyv {
|
||||||
|
'apps' = 'apps',
|
||||||
|
'authInfo' = 'authInfo',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IKeyvStore {
|
||||||
|
apps: App[];
|
||||||
|
authInfo: AuthInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyvSqlite = new KeyvSqlite(path.join(config.dbPath, 'keyv.sqlite'));
|
||||||
|
export const keyvStore = new Keyv<IKeyvStore>({ store: keyvSqlite });
|
||||||
|
|
||||||
|
export const shareStore = {
|
||||||
|
getAuthInfo() {
|
||||||
|
return keyvStore.get<IKeyvStore['authInfo']>(EKeyv.authInfo);
|
||||||
|
},
|
||||||
|
updateAuthInfo(value: IKeyvStore['authInfo']) {
|
||||||
|
return keyvStore.set<IKeyvStore['authInfo']>(EKeyv.authInfo, value);
|
||||||
|
},
|
||||||
|
getApps() {
|
||||||
|
return keyvStore.get<IKeyvStore['apps']>(EKeyv.apps);
|
||||||
|
},
|
||||||
|
updateApps(apps: App[]) {
|
||||||
|
return keyvStore.set<IKeyvStore['apps']>(EKeyv.apps, apps);
|
||||||
|
},
|
||||||
|
};
|
|
@ -54,6 +54,9 @@
|
||||||
"react-dom": "18",
|
"react-dom": "18",
|
||||||
"dva-core": "2"
|
"dva-core": "2"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -98,7 +101,9 @@
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
"request-ip": "3.3.0",
|
"request-ip": "3.3.0",
|
||||||
"ip2region": "2.3.0"
|
"ip2region": "2.3.0",
|
||||||
|
"keyv": "^5.2.3",
|
||||||
|
"@keyv/sqlite": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
settings:
|
overrides:
|
||||||
autoInstallPeers: true
|
sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@grpc/grpc-js':
|
'@grpc/grpc-js':
|
||||||
specifier: ^1.12.3
|
specifier: ^1.12.3
|
||||||
version: 1.12.3
|
version: 1.12.3
|
||||||
|
'@keyv/sqlite':
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
'@otplib/preset-default':
|
'@otplib/preset-default':
|
||||||
specifier: ^12.0.1
|
specifier: ^12.0.1
|
||||||
version: 12.0.1
|
version: 12.0.1
|
||||||
|
@ -74,6 +76,9 @@ dependencies:
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.2
|
specifier: ^9.0.2
|
||||||
version: 9.0.2
|
version: 9.0.2
|
||||||
|
keyv:
|
||||||
|
specifier: ^5.2.3
|
||||||
|
version: 5.2.3
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
@ -2773,6 +2778,23 @@ packages:
|
||||||
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
|
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@keyv/serialize@1.0.2:
|
||||||
|
resolution: {integrity: sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==}
|
||||||
|
dependencies:
|
||||||
|
buffer: 6.0.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@keyv/sqlite@4.0.1:
|
||||||
|
resolution: {integrity: sha512-Ngs9jhElXN7efS9WvvCC/p6rXMbihna8eNLVBc421Zf+VcFd+pR4DOcS6yA9V22EKtAy4DEj7LtvEEIm0bb80A==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
dependencies:
|
||||||
|
sqlite3: github.com/whyour/node-sqlite3/3a00af0b5d7603b7f1b290032507320b18a6b741
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bluebird
|
||||||
|
- encoding
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@lezer/common@1.2.3:
|
/@lezer/common@1.2.3:
|
||||||
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -6377,7 +6399,6 @@ packages:
|
||||||
|
|
||||||
/base64-js@1.5.1:
|
/base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/before@0.0.1:
|
/before@0.0.1:
|
||||||
resolution: {integrity: sha512-1J5SWbkoVJH9DTALN8igB4p+nPKZzPrJ/HomqBDLpfUvDXCdjdBmBUcH5McZfur0lftVssVU6BZug5NYh87zTw==}
|
resolution: {integrity: sha512-1J5SWbkoVJH9DTALN8igB4p+nPKZzPrJ/HomqBDLpfUvDXCdjdBmBUcH5McZfur0lftVssVU6BZug5NYh87zTw==}
|
||||||
|
@ -6572,6 +6593,13 @@ packages:
|
||||||
isarray: 1.0.0
|
isarray: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/buffer@6.0.3:
|
||||||
|
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/builtin-status-codes@3.0.0:
|
/builtin-status-codes@3.0.0:
|
||||||
resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
|
resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -9406,7 +9434,6 @@ packages:
|
||||||
|
|
||||||
/ieee754@1.2.1:
|
/ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ignore-by-default@1.0.1:
|
/ignore-by-default@1.0.1:
|
||||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||||
|
@ -10161,6 +10188,12 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
|
/keyv@5.2.3:
|
||||||
|
resolution: {integrity: sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==}
|
||||||
|
dependencies:
|
||||||
|
'@keyv/serialize': 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/kind-of@6.0.3:
|
/kind-of@6.0.3:
|
||||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -16141,3 +16174,7 @@ packages:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
|
@ -193,12 +193,6 @@ fix_config() {
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -s $file_auth_user ]]; then
|
|
||||||
echo -e "复制一份 $file_auth_sample 为 $file_auth_user\n"
|
|
||||||
cp -fv $file_auth_sample $file_auth_user
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -s $file_notify_py ]]; then
|
if [[ ! -s $file_notify_py ]]; then
|
||||||
echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n"
|
echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n"
|
||||||
cp -fv $file_notify_py_sample $file_notify_py
|
cp -fv $file_notify_py_sample $file_notify_py
|
||||||
|
|
Loading…
Reference in New Issue
Block a user