Updating a bunch of stuff.
This version has partially support for TwoFactorAuthentication.
This commit is contained in:
16
src/api/user/account.ts
Normal file
16
src/api/user/account.ts
Normal 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 });
|
||||
});
|
@ -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;
|
@ -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;
|
@ -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 });
|
||||
});
|
74
src/api/user/twofactor/backup/index.ts
Normal file
74
src/api/user/twofactor/backup/index.ts
Normal 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;
|
@ -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;
|
||||
}
|
@ -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;
|
105
src/api/user/twofactor/otc/index.ts
Normal file
105
src/api/user/twofactor/otc/index.ts
Normal 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;
|
@ -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;
|
||||
|
Reference in New Issue
Block a user