Running prettier
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Fabian Stamm
2020-08-07 16:16:39 +02:00
parent 77fedd2815
commit 51a8609880
87 changed files with 4000 additions and 2812 deletions

View File

@ -5,16 +5,15 @@ import Client from "../../models/client";
import verify, { Types } from "../middlewares/verify";
import { randomBytes } from "crypto";
const ClientRouter: Router = Router();
ClientRouter.route("/")
/**
* @api {get} /admin/client
* @apiName AdminGetClients
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiSuccess {Object[]} clients
* @apiSuccess {String} clients._id The internally used id
* @apiSuccess {String} clients.maintainer
@ -26,24 +25,26 @@ ClientRouter.route("/")
* @apiSuccess {String} clients.client_id Client ID used outside of DB
* @apiSuccess {String} clients.client_secret
*/
.get(promiseMiddleware(async (req, res) => {
let clients = await Client.find({});
//ToDo check if user is required!
res.json(clients);
}))
.get(
promiseMiddleware(async (req, res) => {
let clients = await Client.find({});
//ToDo check if user is required!
res.json(clients);
})
)
/**
* @api {get} /admin/client
* @apiName AdminAddClients
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiParam {Boolean} internal Is it an internal app
* @apiParam {String} name
* @apiParam {String} redirect_url
* @apiParam {String} website
* @apiParam {String} logo
*
*
* @apiSuccess {Object[]} clients
* @apiSuccess {String} clients._id The internally used id
* @apiSuccess {String} clients.maintainer
@ -55,62 +56,70 @@ ClientRouter.route("/")
* @apiSuccess {String} clients.client_id Client ID used outside of DB
* @apiSuccess {String} clients.client_secret
*/
.post(verify({
internal: {
type: Types.BOOLEAN,
optional: true
},
name: {
type: Types.STRING
},
redirect_url: {
type: Types.STRING
},
website: {
type: Types.STRING
},
logo: {
type: Types.STRING,
optional: true
}
}, true), promiseMiddleware(async (req, res) => {
req.body.client_secret = randomBytes(32).toString("hex");
let client = Client.new(req.body);
client.maintainer = req.user._id;
await Client.save(client)
res.json(client);
}))
.post(
verify(
{
internal: {
type: Types.BOOLEAN,
optional: true,
},
name: {
type: Types.STRING,
},
redirect_url: {
type: Types.STRING,
},
website: {
type: Types.STRING,
},
logo: {
type: Types.STRING,
optional: true,
},
},
true
),
promiseMiddleware(async (req, res) => {
req.body.client_secret = randomBytes(32).toString("hex");
let client = Client.new(req.body);
client.maintainer = req.user._id;
await Client.save(client);
res.json(client);
})
);
ClientRouter.route("/:id")
/**
* @api {delete} /admin/client/:id
* @apiParam {String} id Client _id
* @apiName AdminDeleteClient
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*/
.delete(promiseMiddleware(async (req, res) => {
let { id } = req.params;
await Client.delete(id);
res.json({ success: true });
}))
.delete(
promiseMiddleware(async (req, res) => {
let { id } = req.params;
await Client.delete(id);
res.json({ success: true });
})
)
/**
* @api {put} /admin/client/:id
* @apiParam {String} id Client _id
* @apiName AdminUpdateClient
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiParam {Boolean} internal Is it an internal app
* @apiParam {String} name
* @apiParam {String} redirect_url
* @apiParam {String} website
* @apiParam {String} logo
*
*
* @apiSuccess {String} _id The internally used id
* @apiSuccess {String} maintainer UserID of client maintainer
* @apiSuccess {Boolean} internal Defines if it is a internal client
@ -118,40 +127,49 @@ ClientRouter.route("/:id")
* @apiSuccess {String} redirect_url Redirect URL after login
* @apiSuccess {String} website Website of Client
* @apiSuccess {String} logo The Logo of the Client (optional)
* @apiSuccess {String} client_id Client ID used outside of DB
* @apiSuccess {String} client_id Client ID used outside of DB
* @apiSuccess {String} client_secret The client secret, that can be used to obtain token
*/
.put(verify({
internal: {
type: Types.BOOLEAN,
optional: true
},
name: {
type: Types.STRING,
optional: true
},
redirect_url: {
type: Types.STRING,
optional: true
},
website: {
type: Types.STRING,
optional: true
},
logo: {
type: Types.STRING,
optional: true
}
}, true), promiseMiddleware(async (req, res) => {
let { id } = req.query;
let client = await Client.findById(id);
if (!client) throw new RequestError(req.__("Client not found"), HttpStatusCode.BAD_REQUEST);
for (let key in req.body) {
client[key] = req.body[key];
}
await Client.save(client);
res.json(client);
}))
.put(
verify(
{
internal: {
type: Types.BOOLEAN,
optional: true,
},
name: {
type: Types.STRING,
optional: true,
},
redirect_url: {
type: Types.STRING,
optional: true,
},
website: {
type: Types.STRING,
optional: true,
},
logo: {
type: Types.STRING,
optional: true,
},
},
true
),
promiseMiddleware(async (req, res) => {
let { id } = req.query;
let client = await Client.findById(id);
if (!client)
throw new RequestError(
req.__("Client not found"),
HttpStatusCode.BAD_REQUEST
);
for (let key in req.body) {
client[key] = req.body[key];
}
await Client.save(client);
res.json(client);
})
);
export default ClientRouter;
export default ClientRouter;

View File

@ -9,12 +9,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
const AdminRoute: Router = Router();
AdminRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
if (!req.isAdmin) throw new RequestError("You have no permission to access this API", HttpStatusCode.FORBIDDEN);
else next()
if (!req.isAdmin)
throw new RequestError(
"You have no permission to access this API",
HttpStatusCode.FORBIDDEN
);
else next();
});
AdminRoute.use("/client", ClientRoute);
AdminRoute.use("/regcode", RegCodeRoute)
AdminRoute.use("/user", UserRoute)
AdminRoute.use("/regcode", RegCodeRoute);
AdminRoute.use("/user", UserRoute);
AdminRoute.use("/permission", PermissionRoute);
export default AdminRoute;
export default AdminRoute;

View File

@ -56,18 +56,18 @@ PermissionRoute.route("/")
verify(
{
client: {
type: Types.STRING
type: Types.STRING,
},
name: {
type: Types.STRING
type: Types.STRING,
},
description: {
type: Types.STRING
type: Types.STRING,
},
type: {
type: Types.ENUM,
values: ["user", "client"]
}
values: ["user", "client"],
},
},
true
),
@ -83,7 +83,7 @@ PermissionRoute.route("/")
description: req.body.description,
name: req.body.name,
client: client._id,
grant_type: req.body.type
grant_type: req.body.type,
});
await Permission.save(permission);
res.json(permission);

View File

@ -10,54 +10,60 @@ const RegCodeRoute: Router = Router();
RegCodeRoute.route("/")
/**
* @api {get} /admin/regcode
* @apiName AdminGetRegcodes
*
* @apiName AdminGetRegcodes
*
* @apiGroup admin_regcode
* @apiPermission admin
*
*
* @apiSuccess {Object[]} regcodes
* @apiSuccess {String} permissions._id The ID
* @apiSuccess {String} permissions.token The Regcode Token
* @apiSuccess {String} permissions.valid Defines if the Regcode is valid
* @apiSuccess {String} permissions.validTill Expiration date of RegCode
*/
.get(promiseMiddleware(async (req, res) => {
let regcodes = await RegCode.find({});
res.json(regcodes);
}))
.get(
promiseMiddleware(async (req, res) => {
let regcodes = await RegCode.find({});
res.json(regcodes);
})
)
/**
* @api {delete} /admin/regcode
* @apiName AdminDeleteRegcode
*
*
* @apiParam {String} id The id of the RegCode
*
*
* @apiGroup admin_regcode
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*/
.delete(promiseMiddleware(async (req, res) => {
let { id } = req.query;
await RegCode.delete(id);
res.json({ success: true });
}))
.delete(
promiseMiddleware(async (req, res) => {
let { id } = req.query;
await RegCode.delete(id);
res.json({ success: true });
})
)
/**
* @api {post} /admin/regcode
* @apiName AdminAddRegcode
*
* @apiName AdminAddRegcode
*
* @apiGroup admin_regcode
* @apiPermission admin
*
*
* @apiSuccess {String} code The newly created code
*/
.post(promiseMiddleware(async (req, res) => {
let regcode = RegCode.new({
token: randomBytes(10).toString("hex"),
valid: true,
validTill: moment().add("1", "month").toDate()
.post(
promiseMiddleware(async (req, res) => {
let regcode = RegCode.new({
token: randomBytes(10).toString("hex"),
valid: true,
validTill: moment().add("1", "month").toDate(),
});
await RegCode.save(regcode);
res.json({ code: regcode.token });
})
await RegCode.save(regcode);
res.json({ code: regcode.token });
}))
);
export default RegCodeRoute;
export default RegCodeRoute;

View File

@ -9,15 +9,15 @@ import LoginToken from "../../models/login_token";
const UserRoute: Router = Router();
UserRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN)
else next()
})
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
else next();
});
UserRoute.route("/")
/**
* @api {get} /admin/user
* @apiName AdminGetUsers
*
* @apiName AdminGetUsers
*
* @apiGroup admin_user
* @apiPermission admin
* @apiSuccess {Object[]} user
@ -29,57 +29,65 @@ UserRoute.route("/")
* @apiSuccess {Number} user.gender 0 = none, 1 = male, 2 = female, 3 = other
* @apiSuccess {Boolean} user.admin Is admin or not
*/
.get(promiseMiddleware(async (req, res) => {
let users = await User.find({});
users.forEach(e => delete e.password && delete e.salt && delete e.encryption_key);
res.json(users);
}))
.get(
promiseMiddleware(async (req, res) => {
let users = await User.find({});
users.forEach(
(e) => delete e.password && delete e.salt && delete e.encryption_key
);
res.json(users);
})
)
/**
* @api {delete} /admin/user
* @apiName AdminDeleteUser
*
* @apiName AdminDeleteUser
*
* @apiParam {String} id The User ID
*
*
* @apiGroup admin_user
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*/
.delete(promiseMiddleware(async (req, res) => {
let { id } = req.query;
let user = await User.findById(id);
.delete(
promiseMiddleware(async (req, res) => {
let { id } = req.query;
let user = await User.findById(id);
await Promise.all([
user.mails.map(mail => Mail.delete(mail)),
[
RefreshToken.deleteFilter({ user: user._id }),
LoginToken.deleteFilter({ user: user._id })
]
])
await Promise.all([
user.mails.map((mail) => Mail.delete(mail)),
[
RefreshToken.deleteFilter({ user: user._id }),
LoginToken.deleteFilter({ user: user._id }),
],
]);
await User.delete(user);
res.json({ success: true });
}))
await User.delete(user);
res.json({ success: true });
})
)
/**
* @api {put} /admin/user
* @apiName AdminChangeUser
*
* @apiName AdminChangeUser
*
* @apiParam {String} id The User ID
*
*
* @apiGroup admin_user
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*
* @apiDescription Flipps the user role:
* admin -> user
*
* @apiDescription Flipps the user role:
* admin -> user
* user -> admin
*/
.put(promiseMiddleware(async (req, res) => {
let { id } = req.query;
let user = await User.findById(id);
user.admin = !user.admin;
await User.save(user);
res.json({ success: true })
}))
export default UserRoute;
.put(
promiseMiddleware(async (req, res) => {
let { id } = req.query;
let user = await User.findById(id);
user.admin = !user.admin;
await User.save(user);
res.json({ success: true });
})
);
export default UserRoute;

View File

@ -1,6 +1,9 @@
import { Request, Response, Router } from "express"
import { Request, Response, Router } from "express";
import Stacker from "../middlewares/stacker";
import { GetClientAuthMiddleware, GetClientApiAuthMiddleware } from "../middlewares/client";
import {
GetClientAuthMiddleware,
GetClientApiAuthMiddleware,
} from "../middlewares/client";
import { GetUserMiddleware } from "../middlewares/user";
import { createJWT } from "../../keys";
import Client from "../../models/client";
@ -8,55 +11,74 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
import config from "../../config";
import Mail from "../../models/mail";
const ClientRouter = Router();
/**
* @api {get} /client/user
*
*
* @apiDescription Can be used for simple authentication of user. It will redirect the user to the redirect URI with a very short lived jwt.
*
*
* @apiParam {String} redirect_uri URL to redirect to on success
* @apiParam {String} state A optional state, that will be included in the JWT and redirect_uri as parameter
*
* @apiName ClientUser
* @apiName ClientUser
* @apiGroup client
*
* @apiPermission user_client Requires ClientID and Authenticated User
*/
ClientRouter.get("/user", Stacker(GetClientAuthMiddleware(false), GetUserMiddleware(false, false), async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query;
ClientRouter.get(
"/user",
Stacker(
GetClientAuthMiddleware(false),
GetUserMiddleware(false, false),
async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query;
if (redirect_uri !== req.client.redirect_url)
throw new RequestError("Invalid redirect URI", HttpStatusCode.BAD_REQUEST);
if (redirect_uri !== req.client.redirect_url)
throw new RequestError(
"Invalid redirect URI",
HttpStatusCode.BAD_REQUEST
);
let jwt = await createJWT({
client: req.client.client_id,
uid: req.user.uid,
username: req.user.username,
state: state
}, {
expiresIn: 30,
issuer: config.core.url,
algorithm: "RS256",
subject: req.user.uid,
audience: req.client.client_id
}); //after 30 seconds this token is invalid
res.redirect(redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : ""));
}));
ClientRouter.get("/account", Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
let mails = await Promise.all(req.user.mails.map(id => Mail.findById(id)));
let mail = mails.find(e => e.primary) || mails[0];
res.json({
user: {
username: req.user.username,
name: req.user.name,
email: mail
let jwt = await createJWT(
{
client: req.client.client_id,
uid: req.user.uid,
username: req.user.username,
state: state,
},
{
expiresIn: 30,
issuer: config.core.url,
algorithm: "RS256",
subject: req.user.uid,
audience: req.client.client_id,
}
); //after 30 seconds this token is invalid
res.redirect(
redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : "")
);
}
)
);
ClientRouter.get(
"/account",
Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
let mails = await Promise.all(
req.user.mails.map((id) => Mail.findById(id))
);
let mail = mails.find((e) => e.primary) || mails[0];
res.json({
user: {
username: req.user.username,
name: req.user.name,
email: mail,
},
});
})
}));
);
export default ClientRouter;

View File

@ -2,7 +2,7 @@ import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import {
ClientAuthMiddleware,
GetClientAuthMiddleware
GetClientAuthMiddleware,
} from "../middlewares/client";
import Permission from "../../models/permissions";
import User from "../../models/user";
@ -22,19 +22,19 @@ export const GetPermissions = Stacker(
if (user) {
const grant = await Grant.findOne({
client: req.client._id,
user: user
user: user,
});
permissions = await Promise.all(
grant.permissions.map(perm => Permission.findById(perm))
).then(res =>
grant.permissions.map((perm) => Permission.findById(perm))
).then((res) =>
res
.filter(e => e.grant_type === "client")
.map(e => {
.filter((e) => e.grant_type === "client")
.map((e) => {
return {
id: e._id.toHexString(),
name: e.name,
description: e.description
description: e.description,
};
})
);
@ -43,10 +43,10 @@ export const GetPermissions = Stacker(
if (permission) {
const grants = await Grant.find({
client: req.client._id,
permissions: new ObjectID(permission)
permissions: new ObjectID(permission),
});
users = grants.map(grant => grant.user.toHexString());
users = grants.map((grant) => grant.user.toHexString());
}
res.json({ permissions, users });
@ -73,14 +73,14 @@ export const PostPermissions = Stacker(
let grant = await Grant.findOne({
client: req.client._id,
user: req.user._id
user: req.user._id,
});
if (!grant) {
grant = Grant.new({
client: req.client._id,
user: req.user._id,
permissions: []
permissions: [],
});
}
@ -92,7 +92,7 @@ export const PostPermissions = Stacker(
await Grant.save(grant);
res.json({
success: true
success: true,
});
}
);

View File

@ -1,4 +1,4 @@
import * as express from "express"
import * as express from "express";
import AdminRoute from "./admin";
import UserRoute from "./user";
import InternalRoute from "./internal";
@ -9,7 +9,7 @@ import OAuthRoute from "./oauth";
const ApiRouter: express.IRouter = express.Router();
ApiRouter.use("/admin", AdminRoute);
ApiRouter.use(cors())
ApiRouter.use(cors());
ApiRouter.use("/user", UserRoute);
ApiRouter.use("/internal", InternalRoute);
ApiRouter.use("/oauth", OAuthRoute);
@ -22,4 +22,4 @@ ApiRouter.use("/", ClientRouter);
// Legacy reasons (deprecated)
ApiRouter.post("/login", Login);
export default ApiRouter;
export default ApiRouter;

View File

@ -6,10 +6,10 @@ const InternalRoute: Router = Router();
/**
* @api {get} /internal/oauth
* @apiName ClientInteralOAuth
*
*
* @apiGroup client_internal
* @apiPermission client_internal Only ClientID
*
*
* @apiParam {String} redirect_uri Redirect URI called after success
* @apiParam {String} state State will be set in RedirectURI for the client to check
*/
@ -18,13 +18,13 @@ InternalRoute.get("/oauth", OAuthInternalApp);
/**
* @api {post} /internal/password
* @apiName ClientInteralPassword
*
*
* @apiGroup client_internal
* @apiPermission client_internal Requires ClientID and Secret
*
*
* @apiParam {String} username Username (either username or UID)
* @apiParam {String} uid User ID (either username or UID)
* @apiParam {String} password Hashed and Salted according to specification
*/
InternalRoute.post("/password", PasswordAuth)
export default InternalRoute;
InternalRoute.post("/password", PasswordAuth);
export default InternalRoute;

View File

@ -6,11 +6,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
import ClientCode from "../../models/client_code";
import moment = require("moment");
import { randomBytes } from "crypto";
export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), UserMiddleware,
export const OAuthInternalApp = Stacker(
GetClientAuthMiddleware(false, true),
UserMiddleware,
async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query
let { redirect_uri, state } = req.query;
if (!redirect_uri) {
throw new RequestError("No redirect url set!", HttpStatusCode.BAD_REQUEST);
throw new RequestError(
"No redirect url set!",
HttpStatusCode.BAD_REQUEST
);
}
let sep = redirect_uri.indexOf("?") < 0 ? "?" : "&";
@ -20,10 +25,17 @@ export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), Us
client: req.client._id,
validTill: moment().add(30, "minutes").toDate(),
code: randomBytes(16).toString("hex"),
permissions: []
permissions: [],
});
await ClientCode.save(code);
res.redirect(redirect_uri + sep + "code=" + code.code + (state ? "&state=" + state : ""));
res.redirect(
redirect_uri +
sep +
"code=" +
code.code +
(state ? "&state=" + state : "")
);
res.end();
});
}
);

View File

@ -4,22 +4,32 @@ import Stacker from "../middlewares/stacker";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
const PasswordAuth = Stacker(GetClientAuthMiddleware(true, true), async (req: Request, res: Response) => {
let { username, password, uid }: { username: string, password: string, uid: string } = req.body;
let query: any = { password: password };
if (username) {
query.username = username.toLowerCase()
} else if (uid) {
query.uid = uid;
} else {
throw new RequestError(req.__("No username or uid set"), HttpStatusCode.BAD_REQUEST);
}
const PasswordAuth = Stacker(
GetClientAuthMiddleware(true, true),
async (req: Request, res: Response) => {
let {
username,
password,
uid,
}: { username: string; password: string; uid: string } = req.body;
let query: any = { password: password };
if (username) {
query.username = username.toLowerCase();
} else if (uid) {
query.uid = uid;
} else {
throw new RequestError(
req.__("No username or uid set"),
HttpStatusCode.BAD_REQUEST
);
}
let user = await User.findOne(query);
if (!user) {
res.json({ error: req.__("Password or username wrong") })
} else {
res.json({ success: true, uid: user.uid });
let user = await User.findOne(query);
if (!user) {
res.json({ error: req.__("Password or username wrong") });
} else {
res.json({ success: true, uid: user.uid });
}
}
});
export default PasswordAuth;
);
export default PasswordAuth;

View File

@ -6,7 +6,11 @@ import User from "../../models/user";
import Mail from "../../models/mail";
import { OAuthJWT } from "../../helper/jwt";
export function GetClientAuthMiddleware(checksecret = true, internal = false, checksecret_if_available = false) {
export function GetClientAuthMiddleware(
checksecret = true,
internal = false,
checksecret_if_available = false
) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
let client_id = req.query.client_id || req.body.client_id;
@ -24,19 +28,29 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch
}
if (!client_id || (!client_secret && checksecret)) {
throw new RequestError("No client credentials", HttpStatusCode.BAD_REQUEST);
throw new RequestError(
"No client credentials",
HttpStatusCode.BAD_REQUEST
);
}
let w = { client_id: client_id, client_secret: client_secret };
if (!checksecret && !(checksecret_if_available && client_secret)) delete w.client_secret;
if (!checksecret && !(checksecret_if_available && client_secret))
delete w.client_secret;
let client = await Client.findOne(w)
let client = await Client.findOne(w);
if (!client) {
throw new RequestError("Invalid client_id" + (checksecret ? "or client_secret" : ""), HttpStatusCode.BAD_REQUEST);
throw new RequestError(
"Invalid client_id" + (checksecret ? "or client_secret" : ""),
HttpStatusCode.BAD_REQUEST
);
}
if (internal && !client.internal) {
throw new RequestError(req.__("Client has no permission for access"), HttpStatusCode.FORBIDDEN)
throw new RequestError(
req.__("Client has no permission for access"),
HttpStatusCode.FORBIDDEN
);
}
req.client = client;
next();
@ -44,7 +58,7 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch
if (next) next(e);
else throw e;
}
}
};
}
export const ClientAuthMiddleware = GetClientAuthMiddleware();
@ -52,10 +66,13 @@ export const ClientAuthMiddleware = GetClientAuthMiddleware();
export function GetClientApiAuthMiddleware(permissions?: string[]) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const invalid_err = new RequestError(req.__("You are not logged in or your login is expired"), HttpStatusCode.UNAUTHORIZED);
let token: string = req.query.access_token || req.headers.authorization;
if (!token)
throw invalid_err;
const invalid_err = new RequestError(
req.__("You are not logged in or your login is expired"),
HttpStatusCode.UNAUTHORIZED
);
let token: string =
req.query.access_token || req.headers.authorization;
if (!token) throw invalid_err;
if (token.toLowerCase().startsWith("bearer "))
token = token.substring(7);
@ -64,19 +81,21 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
try {
data = await validateJWT(token);
} catch (err) {
throw invalid_err
throw invalid_err;
}
let user = await User.findOne({ uid: data.user });
if (!user)
throw invalid_err;
if (!user) throw invalid_err;
let client = await Client.findOne({ client_id: data.application })
if (!client)
throw invalid_err;
let client = await Client.findOne({ client_id: data.application });
if (!client) throw invalid_err;
if (permissions && (!data.permissions || !permissions.every(e => data.permissions.indexOf(e) >= 0)))
if (
permissions &&
(!data.permissions ||
!permissions.every((e) => data.permissions.indexOf(e) >= 0))
)
throw invalid_err;
req.user = user;
@ -86,5 +105,5 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
if (next) next(e);
else throw e;
}
}
};
}

View File

@ -8,19 +8,21 @@ function call(handler: RH, req: Request, res: Response) {
let p = handler(req, res, (err) => {
if (err) no(err);
else yes();
})
if (p && p.catch) p.catch(err => no(err));
})
});
if (p && p.catch) p.catch((err) => no(err));
});
}
const Stacker = (...handler: RH[]) => {
return promiseMiddleware(async (req: Request, res: Response, next: NextFunction) => {
let hc = handler.concat();
while (hc.length > 0) {
let h = hc.shift();
await call(h, req, res);
return promiseMiddleware(
async (req: Request, res: Response, next: NextFunction) => {
let hc = handler.concat();
while (hc.length > 0) {
let h = hc.shift();
await call(h, req, res);
}
next();
}
next();
});
}
export default Stacker;
);
};
export default Stacker;

View File

@ -22,7 +22,7 @@ export function GetUserMiddleware(
redirect_uri?: string,
validated = true
) {
return promiseMiddleware(async function(
return promiseMiddleware(async function (
req: Request,
res: Response,
next?: NextFunction
@ -57,7 +57,7 @@ export function GetUserMiddleware(
token: special,
special: true,
valid: true,
user: token.user
user: token.user,
});
if (!(await CheckToken(special_token, validated)))
invalid("Special token invalid");
@ -68,7 +68,7 @@ export function GetUserMiddleware(
req.isAdmin = user.admin;
req.token = {
login: token,
special: special_token
special: special_token,
};
if (next) next();

View File

@ -1,6 +1,14 @@
import { Request, Response, NextFunction } from "express"
import { Request, Response, NextFunction } from "express";
import { Logging } from "@hibas123/nodelogging";
import { isBoolean, isString, isNumber, isObject, isDate, isArray, isSymbol } from "util";
import {
isBoolean,
isString,
isNumber,
isObject,
isDate,
isArray,
isSymbol,
} from "util";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export enum Types {
@ -11,39 +19,41 @@ export enum Types {
OBJECT,
DATE,
ARRAY,
ENUM
ENUM,
}
function isEmail(value: any): boolean {
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(value)
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
);
}
export interface CheckObject {
type: Types
query?: boolean
optional?: boolean
type: Types;
query?: boolean;
optional?: boolean;
/**
* Only when Type.ENUM
*
*
* values to check before
*/
values?: string[]
values?: string[];
/**
* Only when Type.STRING
*/
notempty?: boolean // Only STRING
notempty?: boolean; // Only STRING
}
export interface Checks {
[index: string]: CheckObject// | Types
[index: string]: CheckObject; // | Types
}
// req: Request, res: Response, next: NextFunction
export default function (fields: Checks, noadditional = false) {
return (req: Request, res: Response, next: NextFunction) => {
let errors: { message: string, field: string }[] = []
let errors: { message: string; field: string }[] = [];
function check(data: any, field_name: string, field: CheckObject) {
if (data !== undefined && data !== null) {
@ -78,48 +88,60 @@ export default function (fields: Checks, noadditional = false) {
}
break;
default:
Logging.error(`Invalid type to check: ${field.type} ${Types[field.type]}`)
Logging.error(
`Invalid type to check: ${field.type} ${Types[field.type]}`
);
}
errors.push({
message: res.__("Field {{field}} has wrong type. It should be from type {{type}}", { field: field_name, type: Types[field.type].toLowerCase() }),
field: field_name
})
message: res.__(
"Field {{field}} has wrong type. It should be from type {{type}}",
{ field: field_name, type: Types[field.type].toLowerCase() }
),
field: field_name,
});
} else {
if (!field.optional) errors.push({
message: res.__("Field {{field}} is not defined", { field: field_name }),
field: field_name
})
if (!field.optional)
errors.push({
message: res.__("Field {{field}} is not defined", {
field: field_name,
}),
field: field_name,
});
}
}
for (let field_name in fields) {
let field = fields[field_name]
let data = fields[field_name].query ? req.query[field_name] : req.body[field_name]
check(data, field_name, field)
let field = fields[field_name];
let data = fields[field_name].query
? req.query[field_name]
: req.body[field_name];
check(data, field_name, field);
}
if (noadditional) { //Checks if the data given has additional parameters
if (noadditional) {
//Checks if the data given has additional parameters
let should = Object.keys(fields);
should = should.filter(e => !fields[e].query); //Query parameters should not exist on body
should = should.filter((e) => !fields[e].query); //Query parameters should not exist on body
let has = Object.keys(req.body);
has.every(e => {
has.every((e) => {
if (should.indexOf(e) >= 0) {
return true;
} else {
errors.push({
message: res.__("Field {{field}} should not be there", { field: e }),
field: e
})
message: res.__("Field {{field}} should not be there", {
field: e,
}),
field: e,
});
return false;
}
})
});
}
if (errors.length > 0) {
let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true);
next(err);
} else
next()
}
}
} else next();
};
}

View File

@ -93,11 +93,13 @@ const GetAuthRoute = (view = false) =>
redirect_uri,
scope,
state,
nored
nored,
} = req.query;
const sendError = type => {
const sendError = (type) => {
if (redirect_uri === "$local") redirect_uri = "/code";
res.redirect((redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`));
res.redirect(
(redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`)
);
};
const scopes = scope.split(";");
@ -123,7 +125,7 @@ const GetAuthRoute = (view = false) =>
let permissions: IPermission[] = [];
let proms: PromiseLike<void>[] = [];
if (scopes) {
for (let perm of scopes.filter(e => e !== "read_user")) {
for (let perm of scopes.filter((e) => e !== "read_user")) {
let oid = undefined;
try {
oid = new ObjectID(perm);
@ -132,7 +134,7 @@ const GetAuthRoute = (view = false) =>
continue;
}
proms.push(
Permission.findById(oid).then(p => {
Permission.findById(oid).then((p) => {
if (!p) return Promise.reject(new Error());
permissions.push(p);
})
@ -141,7 +143,7 @@ const GetAuthRoute = (view = false) =>
}
let err = undefined;
await Promise.all(proms).catch(e => {
await Promise.all(proms).catch((e) => {
err = e;
});
@ -152,7 +154,7 @@ const GetAuthRoute = (view = false) =>
let grant: IGrant | undefined = await Grant.findOne({
client: client._id,
user: req.user._id
user: req.user._id,
});
Logging.debug("Grant", grant, permissions);
@ -161,14 +163,14 @@ const GetAuthRoute = (view = false) =>
if (grant) {
missing_permissions = grant.permissions
.map(perm => permissions.find(p => p._id.equals(perm)))
.filter(e => !!e);
.map((perm) => permissions.find((p) => p._id.equals(perm)))
.filter((e) => !!e);
} else {
missing_permissions = permissions;
}
let client_granted_perm = missing_permissions.filter(
e => e.grant_type == "client"
(e) => e.grant_type == "client"
);
if (client_granted_perm.length > 0) {
return sendError("no_permission");
@ -186,11 +188,11 @@ const GetAuthRoute = (view = false) =>
GetAuthPage(
req.__,
client.name,
permissions.map(perm => {
permissions.map((perm) => {
return {
name: perm.name,
description: perm.description,
logo: client.logo
logo: client.logo,
};
})
)
@ -202,11 +204,11 @@ const GetAuthRoute = (view = false) =>
grant = Grant.new({
client: client._id,
user: req.user._id,
permissions: []
permissions: [],
});
grant.permissions.push(
...missing_permissions.map(e => e._id)
...missing_permissions.map((e) => e._id)
);
await Grant.save(grant);
} else {
@ -218,20 +220,19 @@ const GetAuthRoute = (view = false) =>
let code = ClientCode.new({
user: req.user._id,
client: client._id,
permissions: permissions.map(p => p._id),
validTill: moment()
.add(30, "minutes")
.toDate(),
code: randomBytes(16).toString("hex")
permissions: permissions.map((p) => p._id),
validTill: moment().add(30, "minutes").toDate(),
code: randomBytes(16).toString("hex"),
});
await ClientCode.save(code);
let redir =
client.redirect_url === "$local" ? "/code" : client.redirect_url;
let ruri = redir + `?code=${code.code}${state ? "&state=" + state : ""}`;
let ruri =
redir + `?code=${code.code}${state ? "&state=" + state : ""}`;
if (nored === "true") {
res.json({
redirect_uri: ruri
redirect_uri: ruri,
});
} else {
res.redirect(ruri);

View File

@ -8,10 +8,10 @@ const OAuthRoue: Router = Router();
/**
* @api {post} /oauth/auth
* @apiName OAuthAuth
*
*
* @apiGroup oauth
* @apiPermission user Special required
*
*
* @apiParam {String} response_type must be "code" others are not supported
* @apiParam {String} client_id ClientID
* @apiParam {String} redirect_uri The URI to redirect with code
@ -24,31 +24,31 @@ OAuthRoue.post("/auth", GetAuthRoute(false));
/**
* @api {get} /oauth/jwt
* @apiName OAuthJwt
*
*
* @apiGroup oauth
* @apiPermission none
*
*
* @apiParam {String} refreshtoken
*
*
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
*/
OAuthRoue.get("/jwt", JWTRoute)
OAuthRoue.get("/jwt", JWTRoute);
/**
* @api {get} /oauth/public
* @apiName OAuthPublic
*
*
* @apiGroup oauth
* @apiPermission none
*
*
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
*/
OAuthRoue.get("/public", Public)
OAuthRoue.get("/public", Public);
/**
* @api {get} /oauth/refresh
* @apiName OAuthRefreshGet
*
*
* @apiGroup oauth
*/
OAuthRoue.get("/refresh", RefreshTokenRoute);
@ -56,7 +56,7 @@ OAuthRoue.get("/refresh", RefreshTokenRoute);
/**
* @api {post} /oauth/refresh
* @apiName OAuthRefreshPost
*
*
* @apiGroup oauth
*/
OAuthRoue.post("/refresh", RefreshTokenRoute);

View File

@ -8,21 +8,36 @@ import { getAccessTokenJWT } from "../../helper/jwt";
const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => {
let { refreshtoken } = req.query;
if (!refreshtoken) throw new RequestError(req.__("Refresh token not set"), HttpStatusCode.BAD_REQUEST);
if (!refreshtoken)
throw new RequestError(
req.__("Refresh token not set"),
HttpStatusCode.BAD_REQUEST
);
let token = await RefreshToken.findOne({ token: refreshtoken });
if (!token) throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
if (!token)
throw new RequestError(
req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST
);
let user = await User.findById(token.user);
if (!user) {
token.valid = false;
await RefreshToken.save(token);
throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
throw new RequestError(
req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST
);
}
let client = await Client.findById(token.client);
let jwt = await getAccessTokenJWT({ user, permissions: token.permissions, client });
let jwt = await getAccessTokenJWT({
user,
permissions: token.permissions,
client,
});
res.json({ token: jwt });
})
export default JWTRoute;
});
export default JWTRoute;

View File

@ -2,5 +2,5 @@ import { Request, Response } from "express";
import { public_key } from "../../keys";
export default function Public(req: Request, res: Response) {
res.json({ public_key: public_key })
}
res.json({ public_key: public_key });
}

View File

@ -2,9 +2,13 @@ import { Request, Response } from "express";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
import Client from "../../models/client";
import { getAccessTokenJWT, getIDToken, AccessTokenJWTExp } from "../../helper/jwt";
import {
getAccessTokenJWT,
getIDToken,
AccessTokenJWTExp,
} from "../../helper/jwt";
import Stacker from "../middlewares/stacker";
import { GetClientAuthMiddleware } from "../middlewares/client"
import { GetClientAuthMiddleware } from "../middlewares/client";
import ClientCode from "../../models/client_code";
import Mail from "../../models/mail";
import { randomBytes } from "crypto";
@ -25,72 +29,95 @@ token, which will inform the authorization server of the breach.
const refreshTokenValidTime = moment.duration(6, "month");
const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), async (req: Request, res: Response) => {
let grant_type = req.query.grant_type || req.body.grant_type;
if (!grant_type || grant_type === "authorization_code") {
let code = req.query.code || req.body.code;
let nonce = req.query.nonce || req.body.nonce;
const RefreshTokenRoute = Stacker(
GetClientAuthMiddleware(false, false, true),
async (req: Request, res: Response) => {
let grant_type = req.query.grant_type || req.body.grant_type;
if (!grant_type || grant_type === "authorization_code") {
let code = req.query.code || req.body.code;
let nonce = req.query.nonce || req.body.nonce;
let c = await ClientCode.findOne({ code: code })
if (!c || moment(c.validTill).isBefore()) {
throw new RequestError(req.__("Invalid code"), HttpStatusCode.BAD_REQUEST);
let c = await ClientCode.findOne({ code: code });
if (!c || moment(c.validTill).isBefore()) {
throw new RequestError(
req.__("Invalid code"),
HttpStatusCode.BAD_REQUEST
);
}
let client = await Client.findById(c.client);
let user = await User.findById(c.user);
let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m)));
let token = RefreshToken.new({
user: c.user,
client: c.client,
permissions: c.permissions,
token: randomBytes(16).toString("hex"),
valid: true,
validTill: moment().add(refreshTokenValidTime).toDate(),
});
await RefreshToken.save(token);
await ClientCode.delete(c);
let mail = mails.find((e) => e.primary);
if (!mail) mail = mails[0];
res.json({
refresh_token: token.token,
token: token.token,
access_token: await getAccessTokenJWT({
client: client,
user: user,
permissions: c.permissions,
}),
token_type: "bearer",
expires_in: AccessTokenJWTExp.asSeconds(),
profile: {
uid: user.uid,
email: mail ? mail.mail : "",
name: user.name,
enc_key: getEncryptionKey(user, client),
},
id_token: getIDToken(user, client.client_id, nonce),
});
} else if (grant_type === "refresh_token") {
let refresh_token = req.query.refresh_token || req.body.refresh_token;
if (!refresh_token)
throw new RequestError(
req.__("refresh_token not set"),
HttpStatusCode.BAD_REQUEST
);
let token = await RefreshToken.findOne({ token: refresh_token });
if (!token || !token.valid || moment(token.validTill).isBefore())
throw new RequestError(
req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST
);
token.validTill = moment().add(refreshTokenValidTime).toDate();
await RefreshToken.save(token);
let user = await User.findById(token.user);
let client = await Client.findById(token.client);
let jwt = await getAccessTokenJWT({
user,
client,
permissions: token.permissions,
});
res.json({
access_token: jwt,
expires_in: AccessTokenJWTExp.asSeconds(),
});
} else {
throw new RequestError(
"invalid grant_type",
HttpStatusCode.BAD_REQUEST
);
}
let client = await Client.findById(c.client);
let user = await User.findById(c.user);
let mails = await Promise.all(user.mails.map(m => Mail.findOne(m)));
let token = RefreshToken.new({
user: c.user,
client: c.client,
permissions: c.permissions,
token: randomBytes(16).toString("hex"),
valid: true,
validTill: moment().add(refreshTokenValidTime).toDate()
});
await RefreshToken.save(token);
await ClientCode.delete(c);
let mail = mails.find(e => e.primary);
if (!mail) mail = mails[0];
res.json({
refresh_token: token.token,
token: token.token,
access_token: await getAccessTokenJWT({
client: client,
user: user,
permissions: c.permissions
}),
token_type: "bearer",
expires_in: AccessTokenJWTExp.asSeconds(),
profile: {
uid: user.uid,
email: mail ? mail.mail : "",
name: user.name,
enc_key: getEncryptionKey(user, client)
},
id_token: getIDToken(user, client.client_id, nonce)
});
} else if (grant_type === "refresh_token") {
let refresh_token = req.query.refresh_token || req.body.refresh_token;
if (!refresh_token) throw new RequestError(req.__("refresh_token not set"), HttpStatusCode.BAD_REQUEST);
let token = await RefreshToken.findOne({ token: refresh_token });
if (!token || !token.valid || moment(token.validTill).isBefore())
throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
token.validTill = moment().add(refreshTokenValidTime).toDate();
await RefreshToken.save(token);
let user = await User.findById(token.user);
let client = await Client.findById(token.client)
let jwt = await getAccessTokenJWT({ user, client, permissions: token.permissions });
res.json({ access_token: jwt, expires_in: AccessTokenJWTExp.asSeconds() });
} else {
throw new RequestError("invalid grant_type", HttpStatusCode.BAD_REQUEST);
}
})
);
export default RefreshTokenRoute;
export default RefreshTokenRoute;

View File

@ -4,13 +4,16 @@ 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 });
});
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

@ -3,14 +3,17 @@ 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))
);
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 });
});
let contact = {
mails: mails.filter((e) => !!e),
phones: req.user.phones,
};
res.json({ contact });
}
);

View File

@ -41,20 +41,20 @@ 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
@ -62,16 +62,16 @@ UserRoute.post("/register", Register);
* @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.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
@ -84,25 +84,24 @@ 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
@ -126,4 +125,4 @@ UserRoute.get("/account", GetAccount);
* @apiSuccess {Object[]} user.phone Phone numbers
*/
UserRoute.get("/contact", GetContactInfos);
export default UserRoute;
export default UserRoute;

View File

@ -1,4 +1,4 @@
import { Request, Response } from "express"
import { Request, Response } from "express";
import User, { IUser } from "../../models/user";
import { randomBytes } from "crypto";
import moment = require("moment");
@ -12,36 +12,39 @@ 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 });
let user = await User.findOne(
username ? { username: username.toLowerCase() } : { uid: uid }
);
if (!user) {
res.json({ error: req.__("User not found") })
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 ip =
req.headers["x-forwarded-for"] || req.connection.remoteAddress;
let client = {
ip: Array.isArray(ip) ? ip[0] : ip,
browser: req.headers["user-agent"]
}
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 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
...client,
});
await LoginToken.save(token);
let special_str = randomBytes(24).toString("hex");
let special_exp = moment().add(30, "minutes").toDate()
let special_exp = moment().add(30, "minutes").toDate();
let special = LoginToken.new({
token: special_str,
valid: true,
@ -49,50 +52,74 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
special: true,
user: user._id,
validated: tfa ? false : true,
...client
...client,
});
await LoginToken.save(special);
res.json({
login: { token: token_str, expires: token.validTill.toUTCString() },
special: { token: special_str, expires: special.validTill.toUTCString() },
tfa
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 })
let user = await User.findOne(
username ? { username: username.toLowerCase() } : { uid: uid }
);
if (!user) {
res.json({ error: req.__("User not found") })
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!") });
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");
upw = crypto
.createHash("sha512")
.update(upw + date.toString())
.digest("hex");
}
}
if (upw !== password) {
res.json({ error: req.__("Password or username wrong") })
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);
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 => {
let tfa = twofactor.map((e) => {
return {
id: e._id,
name: e.name || TFANames.get(e.type),
type: e.type
}
})
type: e.type,
};
});
await sendToken(user, tfa);
} else {
await sendToken(user);
@ -104,4 +131,4 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
}
});
export default Login;
export default Login;

View File

@ -1,4 +1,4 @@
import { Request, Response, Router } from "express"
import { Request, Response, Router } from "express";
import Stacker from "../middlewares/stacker";
import verify, { Types } from "../middlewares/verify";
import promiseMiddleware from "../../helper/promiseMiddleware";
@ -7,138 +7,149 @@ import { HttpStatusCode } from "../../helper/request_error";
import Mail from "../../models/mail";
import RegCode from "../../models/regcodes";
const Register = Stacker(verify({
mail: {
type: Types.EMAIL,
notempty: true
},
username: {
type: Types.STRING,
notempty: true
},
password: {
type: Types.STRING,
notempty: true
},
salt: {
type: Types.STRING,
notempty: true
},
regcode: {
type: Types.STRING,
notempty: true
},
gender: {
type: Types.STRING,
notempty: true
},
name: {
type: Types.STRING,
notempty: true
},
// birthday: {
// type: Types.DATE
// }
}), promiseMiddleware(async (req: Request, res: Response) => {
let { username, password, salt, mail, gender, name, birthday, regcode } = req.body;
let u = await User.findOne({ username: username.toLowerCase() })
if (u) {
let err = {
message: [
{
message: req.__("Username taken"),
field: "username"
}
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true
const Register = Stacker(
verify({
mail: {
type: Types.EMAIL,
notempty: true,
},
username: {
type: Types.STRING,
notempty: true,
},
password: {
type: Types.STRING,
notempty: true,
},
salt: {
type: Types.STRING,
notempty: true,
},
regcode: {
type: Types.STRING,
notempty: true,
},
gender: {
type: Types.STRING,
notempty: true,
},
name: {
type: Types.STRING,
notempty: true,
},
// birthday: {
// type: Types.DATE
// }
}),
promiseMiddleware(async (req: Request, res: Response) => {
let {
username,
password,
salt,
mail,
gender,
name,
birthday,
regcode,
} = req.body;
let u = await User.findOne({ username: username.toLowerCase() });
if (u) {
let err = {
message: [
{
message: req.__("Username taken"),
field: "username",
},
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true,
};
throw err;
}
throw err;
}
let m = await Mail.findOne({ mail: mail })
if (m) {
let err = {
message: [
{
message: req.__("Mail linked with other account"),
field: "mail"
}
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true
let m = await Mail.findOne({ mail: mail });
if (m) {
let err = {
message: [
{
message: req.__("Mail linked with other account"),
field: "mail",
},
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true,
};
throw err;
}
throw err;
}
let regc = await RegCode.findOne({ token: regcode })
if (!regc) {
let err = {
message: [
{
message: req.__("Invalid registration code"),
field: "regcode"
}
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true
let regc = await RegCode.findOne({ token: regcode });
if (!regc) {
let err = {
message: [
{
message: req.__("Invalid registration code"),
field: "regcode",
},
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true,
};
throw err;
}
throw err;
}
if (!regc.valid) {
let err = {
message: [
{
message: req.__("Registration code already used"),
field: "regcode"
}
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true
if (!regc.valid) {
let err = {
message: [
{
message: req.__("Registration code already used"),
field: "regcode",
},
],
status: HttpStatusCode.BAD_REQUEST,
nolog: true,
};
throw err;
}
throw err;
}
let g = -1;
switch (gender) {
case "male":
g = Gender.male
break;
case "female":
g = Gender.female
break;
case "other":
g = Gender.other
break;
default:
g = Gender.none
break;
}
let g = -1;
switch (gender) {
case "male":
g = Gender.male;
break;
case "female":
g = Gender.female;
break;
case "other":
g = Gender.other;
break;
default:
g = Gender.none;
break;
}
let user = User.new({
username: username.toLowerCase(),
password: password,
salt: salt,
gender: g,
name: name,
// birthday: birthday,
admin: false
let user = User.new({
username: username.toLowerCase(),
password: password,
salt: salt,
gender: g,
name: name,
// birthday: birthday,
admin: false,
});
regc.valid = false;
await RegCode.save(regc);
let ml = Mail.new({
mail: mail,
primary: true,
});
await Mail.save(ml);
user.mails.push(ml._id);
await User.save(user);
res.json({ success: true });
})
regc.valid = false;
await RegCode.save(regc);
let ml = Mail.new({
mail: mail,
primary: true
})
await Mail.save(ml);
user.mails.push(ml._id);
await User.save(user)
res.json({ success: true });
}))
export default Register;
);
export default Register;

View File

@ -4,26 +4,42 @@ 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 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 });
});
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

@ -1,7 +1,10 @@
import { Router } from "express"
import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, { TFATypes as TwoFATypes, IBackupCode } from "../../../../models/twofactor";
import TwoFactor, {
TFATypes as TwoFATypes,
IBackupCode,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
@ -15,60 +18,83 @@ function generateCode(length: number) {
let bytes = crypto.randomBytes(length);
let nrs = "";
bytes.forEach((b, idx) => {
let nr = Math.floor((b / 255) * 9.9999)
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;
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);
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
code = code.replace(/\s/g, "");
let valid = twofactor.data.find(c => c === code);
res.json({
codes,
id: twofactor._id,
});
})
);
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);
}
}))
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

@ -6,8 +6,11 @@ export async function upgradeToken(token: ILoginToken) {
token.valid = true;
token.validated = true;
//TODO durations from config
let expires = (token.special ? moment().add(30, "minute") : moment().add(6, "months")).toDate();
let expires = (token.special
? moment().add(30, "minute")
: moment().add(6, "months")
).toDate();
token.validTill = expires;
await LoginToken.save(token);
return expires;
}
}

View File

@ -3,44 +3,54 @@ 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 * 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
}
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 });
})
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.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;
export default TwoFactorRouter;

View File

@ -1,7 +1,10 @@
import { Router } from "express"
import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, { TFATypes as TwoFATypes, IOTC } from "../../../../models/twofactor";
import TwoFactor, {
TFATypes as TwoFATypes,
IOTC,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
@ -13,93 +16,120 @@ 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;
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({ success: true });
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 Code!", HttpStatusCode.BAD_REQUEST);
throw new RequestError("Invalid type", 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);
}
}))
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,9 +1,12 @@
import { Router, Request } from "express"
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 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";
@ -15,120 +18,189 @@ 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);
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;
let twofactor = TwoFactor.new(<IYubiKey>{
user: req.user._id,
type: TwoFATypes.U2F,
valid: false,
data: {
registration: registrationRequest,
},
});
await TwoFactor.save(twofactor);
res.json({ success: true });
res.json({
request: registrationRequest,
id: twofactor._id,
appid: config.core.url,
});
} else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
}
}));
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();
}
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.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
if (!twofactor) {
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
}
const result = u2f.checkRegistration(
twofactor.data.registration,
response
);
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);
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
);
}
}
login_exp = await upgradeToken(login);
}
else {
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
}
res.json({ success: true, login_exp, special_exp })
}))
})
);
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;