添加两步验证

This commit is contained in:
hanhh
2021-08-30 23:37:26 +08:00
parent 3a998a37f0
commit 86c3e9a843
11 changed files with 592 additions and 154 deletions
+80 -4
View File
@@ -80,10 +80,14 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {
if (err) console.log(err);
const authInfo = JSON.parse(data);
res.send({ code: 200, data: { username: authInfo.username } });
const authService = Container.get(AuthService);
const authInfo = await authService.getUserInfo();
res.send({
code: 200,
data: {
username: authInfo.username,
twoFactorActived: authInfo.twoFactorActived,
},
});
} catch (e) {
logger.error('🔥 error: %o', e);
@@ -91,4 +95,76 @@ export default (app: Router) => {
}
},
);
route.get(
'/user/two-factor/init',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const authService = Container.get(AuthService);
const data = await authService.initTwoFactor();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.put(
'/user/two-factor/active',
celebrate({
body: Joi.object({
code: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const authService = Container.get(AuthService);
const data = await authService.activeTwoFactor(req.body.code);
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/user/two-factor/deactive',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const authService = Container.get(AuthService);
const data = await authService.deactiveTwoFactor();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.put(
'/user/two-factor/login',
celebrate({
body: Joi.object({
code: Joi.string().required(),
username: Joi.string().required(),
password: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const authService = Container.get(AuthService);
const data = await authService.twoFactorLogin(req.body, req);
res.send(data);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
};
+9 -2
View File
@@ -18,7 +18,12 @@ export default ({ app }: { app: Application }) => {
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(
jwt({ secret: config.secret as string, algorithms: ['HS384'] }).unless({
path: ['/api/login', '/api/crons/status', /^\/open\//],
path: [
'/api/login',
'/api/crons/status',
/^\/open\//,
'/api/user/two-factor/login',
],
}),
);
@@ -52,7 +57,9 @@ export default ({ app }: { app: Application }) => {
if (
!headerToken &&
req.path &&
(req.path === '/api/login' || req.path === '/open/auth/token')
(req.path === '/api/login' ||
req.path === '/open/auth/token' ||
req.path === '/api/user/two-factor/login')
) {
return next();
}
+85 -1
View File
@@ -5,6 +5,7 @@ import config from '../config';
import * as fs from 'fs';
import _ from 'lodash';
import jwt from 'jsonwebtoken';
import { authenticator } from '@otplib/preset-default';
@Service()
export default class AuthService {
@@ -32,6 +33,8 @@ export default class AuthService {
lastlogon,
lastip,
lastaddr,
twoFactorActived,
twoFactorChecked,
} = JSON.parse(content);
if (
@@ -42,7 +45,21 @@ export default class AuthService {
return this.initAuthInfo();
}
if (twoFactorActived && !twoFactorChecked) {
return {
code: 420,
message: '请输入两步验证token',
};
}
if (retries > 2 && Date.now() - lastlogon < Math.pow(3, retries) * 1000) {
fs.writeFileSync(
config.authConfigFile,
JSON.stringify({
...JSON.parse(content),
twoFactorChecked: false,
}),
);
return {
code: 410,
message: `失败次数过多,请${Math.round(
@@ -57,8 +74,9 @@ export default class AuthService {
const { ip, address } = await getNetIp(req);
if (username === cUsername && password === cPassword) {
const data = createRandomString(50, 100);
const expiration = twoFactorActived ? 30 : 3;
let token = jwt.sign({ data }, config.secret as any, {
expiresIn: 60 * 60 * 24 * 3,
expiresIn: 60 * 60 * 24 * expiration,
algorithm: 'HS384',
});
fs.writeFileSync(
@@ -70,6 +88,7 @@ export default class AuthService {
retries: 0,
lastip: ip,
lastaddr: address,
twoFactorChecked: false,
}),
);
return { code: 200, data: { token, lastip, lastaddr, lastlogon } };
@@ -82,6 +101,7 @@ export default class AuthService {
lastlogon: timestamp,
lastip: ip,
lastaddr: address,
twoFactorChecked: false,
}),
);
return { code: 400, message: config.authError };
@@ -105,4 +125,68 @@ export default class AuthService {
message: '已初始化密码,请前往auth.json查看并重新登录',
};
}
public getUserInfo(): Promise<any> {
return new Promise((resolve) => {
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {
if (err) console.log(err);
resolve(JSON.parse(data));
});
});
}
public initTwoFactor() {
const secret = authenticator.generateSecret();
const authInfo = this.getAuthInfo();
const otpauth = authenticator.keyuri(authInfo.username, 'qinglong', secret);
this.updateAuthInfo(authInfo, { twoFactorSecret: secret });
return { secret, url: otpauth };
}
public activeTwoFactor(code: string) {
const authInfo = this.getAuthInfo();
const isValid = authenticator.verify({
token: code,
secret: authInfo.twoFactorSecret,
});
if (isValid) {
this.updateAuthInfo(authInfo, { twoFactorActived: true });
}
return isValid;
}
public twoFactorLogin({ username, password, code }, req) {
const authInfo = this.getAuthInfo();
const isValid = authenticator.verify({
token: code,
secret: authInfo.twoFactorSecret,
});
if (isValid) {
this.updateAuthInfo(authInfo, { twoFactorChecked: true });
return this.login({ username, password }, req);
} else {
return { code: 430, message: '验证失败' };
}
}
public deactiveTwoFactor() {
const authInfo = this.getAuthInfo();
this.updateAuthInfo(authInfo, {
twoFactorActived: false,
twoFactorSecret: '',
});
return true;
}
private getAuthInfo() {
const content = fs.readFileSync(config.authConfigFile, 'utf8');
return JSON.parse(content || '{}');
}
private updateAuthInfo(authInfo: any, info: any) {
fs.writeFileSync(
config.authConfigFile,
JSON.stringify({ ...authInfo, ...info }),
);
}
}