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

16
src/api/user/account.ts Normal file
View File

@ -0,0 +1,16 @@
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetAccount = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
let user = {
id: req.user.uid,
name: req.user.name,
username: req.user.username,
birthday: req.user.birthday,
gender: req.user.gender,
};
res.json({ user });
});

View File

@ -1,95 +1,113 @@
import { Router } from "express";
import Register from "./register";
import Login from "./login";
import TwoFactorRoute from "./twofactor";
import { GetToken, DeleteToken } from "./token";
const UserRoute: Router = Router();
/**
* @api {post} /user/register
* @apiName UserRegister
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} mail EMail linked to this Account
* @apiParam {String} username The new Username
* @apiParam {String} password Password hashed and salted like specification
* @apiParam {String} salt The Salt used for password hashing
* @apiParam {String} regcode The regcode, that should be used
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
* @apiParam {String} name The real name of the User
*
* @apiSuccess {Boolean} success
*
* @apiErrorExample {Object} Error-Response:
{
error: [
{
message: "Some Error",
field: "username"
}
],
status: 400
}
*/
UserRoute.post("/register", Register);
/**
* @api {post} /user/login?type=:type
* @apiName UserLogin
*
* @apiParam {String} type Type could be either "username" or "password"
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} username Username (either username or uid required)
* @apiParam {String} uid (either username or uid required)
* @apiParam {String} password Password hashed and salted like specification (only on type password)
*
* @apiSuccess {String} uid On type = "username"
* @apiSuccess {String} salt On type = "username"
*
* @apiSuccess {String} login On type = "password". Login Token
* @apiSuccess {String} special On type = "password". Special Token
* @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required
* @apiSuccess {String} tfa.id The ID of the TFA Method
* @apiSuccess {String} tfa.name The name of the TFA Method
* @apiSuccess {String} tfa.type The type of the TFA Method
*/
UserRoute.post("/login", Login)
UserRoute.use("/twofactor", TwoFactorRoute);
/**
* @api {get} /user/token
* @apiName UserGetToken
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Object[]} token
* @apiSuccess {String} token.id The Token ID
* @apiSuccess {String} token.special Identifies Special Token
* @apiSuccess {String} token.ip IP the token was optained from
* @apiSuccess {String} token.browser The Browser the token was optained from (User Agent)
* @apiSuccess {Boolean} token.isthis Shows if it is token used by this session
*/
UserRoute.get("/token", GetToken);
/**
* @api {delete} /user/token/:id
* @apiParam {String} id The id of the token to be deleted
*
* @apiName UserDeleteToken
*
* @apiParam {String} type Type could be either "username" or "password"
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
*/
UserRoute.delete("/token/:id", DeleteToken);
import { Router } from "express";
import Register from "./register";
import Login from "./login";
import TwoFactorRoute from "./twofactor";
import { GetToken, DeleteToken } from "./token";
import { GetAccount } from "./account";
const UserRoute: Router = Router();
/**
* @api {post} /user/register
* @apiName UserRegister
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} mail EMail linked to this Account
* @apiParam {String} username The new Username
* @apiParam {String} password Password hashed and salted like specification
* @apiParam {String} salt The Salt used for password hashing
* @apiParam {String} regcode The regcode, that should be used
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
* @apiParam {String} name The real name of the User
*
* @apiSuccess {Boolean} success
*
* @apiErrorExample {Object} Error-Response:
{
error: [
{
message: "Some Error",
field: "username"
}
],
status: 400
}
*/
UserRoute.post("/register", Register);
/**
* @api {post} /user/login?type=:type
* @apiName UserLogin
*
* @apiParam {String} type Type could be either "username" or "password"
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} username Username (either username or uid required)
* @apiParam {String} uid (either username or uid required)
* @apiParam {String} password Password hashed and salted like specification (only on type password)
*
* @apiSuccess {String} uid On type = "username"
* @apiSuccess {String} salt On type = "username"
*
* @apiSuccess {String} login On type = "password". Login Token
* @apiSuccess {String} special On type = "password". Special Token
* @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required
* @apiSuccess {String} tfa.id The ID of the TFA Method
* @apiSuccess {String} tfa.name The name of the TFA Method
* @apiSuccess {String} tfa.type The type of the TFA Method
*/
UserRoute.post("/login", Login)
UserRoute.use("/twofactor", TwoFactorRoute);
/**
* @api {get} /user/token
* @apiName UserGetToken
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Object[]} token
* @apiSuccess {String} token.id The Token ID
* @apiSuccess {String} token.special Identifies Special Token
* @apiSuccess {String} token.ip IP the token was optained from
* @apiSuccess {String} token.browser The Browser the token was optained from (User Agent)
* @apiSuccess {Boolean} token.isthis Shows if it is token used by this session
*/
UserRoute.get("/token", GetToken);
/**
* @api {delete} /user/token/:id
* @apiParam {String} id The id of the token to be deleted
*
* @apiName UserDeleteToken
*
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
*/
UserRoute.delete("/token/:id", DeleteToken);
/**
* @api {delete} /user/account
* @apiName UserGetAccount
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
* @apiSuccess {Object[]} user
* @apiSuccess {String} user.id User ID
* @apiSuccess {String} token.name Full name of the user
* @apiSuccess {String} token.username Username of user
* @apiSuccess {Date} token.birthday Birthday
* @apiSuccess {Number} token.gender Gender of user (none = 0, male = 1, female = 2, other = 3)
*/
UserRoute.get("/account", GetAccount);
export default UserRoute;

View File

@ -1,98 +1,96 @@
import { Request, Response } from "express"
import User, { IUser } from "../../models/user";
import { randomBytes } from "crypto";
import moment = require("moment");
import LoginToken from "../../models/login_token";
import promiseMiddleware from "../../helper/promiseMiddleware";
import TwoFactor from "../../models/twofactor";
const Login = promiseMiddleware(async (req: Request, res: Response) => {
let type = req.query.type;
if (type === "username") {
let { username, uid } = req.query;
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid });
if (!user) {
res.json({ error: req.__("User not found") })
} else {
res.json({ salt: user.salt, uid: user.uid });
}
return;
} else if (type === "password") {
const sendToken = async (user: IUser, tfa?: any[]) => {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
let client = {
ip: Array.isArray(ip) ? ip[0] : ip,
browser: req.headers["user-agent"]
}
let token_str = randomBytes(16).toString("hex");
let tfa_exp = moment().add(5, "minutes").toDate()
let token_exp = moment().add(6, "months").toDate()
let token = LoginToken.new({
token: token_str,
valid: true,
validTill: tfa ? tfa_exp : token_exp,
user: user._id,
validated: tfa ? false : true,
...client
});
await LoginToken.save(token);
let special_str = randomBytes(24).toString("hex");
let special_exp = moment().add(30, "minutes").toDate()
let special = LoginToken.new({
token: special_str,
valid: true,
validTill: tfa ? tfa_exp : special_exp,
special: true,
user: user._id,
validated: tfa ? false : true,
...client
});
await LoginToken.save(special);
res.json({
login: { token: token_str, expires: token.validTill.toUTCString() },
special: { token: special_str, expires: special.validTill.toUTCString() },
tfa
});
}
let { username, password, uid } = req.body;
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
if (!user) {
res.json({ error: req.__("User not found") })
} else {
if (user.password !== password) {
res.json({ error: req.__("Password or username wrong") })
} else {
let twofactor = await TwoFactor.find({ user: 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);
if (twofactor && twofactor.length > 0) {
let tfa = twofactor.map(e => {
return {
id: e._id,
name: e.name,
type: e.type
}
})
await sendToken(user, tfa);
} else {
await sendToken(user);
}
}
}
} else {
res.json({ error: req.__("Invalid type!") });
}
});
import { Request, Response } from "express"
import User, { IUser } from "../../models/user";
import { randomBytes } from "crypto";
import moment = require("moment");
import LoginToken from "../../models/login_token";
import promiseMiddleware from "../../helper/promiseMiddleware";
import TwoFactor, { TFATypes, TFANames } from "../../models/twofactor";
const Login = promiseMiddleware(async (req: Request, res: Response) => {
let type = req.query.type;
if (type === "username") {
let { username, uid } = req.query;
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid });
if (!user) {
res.json({ error: req.__("User not found") })
} else {
res.json({ salt: user.salt, uid: user.uid });
}
return;
} else if (type === "password") {
const sendToken = async (user: IUser, tfa?: any[]) => {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
let client = {
ip: Array.isArray(ip) ? ip[0] : ip,
browser: req.headers["user-agent"]
}
let token_str = randomBytes(16).toString("hex");
let tfa_exp = moment().add(5, "minutes").toDate()
let token_exp = moment().add(6, "months").toDate()
let token = LoginToken.new({
token: token_str,
valid: true,
validTill: tfa ? tfa_exp : token_exp,
user: user._id,
validated: tfa ? false : true,
...client
});
await LoginToken.save(token);
let special_str = randomBytes(24).toString("hex");
let special_exp = moment().add(30, "minutes").toDate()
let special = LoginToken.new({
token: special_str,
valid: true,
validTill: tfa ? tfa_exp : special_exp,
special: true,
user: user._id,
validated: tfa ? false : true,
...client
});
await LoginToken.save(special);
res.json({
login: { token: token_str, expires: token.validTill.toUTCString() },
special: { token: special_str, expires: special.validTill.toUTCString() },
tfa
});
}
let { username, password, uid } = req.body;
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
if (!user) {
res.json({ error: req.__("User not found") })
} else {
if (user.password !== password) {
res.json({ error: req.__("Password or username wrong") })
} else {
let twofactor = await TwoFactor.find({ user: 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);
if (twofactor && twofactor.length > 0) {
let tfa = twofactor.map(e => {
return {
id: e._id,
name: e.name || TFANames.get(e.type),
type: e.type
}
})
await sendToken(user, tfa);
} else {
await sendToken(user);
}
}
}
} else {
res.json({ error: req.__("Invalid type!") });
}
});
export default Login;

View File

@ -1,29 +1,29 @@
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
let raw_token = await LoginToken.find({ user: req.user._id, valid: true });
let token = await Promise.all(raw_token.map(async token => {
await CheckToken(token);
return {
id: token._id,
special: token.special,
ip: token.ip,
browser: token.browser,
isthis: token._id.equals(token.special ? req.token.special._id : req.token.login._id)
}
}).filter(t => t !== undefined));
res.json({ token });
});
export const DeleteToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
let { id } = req.query;
let token = await LoginToken.findById(id);
if (!token || !token.user.equals(req.user._id)) throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
token.valid = false;
await LoginToken.save(token);
res.json({ success: true });
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
let raw_token = await LoginToken.find({ user: req.user._id, valid: true });
let token = await Promise.all(raw_token.map(async token => {
await CheckToken(token);
return {
id: token._id,
special: token.special,
ip: token.ip,
browser: token.browser,
isthis: token._id.equals(token.special ? req.token.special._id : req.token.login._id)
}
}).filter(t => t !== undefined));
res.json({ token });
});
export const DeleteToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
let { id } = req.params;
let token = await LoginToken.findById(id);
if (!token || !token.user.equals(req.user._id)) throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
token.valid = false;
await LoginToken.save(token);
res.json({ success: true });
});

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;