Updating a bunch of stuff.

This version has partially support for TwoFactorAuthentication.
This commit is contained in:
Fabian Stamm
2019-08-06 17:06:22 +02:00
parent d98588d1c3
commit 11f460406b
22 changed files with 2224 additions and 1812 deletions

View File

@ -0,0 +1,74 @@
import { Router } from "express"
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, { TFATypes as TwoFATypes, IBackupCode } from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import * as crypto from "crypto";
import Logging from "@hibas123/nodelogging";
const BackupCodeRoute = Router();
// TODO: Further checks if this is good enough randomness
function generateCode(length: number) {
let bytes = crypto.randomBytes(length);
let nrs = "";
bytes.forEach((b, idx) => {
let nr = Math.floor((b / 255) * 9.9999)
if (nr > 9) nr = 9;
nrs += String(nr);
})
return nrs;
}
BackupCodeRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
//Generating new
let codes = Array(10).map(() => generateCode(8));
console.log(codes);
let twofactor = TwoFactor.new(<IBackupCode>{
user: req.user._id,
type: TwoFATypes.OTC,
valid: true,
data: codes,
name: ""
})
await TwoFactor.save(twofactor);
res.json({
codes,
id: twofactor._id
})
}));
BackupCodeRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
let { login, special } = req.token;
let { id, code }: { id: string, code: string } = req.body;
let twofactor: IBackupCode = await TwoFactor.findById(id);
if (!twofactor || !twofactor.valid || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.OTC) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
code = code.replace(/\s/g, "");
let valid = twofactor.data.find(c => c === code);
if (valid) {
twofactor.data = twofactor.data.filter(c => c !== code);
await TwoFactor.save(twofactor);
let [login_exp, special_exp] = await Promise.all([
upgradeToken(login),
upgradeToken(special)
]);
res.json({ success: true, login_exp, special_exp })
} else {
throw new RequestError("Invalid or already used code!", HttpStatusCode.BAD_REQUEST);
}
}))
export default BackupCodeRoute;

View File

@ -1,79 +1,13 @@
import LoginToken, { ILoginToken } from "../../../models/login_token";
import moment = require("moment");
// export async function unlockToken() {
// let { type, code, login, special } = req.body;
// let [login_t, special_t] = await Promise.all([LoginToken.findOne({ token: login }), LoginToken.findOne({ token: special })]);
// if ((login && !login_t) || (special && !special_t)) {
// res.json({ error: req.__("Token not found!") });
// } else {
// let atoken = special_t || login_t;
// let user = await User.findById(atoken.user);
// let tf = await TwoFactor.find({ user: user._id, valid: true })
// let valid = false;
// switch (type) {
// case TokenTypes.OTC: {
// let twofactor = await TwoFactor.findOne({ type, valid: true })
// if (twofactor) {
// valid = speakeasy.totp.verify({
// secret: twofactor.token,
// encoding: "base64",
// token: code
// })
// }
// break;
// }
// case TokenTypes.BACKUP_CODE: {
// let twofactor = await TwoFactor.findOne({ type, valid: true, token: code })
// if (twofactor) {
// twofactor.valid = false;
// await TwoFactor.save(twofactor);
// valid = true;
// }
// break;
// }
// case TokenTypes.APP_ALLOW:
// case TokenTypes.YUBI_KEY:
// default:
// res.json({ error: req.__("Invalid twofactor!") });
// return;
// }
// if (!valid) {
// res.json({ error: req.__("Invalid code!") });
// return;
// }
// let result: any = {};
// if (login_t) {
// login_t.validated = true
// await LoginToken.save(login_t)
// result.login = { token: login_t.token, expires: login_t.validTill.toUTCString() }
// }
// if (special_t) {
// special_t.validated = true;
// await LoginToken.save(special_t);
// result.special = { token: special_t.token, expires: special_t.validTill.toUTCString() }
// }
// res.json(result);
// }
// }
export async function upgradeToken(token: ILoginToken) {
token.data = undefined;
token.valid = true;
token.validated = true;
//TODO durations from config
let expires = (token.special ? moment().add(30, "minute") : moment().add(6, "months")).toDate();
token.validTill = expires;
await LoginToken.save(token);
return expires;
import LoginToken, { ILoginToken } from "../../../models/login_token";
import moment = require("moment");
export async function upgradeToken(token: ILoginToken) {
token.data = undefined;
token.valid = true;
token.validated = true;
//TODO durations from config
let expires = (token.special ? moment().add(30, "minute") : moment().add(6, "months")).toDate();
token.validTill = expires;
await LoginToken.save(token);
return expires;
}

View File

@ -1,42 +1,46 @@
import { Router } from "express";
import YubiKeyRoute from "./yubikey";
import { GetUserMiddleware } from "../../middlewares/user";
import Stacker from "../../middlewares/stacker";
import TwoFactor from "../../../models/twofactor";
import * as moment from "moment"
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
const TwoFactorRouter = Router();
TwoFactorRouter.get("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true })
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
await Promise.all(expired.map(e => {
e.valid = false;
return TwoFactor.save(e);
}));
twofactor = twofactor.filter(e => e.valid);
let tfa = twofactor.map(e => {
return {
id: e._id,
name: e.name,
type: e.type
}
})
res.json({ methods: tfa });
}));
TwoFactorRouter.delete("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
let { id } = req.query;
let tfa = await TwoFactor.findById(id);
if (!tfa || !tfa.user.equals(req.user._id)) {
throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST);
}
tfa.valid = false;
await TwoFactor.save(tfa);
res.json({ success: true });
}));
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
import { Router } from "express";
import YubiKeyRoute from "./yubikey";
import { GetUserMiddleware } from "../../middlewares/user";
import Stacker from "../../middlewares/stacker";
import TwoFactor from "../../../models/twofactor";
import * as moment from "moment"
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
import OTCRoute from "./otc";
import BackupCodeRoute from "./backup";
const TwoFactorRouter = Router();
TwoFactorRouter.get("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true })
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
await Promise.all(expired.map(e => {
e.valid = false;
return TwoFactor.save(e);
}));
twofactor = twofactor.filter(e => e.valid);
let tfa = twofactor.map(e => {
return {
id: e._id,
name: e.name,
type: e.type
}
})
res.json({ methods: tfa });
}));
TwoFactorRouter.delete("/:id", Stacker(GetUserMiddleware(true, true), async (req, res) => {
let { id } = req.params;
let tfa = await TwoFactor.findById(id);
if (!tfa || !tfa.user.equals(req.user._id)) {
throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST);
}
tfa.valid = false;
await TwoFactor.save(tfa);
res.json({ success: true });
}));
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
TwoFactorRouter.use("/otc", OTCRoute);
TwoFactorRouter.use("/backup", BackupCodeRoute);
export default TwoFactorRouter;

View File

@ -0,0 +1,105 @@
import { Router } from "express"
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, { TFATypes as TwoFATypes, IOTC } from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
import * as speakeasy from "speakeasy";
import * as qrcode from "qrcode";
import config from "../../../../config";
const OTCRoute = Router();
OTCRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "create") {
//Generating new
let secret = speakeasy.generateSecret({
name: config.core.name,
issuer: config.core.name
});
let twofactor = TwoFactor.new(<IOTC>{
user: req.user._id,
type: TwoFATypes.OTC,
valid: false,
data: secret.base32
})
let dataurl = await qrcode.toDataURL(secret.otpauth_url);
await TwoFactor.save(twofactor);
res.json({
image: dataurl,
id: twofactor._id
})
} else if (type === "validate") {
// Checking code and marking as valid
const { code, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IOTC = await TwoFactor.findById(id);
const err = () => { throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST) };
if (!twofactor || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.OTC || !twofactor.data || twofactor.valid) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
let valid = speakeasy.totp.verify({
secret: twofactor.data,
encoding: "base32",
token: code
})
if (valid) {
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError("Invalid Code!", HttpStatusCode.BAD_REQUEST);
}
} else {
throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST);
}
}));
OTCRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
let { login, special } = req.token;
let { id, code } = req.body;
let twofactor: IOTC = await TwoFactor.findById(id);
if (!twofactor || !twofactor.valid || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.OTC) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
let valid = speakeasy.totp.verify({
secret: twofactor.data,
encoding: "base32",
token: code
})
if (valid) {
let [login_exp, special_exp] = await Promise.all([
upgradeToken(login),
upgradeToken(special)
]);
res.json({ success: true, login_exp, special_exp })
} else {
throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST);
}
}))
export default OTCRoute;

View File

@ -1,131 +1,134 @@
import { Router, Request } from "express"
import Stacker from "../../../middlewares/stacker";
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
import * as u2f from "u2f";
import config from "../../../../config";
import TwoFactor, { TFATypes as TwoFATypes, IYubiKey } from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import LoginToken from "../../../../models/login_token";
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
const U2FRoute = Router();
U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "challenge") {
const registrationRequest = u2f.request(config.core.url);
let twofactor = TwoFactor.new(<IYubiKey>{
user: req.user._id,
type: TwoFATypes.U2F,
valid: false,
data: {
registration: registrationRequest
}
})
await TwoFactor.save(twofactor);
res.json({ request: registrationRequest, id: twofactor._id, appid: config.core.url });
} else {
const { response, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IYubiKey = await TwoFactor.findById(id);
const err = () => { throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST) };
if (!twofactor || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.U2F || !twofactor.data.registration || twofactor.valid) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
const result = u2f.checkRegistration(twofactor.data.registration, response);
if (result.successful) {
twofactor.data = {
keyHandle: result.keyHandle,
publicKey: result.publicKey
}
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
}
}));
U2FRoute.get("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
if (!twofactor) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
if (twofactor.expires) {
if (moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
}
let request = u2f.request(config.core.url, twofactor.data.keyHandle);
login.data = {
type: "ykr",
request
};
let r;;
if (special) {
special.data = login.data;
r = LoginToken.save(special);
}
await Promise.all([r, LoginToken.save(login)]);
res.json({ request });
}))
U2FRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
let { response } = req.body;
if (!twofactor || !login.data || login.data.type !== "ykr" || special && (!special.data || special.data.type !== "ykr")) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
let login_exp;
let special_exp;
let result = u2f.checkSignature(login.data.request, response, twofactor.data.publicKey);
if (result.successful) {
if (special) {
let result = u2f.checkSignature(special.data.request, response, twofactor.data.publicKey);
if (result.successful) {
special_exp = await upgradeToken(special);
}
else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
}
login_exp = await upgradeToken(login);
}
else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
res.json({ success: true, login_exp, special_exp })
}))
export default U2FRoute;
import { Router, Request } from "express"
import Stacker from "../../../middlewares/stacker";
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
import * as u2f from "u2f";
import config from "../../../../config";
import TwoFactor, { TFATypes as TwoFATypes, IYubiKey } from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import LoginToken from "../../../../models/login_token";
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
const U2FRoute = Router();
/**
* Registerinf a new YubiKey
*/
U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "challenge") {
const registrationRequest = u2f.request(config.core.url);
let twofactor = TwoFactor.new(<IYubiKey>{
user: req.user._id,
type: TwoFATypes.U2F,
valid: false,
data: {
registration: registrationRequest
}
})
await TwoFactor.save(twofactor);
res.json({ request: registrationRequest, id: twofactor._id, appid: config.core.url });
} else {
const { response, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IYubiKey = await TwoFactor.findById(id);
const err = () => { throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST) };
if (!twofactor || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.U2F || !twofactor.data.registration || twofactor.valid) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
const result = u2f.checkRegistration(twofactor.data.registration, response);
if (result.successful) {
twofactor.data = {
keyHandle: result.keyHandle,
publicKey: result.publicKey
}
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
}
}));
U2FRoute.get("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
if (!twofactor) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
if (twofactor.expires) {
if (moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
}
let request = u2f.request(config.core.url, twofactor.data.keyHandle);
login.data = {
type: "ykr",
request
};
let r;;
if (special) {
special.data = login.data;
r = LoginToken.save(special);
}
await Promise.all([r, LoginToken.save(login)]);
res.json({ request });
}))
U2FRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
let { response } = req.body;
if (!twofactor || !login.data || login.data.type !== "ykr" || special && (!special.data || special.data.type !== "ykr")) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
let login_exp;
let special_exp;
let result = u2f.checkSignature(login.data.request, response, twofactor.data.publicKey);
if (result.successful) {
if (special) {
let result = u2f.checkSignature(special.data.request, response, twofactor.data.publicKey);
if (result.successful) {
special_exp = await upgradeToken(special);
}
else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
}
login_exp = await upgradeToken(login);
}
else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
res.json({ success: true, login_exp, special_exp })
}))
export default U2FRoute;