mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-23 23:06:06 +08:00
支持多端登录
This commit is contained in:
parent
4c1551c309
commit
f48b91dc0a
|
@ -117,7 +117,9 @@ export function createRandomString(min: number, max: number): string {
|
||||||
export function getToken(req: any) {
|
export function getToken(req: any) {
|
||||||
const { authorization } = req.headers;
|
const { authorization } = req.headers;
|
||||||
if (authorization && authorization.split(' ')[0] === 'Bearer') {
|
if (authorization && authorization.split(' ')[0] === 'Bearer') {
|
||||||
return authorization.split(' ')[1];
|
return (authorization.split(' ')[1] as string)
|
||||||
|
.replace('mobile-', '')
|
||||||
|
.replace('desktop-', '');
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -184,3 +186,36 @@ export async function getNetIp(req: any) {
|
||||||
return { address: `获取失败`, ip };
|
return { address: `获取失败`, ip };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPlatform(userAgent: string): 'mobile' | 'desktop' {
|
||||||
|
const ua = userAgent.toLowerCase();
|
||||||
|
const testUa = (regexp: RegExp) => regexp.test(ua);
|
||||||
|
const testVs = (regexp: RegExp) =>
|
||||||
|
(ua.match(regexp) || [])
|
||||||
|
.toString()
|
||||||
|
.replace(/[^0-9|_.]/g, '')
|
||||||
|
.replace(/_/g, '.');
|
||||||
|
|
||||||
|
// 系统
|
||||||
|
let system = 'unknow';
|
||||||
|
if (testUa(/windows|win32|win64|wow32|wow64/g)) {
|
||||||
|
system = 'windows'; // windows系统
|
||||||
|
} else if (testUa(/macintosh|macintel/g)) {
|
||||||
|
system = 'macos'; // macos系统
|
||||||
|
} else if (testUa(/x11/g)) {
|
||||||
|
system = 'linux'; // linux系统
|
||||||
|
} else if (testUa(/android|adr/g)) {
|
||||||
|
system = 'android'; // android系统
|
||||||
|
} else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
|
||||||
|
system = 'ios'; // ios系统
|
||||||
|
}
|
||||||
|
|
||||||
|
let platform = 'desktop';
|
||||||
|
if (system === 'windows' || system === 'macos' || system === 'linux') {
|
||||||
|
platform = 'desktop';
|
||||||
|
} else if (system === 'android' || system === 'ios' || testUa(/mobile/g)) {
|
||||||
|
platform = 'mobile';
|
||||||
|
}
|
||||||
|
|
||||||
|
return platform as 'mobile' | 'desktop';
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import routes from '../api';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import jwt from 'express-jwt';
|
import jwt from 'express-jwt';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { getToken } from '../config/util';
|
import { getPlatform, getToken } from '../config/util';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import OpenService from '../services/open';
|
import OpenService from '../services/open';
|
||||||
import rewrite from 'express-urlrewrite';
|
import rewrite from 'express-urlrewrite';
|
||||||
|
@ -17,7 +17,10 @@ export default ({ app }: { app: Application }) => {
|
||||||
app.use(bodyParser.json({ limit: '50mb' }));
|
app.use(bodyParser.json({ limit: '50mb' }));
|
||||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||||
app.use(
|
app.use(
|
||||||
jwt({ secret: config.secret as string, algorithms: ['HS384'] }).unless({
|
jwt({
|
||||||
|
secret: config.secret as string,
|
||||||
|
algorithms: ['HS384'],
|
||||||
|
}).unless({
|
||||||
path: [
|
path: [
|
||||||
'/api/login',
|
'/api/login',
|
||||||
'/api/crons/status',
|
'/api/crons/status',
|
||||||
|
@ -27,6 +30,16 @@ export default ({ app }: { app: Application }) => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.use((req: Request, res, next) => {
|
||||||
|
if (!req.headers) {
|
||||||
|
req.platform = 'desktop';
|
||||||
|
} else {
|
||||||
|
const platform = getPlatform(req.headers['user-agent'] || '');
|
||||||
|
req.platform = platform;
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
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/')) {
|
||||||
|
@ -66,8 +79,12 @@ export default ({ app }: { app: Application }) => {
|
||||||
|
|
||||||
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
const data = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||||
if (data) {
|
if (data) {
|
||||||
const { token } = JSON.parse(data);
|
const { token = '', tokens = {} } = JSON.parse(data);
|
||||||
if (token && headerToken === token) {
|
console.log(tokens);
|
||||||
|
console.log(req.platform);
|
||||||
|
console.log(tokens[req.platform]);
|
||||||
|
if (headerToken === token || tokens[req.platform] === headerToken) {
|
||||||
|
console.log('yes');
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { createRandomString, getNetIp } from '../config/util';
|
import { createRandomString, getNetIp, getPlatform } from '../config/util';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
@ -29,7 +29,7 @@ export default class AuthService {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
},
|
},
|
||||||
req: any,
|
req: Request,
|
||||||
needTwoFactor = true,
|
needTwoFactor = true,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (!fs.existsSync(config.authConfigFile)) {
|
if (!fs.existsSync(config.authConfigFile)) {
|
||||||
|
@ -49,6 +49,7 @@ export default class AuthService {
|
||||||
lastaddr,
|
lastaddr,
|
||||||
twoFactorActivated,
|
twoFactorActivated,
|
||||||
twoFactorActived,
|
twoFactorActived,
|
||||||
|
tokens = {},
|
||||||
} = content;
|
} = content;
|
||||||
// patch old field
|
// patch old field
|
||||||
twoFactorActivated = twoFactorActivated || twoFactorActived;
|
twoFactorActivated = twoFactorActivated || twoFactorActived;
|
||||||
|
@ -94,6 +95,10 @@ export default class AuthService {
|
||||||
|
|
||||||
this.updateAuthInfo(content, {
|
this.updateAuthInfo(content, {
|
||||||
token,
|
token,
|
||||||
|
tokens: {
|
||||||
|
...tokens,
|
||||||
|
[req.platform]: token,
|
||||||
|
},
|
||||||
lastlogon: timestamp,
|
lastlogon: timestamp,
|
||||||
retries: 0,
|
retries: 0,
|
||||||
lastip: ip,
|
lastip: ip,
|
||||||
|
@ -214,7 +219,14 @@ export default class AuthService {
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async twoFactorLogin({ username, password, code }, req) {
|
public async twoFactorLogin(
|
||||||
|
{
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
code,
|
||||||
|
}: { username: string; password: string; code: string },
|
||||||
|
req: any,
|
||||||
|
) {
|
||||||
const authInfo = this.getAuthInfo();
|
const authInfo = this.getAuthInfo();
|
||||||
const { isTwoFactorChecking, twoFactorSecret } = authInfo;
|
const { isTwoFactorChecking, twoFactorSecret } = authInfo;
|
||||||
if (!isTwoFactorChecking) {
|
if (!isTwoFactorChecking) {
|
||||||
|
|
|
@ -167,7 +167,7 @@ run_concurrent() {
|
||||||
run_designated() {
|
run_designated() {
|
||||||
local file_param="$1"
|
local file_param="$1"
|
||||||
local env_param="$2"
|
local env_param="$2"
|
||||||
local num_param="$3"
|
local num_param=$(echo "$3" | perl -pe "s|.*$2 (.*)|\1|")
|
||||||
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
|
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
|
||||||
echo -e "\n 缺少单独运行的参数 task xxx.js single Test 1"
|
echo -e "\n 缺少单独运行的参数 task xxx.js single Test 1"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -268,7 +268,7 @@ main() {
|
||||||
1)
|
1)
|
||||||
run_normal "$1"
|
run_normal "$1"
|
||||||
;;
|
;;
|
||||||
2 | 3 | 4)
|
*)
|
||||||
case $2 in
|
case $2 in
|
||||||
now)
|
now)
|
||||||
run_normal "$1" "$2"
|
run_normal "$1" "$2"
|
||||||
|
@ -277,16 +277,13 @@ main() {
|
||||||
run_concurrent "$1" "$3"
|
run_concurrent "$1" "$3"
|
||||||
;;
|
;;
|
||||||
desi)
|
desi)
|
||||||
run_designated "$1" "$3" "$4"
|
run_designated "$1" "$3" "$*"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
run_else "$@"
|
run_else "$@"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
run_else "$@"
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
[[ -f $log_path ]] && cat $log_path
|
[[ -f $log_path ]] && cat $log_path
|
||||||
elif [[ $# -eq 0 ]]; then
|
elif [[ $# -eq 0 ]]; then
|
||||||
|
|
14
typings.d.ts
vendored
14
typings.d.ts
vendored
|
@ -2,7 +2,15 @@ declare module '*.css';
|
||||||
declare module '*.less';
|
declare module '*.less';
|
||||||
declare module '*.png';
|
declare module '*.png';
|
||||||
declare module '*.svg' {
|
declare module '*.svg' {
|
||||||
export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement
|
export function ReactComponent(
|
||||||
const url: string
|
props: React.SVGProps<SVGSVGElement>,
|
||||||
export default url
|
): React.ReactElement;
|
||||||
|
const url: string;
|
||||||
|
export default url;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace Express {
|
||||||
|
export interface Request {
|
||||||
|
platform: 'desktop' | 'mobile';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user