支持多端登录

This commit is contained in:
hanhh 2021-09-24 22:24:39 +08:00
parent 4c1551c309
commit f48b91dc0a
5 changed files with 86 additions and 17 deletions

View File

@ -117,7 +117,9 @@ export function createRandomString(min: number, max: number): string {
export function getToken(req: any) {
const { authorization } = req.headers;
if (authorization && authorization.split(' ')[0] === 'Bearer') {
return authorization.split(' ')[1];
return (authorization.split(' ')[1] as string)
.replace('mobile-', '')
.replace('desktop-', '');
}
return '';
}
@ -184,3 +186,36 @@ export async function getNetIp(req: any) {
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';
}

View File

@ -5,7 +5,7 @@ import routes from '../api';
import config from '../config';
import jwt from 'express-jwt';
import fs from 'fs';
import { getToken } from '../config/util';
import { getPlatform, getToken } from '../config/util';
import Container from 'typedi';
import OpenService from '../services/open';
import rewrite from 'express-urlrewrite';
@ -17,7 +17,10 @@ export default ({ app }: { app: Application }) => {
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(
jwt({ secret: config.secret as string, algorithms: ['HS384'] }).unless({
jwt({
secret: config.secret as string,
algorithms: ['HS384'],
}).unless({
path: [
'/api/login',
'/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) => {
const headerToken = getToken(req);
if (req.path.startsWith('/open/')) {
@ -66,8 +79,12 @@ export default ({ app }: { app: Application }) => {
const data = fs.readFileSync(config.authConfigFile, 'utf8');
if (data) {
const { token } = JSON.parse(data);
if (token && headerToken === token) {
const { token = '', tokens = {} } = JSON.parse(data);
console.log(tokens);
console.log(req.platform);
console.log(tokens[req.platform]);
if (headerToken === token || tokens[req.platform] === headerToken) {
console.log('yes');
return next();
}
}

View File

@ -1,6 +1,6 @@
import { Service, Inject } from 'typedi';
import winston from 'winston';
import { createRandomString, getNetIp } from '../config/util';
import { createRandomString, getNetIp, getPlatform } from '../config/util';
import config from '../config';
import * as fs from 'fs';
import _ from 'lodash';
@ -29,7 +29,7 @@ export default class AuthService {
username: string;
password: string;
},
req: any,
req: Request,
needTwoFactor = true,
): Promise<any> {
if (!fs.existsSync(config.authConfigFile)) {
@ -49,6 +49,7 @@ export default class AuthService {
lastaddr,
twoFactorActivated,
twoFactorActived,
tokens = {},
} = content;
// patch old field
twoFactorActivated = twoFactorActivated || twoFactorActived;
@ -94,6 +95,10 @@ export default class AuthService {
this.updateAuthInfo(content, {
token,
tokens: {
...tokens,
[req.platform]: token,
},
lastlogon: timestamp,
retries: 0,
lastip: ip,
@ -214,7 +219,14 @@ export default class AuthService {
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 { isTwoFactorChecking, twoFactorSecret } = authInfo;
if (!isTwoFactorChecking) {

View File

@ -167,7 +167,7 @@ run_concurrent() {
run_designated() {
local file_param="$1"
local env_param="$2"
local num_param="$3"
local num_param=$(echo "$3" | perl -pe "s|.*$2 (.*)|\1|")
if [[ ! $env_param ]] || [[ ! $num_param ]]; then
echo -e "\n 缺少单独运行的参数 task xxx.js single Test 1"
exit 1
@ -268,7 +268,7 @@ main() {
1)
run_normal "$1"
;;
2 | 3 | 4)
*)
case $2 in
now)
run_normal "$1" "$2"
@ -277,16 +277,13 @@ main() {
run_concurrent "$1" "$3"
;;
desi)
run_designated "$1" "$3" "$4"
run_designated "$1" "$3" "$*"
;;
*)
run_else "$@"
;;
esac
;;
*)
run_else "$@"
;;
esac
[[ -f $log_path ]] && cat $log_path
elif [[ $# -eq 0 ]]; then

14
typings.d.ts vendored
View File

@ -2,7 +2,15 @@ declare module '*.css';
declare module '*.less';
declare module '*.png';
declare module '*.svg' {
export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement
const url: string
export default url
export function ReactComponent(
props: React.SVGProps<SVGSVGElement>,
): React.ReactElement;
const url: string;
export default url;
}
declare namespace Express {
export interface Request {
platform: 'desktop' | 'mobile';
}
}