Add JRPC API, reworked Login and User pages
This commit is contained in:
@ -1,19 +0,0 @@
|
||||
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,19 +0,0 @@
|
||||
import { Request, Response } from "express";
|
||||
import Stacker from "../middlewares/stacker";
|
||||
import { GetUserMiddleware } from "../middlewares/user";
|
||||
import Mail from "../../models/mail";
|
||||
|
||||
export const GetContactInfos = Stacker(
|
||||
GetUserMiddleware(true, true),
|
||||
async (req: Request, res: Response) => {
|
||||
let mails = await Promise.all(
|
||||
req.user.mails.map((mail) => Mail.findById(mail))
|
||||
);
|
||||
|
||||
let contact = {
|
||||
mails: mails.filter((e) => !!e),
|
||||
phones: req.user.phones,
|
||||
};
|
||||
res.json({ contact });
|
||||
}
|
||||
);
|
@ -1,132 +1,39 @@
|
||||
import { Router } from "express";
|
||||
import { GetAccount } from "./account";
|
||||
import { GetContactInfos } from "./contact";
|
||||
import Login from "./login";
|
||||
import Register from "./register";
|
||||
import { DeleteToken, GetToken } from "./token";
|
||||
import TwoFactorRoute from "./twofactor";
|
||||
import OAuthRoute from "./oauth";
|
||||
|
||||
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)
|
||||
* @apiParam {Number} time in milliseconds used to hash password. This is used to make passwords "expire"
|
||||
*
|
||||
* @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} user.name Full name of the user
|
||||
* @apiSuccess {String} user.username Username of user
|
||||
* @apiSuccess {Date} user.birthday Birthday
|
||||
* @apiSuccess {Number} user.gender Gender of user (none = 0, male = 1, female = 2, other = 3)
|
||||
*/
|
||||
UserRoute.get("/account", GetAccount);
|
||||
|
||||
/**
|
||||
* @api {delete} /user/account
|
||||
* @apiName UserGetAccount
|
||||
*
|
||||
* @apiGroup user
|
||||
* @apiPermission user
|
||||
*
|
||||
* @apiSuccess {Boolean} success
|
||||
* @apiSuccess {Object} contact
|
||||
* @apiSuccess {Object[]} user.mail EMail addresses
|
||||
* @apiSuccess {Object[]} user.phone Phone numbers
|
||||
*/
|
||||
UserRoute.get("/contact", GetContactInfos);
|
||||
|
||||
UserRoute.use("/oauth", OAuthRoute);
|
||||
|
||||
export default UserRoute;
|
||||
import { Router } from "express";
|
||||
import Register from "./register";
|
||||
import OAuthRoute from "./oauth";
|
||||
|
||||
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);
|
||||
|
||||
UserRoute.use("/oauth", OAuthRoute);
|
||||
|
||||
export default UserRoute;
|
||||
|
@ -1,134 +0,0 @@
|
||||
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";
|
||||
import * as crypto from "crypto";
|
||||
import Logging from "@hibas123/nodelogging";
|
||||
|
||||
const Login = promiseMiddleware(async (req: Request, res: Response) => {
|
||||
let type = req.query.type as string;
|
||||
if (type === "username") {
|
||||
let { username, uid } = req.query as { [key: string]: string };
|
||||
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, date } = req.body;
|
||||
|
||||
let user = await User.findOne(
|
||||
username ? { username: username.toLowerCase() } : { uid: uid }
|
||||
);
|
||||
if (!user) {
|
||||
res.json({ error: req.__("User not found") });
|
||||
} else {
|
||||
let upw = user.password;
|
||||
if (date) {
|
||||
if (
|
||||
!moment(date).isBetween(
|
||||
moment().subtract(1, "minute"),
|
||||
moment().add(1, "minute")
|
||||
)
|
||||
) {
|
||||
res.json({
|
||||
error: req.__(
|
||||
"Invalid timestamp. Please check your devices time!"
|
||||
),
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
upw = crypto
|
||||
.createHash("sha512")
|
||||
.update(upw + date.toString())
|
||||
.digest("hex");
|
||||
}
|
||||
}
|
||||
if (upw !== 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,38 +1,38 @@
|
||||
import { Request, Response } from "express";
|
||||
import Stacker from "../../middlewares/stacker";
|
||||
import { GetUserMiddleware } from "../../middlewares/user";
|
||||
import { URL } from "url";
|
||||
import Client from "../../../models/client";
|
||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
||||
import { randomBytes } from "crypto";
|
||||
import moment = require("moment");
|
||||
import RefreshToken from "../../../models/refresh_token";
|
||||
import { refreshTokenValidTime } from "../../../config";
|
||||
import { getClientWithOrigin } from "./_helper";
|
||||
import Permission from "../../../models/permissions";
|
||||
|
||||
export const GetPermissionsForAuthRequest = Stacker(
|
||||
GetUserMiddleware(true, false),
|
||||
async (req: Request, res: Response) => {
|
||||
const { client_id, origin, permissions } = req.query as {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
const client = await getClientWithOrigin(client_id, origin);
|
||||
|
||||
const perm = permissions.split(",").filter((e) => !!e);
|
||||
|
||||
const resolved = await Promise.all(
|
||||
perm.map((p) => Permission.findById(p))
|
||||
);
|
||||
|
||||
if (resolved.some((e) => e.grant_type !== "user")) {
|
||||
throw new RequestError(
|
||||
"Invalid Permission requested",
|
||||
HttpStatusCode.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ permissions: resolved });
|
||||
}
|
||||
);
|
||||
import { Request, Response } from "express";
|
||||
import Stacker from "../../middlewares/stacker";
|
||||
import { GetUserMiddleware } from "../../middlewares/user";
|
||||
import { URL } from "url";
|
||||
import Client from "../../../models/client";
|
||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
||||
import { randomBytes } from "crypto";
|
||||
import moment = require("moment");
|
||||
import RefreshToken from "../../../models/refresh_token";
|
||||
import { refreshTokenValidTime } from "../../../config";
|
||||
import { getClientWithOrigin } from "./_helper";
|
||||
import Permission from "../../../models/permissions";
|
||||
|
||||
export const GetPermissionsForAuthRequest = Stacker(
|
||||
GetUserMiddleware(true, false),
|
||||
async (req: Request, res: Response) => {
|
||||
const { client_id, origin, permissions } = req.query as {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
const client = await getClientWithOrigin(client_id, origin);
|
||||
|
||||
const perm = permissions.split(",").filter((e) => !!e);
|
||||
|
||||
const resolved = await Promise.all(
|
||||
perm.map((p) => Permission.findById(p))
|
||||
);
|
||||
|
||||
if (resolved.some((e) => e.grant_type !== "user")) {
|
||||
throw new RequestError(
|
||||
"Invalid Permission requested",
|
||||
HttpStatusCode.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ permissions: resolved });
|
||||
}
|
||||
);
|
||||
|
@ -1,45 +0,0 @@
|
||||
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 });
|
||||
}
|
||||
);
|
@ -1,100 +0,0 @@
|
||||
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.TOTP,
|
||||
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.TOTP
|
||||
) {
|
||||
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,16 +0,0 @@
|
||||
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,56 +0,0 @@
|
||||
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;
|
@ -1,135 +0,0 @@
|
||||
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.TOTP,
|
||||
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.TOTP ||
|
||||
!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.TOTP
|
||||
) {
|
||||
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,206 +0,0 @@
|
||||
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.WEBAUTHN,
|
||||
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.WEBAUTHN ||
|
||||
!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.WEBAUTHN,
|
||||
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.WEBAUTHN,
|
||||
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