First steps towards session support
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Fabian Stamm 2020-06-09 14:20:38 +02:00
parent 48b53f1b8b
commit fbfe8c63ed
8 changed files with 322 additions and 201 deletions

View File

@ -22,6 +22,3 @@ steps:
debug: true
when:
branch: master
event:
exclude:
- pull_request

View File

@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import LoginToken, { CheckToken } from "../../models/login_token";
import LoginToken, { CheckToken, ILoginToken } from "../../models/login_token";
import Logging from "@hibas123/nodelogging";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
@ -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
@ -50,25 +50,29 @@ export function GetUserMiddleware(
invalid("Login token invalid");
}
let special_token;
let special_token: ILoginToken;
let session: any;
if (special) {
Logging.debug("Special found");
special_token = await LoginToken.findOne({
token: special,
special: true,
valid: true,
user: token.user
user: token.user,
});
if (!(await CheckToken(special_token, validated)))
invalid("Special token invalid");
req.special = true;
//
}
req.user = user;
req.isAdmin = user.admin;
req.token = {
login: token,
special: special_token
special: special_token,
session,
};
if (next) next();

View File

@ -97,7 +97,7 @@ const GetAuthRoute = (view = false) =>
} = req.query;
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}`));
};
const scopes = scope.split(";");
@ -228,7 +228,7 @@ const GetAuthRoute = (view = false) =>
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}`;
if (nored === "true") {
res.json({
redirect_uri: ruri

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,125 @@ 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 && config.core.dev === true && code === "000000") {
Logging.warning("Dev mode enabled and dev OTC used.");
valid = true;
}
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,11 +1,19 @@
import SafeMongo from "@hibas123/safe_mongo";
import Config from "./config"
let dbname = "openauth"
let host = "localhost"
import Config from "./config";
let dbname = "openauth";
let host = "localhost";
if (Config.database) {
if (Config.database.database) dbname = Config.database.database;
if (Config.database.host) host = Config.database.host;
}
if (Config.core.dev) dbname += "_dev";
const DB = new SafeMongo("mongodb://" + host, dbname);
export default DB;
const DB = new SafeMongo("mongodb://" + host, dbname, {
useUnifiedTopology: true,
useNewUrlParser: true,
} as any);
export default DB;

5
src/express.d.ts vendored
View File

@ -11,6 +11,7 @@ declare module "express" {
token: {
login: ILoginToken;
special?: ILoginToken;
}
session?: { [key: string]: any };
};
}
}
}

View File

@ -1,30 +1,25 @@
import {
IRouter,
Request,
Router,
static as ServeStatic,
RequestHandler,
} from "express";
import * as Handlebars from "handlebars";
import * as moment from "moment";
import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user";
import GetAuthRoute from "../api/oauth/auth";
import config from "../config";
import { HttpStatusCode } from "../helper/request_error";
import GetAdminPage from "./admin";
import { Router, IRouter, Request, static as ServeStatic } from "express";
import GetLoginPage from "./login";
import GetAuthPage from "./authorize";
import promiseMiddleware from "../helper/promiseMiddleware";
import config from "../config";
import * as Handlebars from "handlebars";
import GetRegistrationPage from "./register";
import GetAdminPage from "./admin";
import { HttpStatusCode } from "../helper/request_error";
import * as moment from "moment";
import Permission, { IPermission } from "../models/permissions";
import Client from "../models/client";
import { Logging } from "@hibas123/nodelogging";
import Stacker from "../api/middlewares/stacker";
import { UserMiddleware, GetUserMiddleware } from "../api/middlewares/user";
// import GetUserPage from "./user";
Handlebars.registerHelper("appname", () => config.core.name);
const cacheTime = !config.core.dev
const cacheTime = config.core.dev
? moment.duration(1, "month").asSeconds()
: 1000;
const addCache: RequestHandler = (req, res, next) => {
res.setHeader("cache-control", "public, max-age=" + cacheTime);
next();
};
: 10;
const ViewRouter: IRouter = Router();
ViewRouter.get("/", UserMiddleware, (req, res) => {
@ -36,17 +31,9 @@ ViewRouter.get("/register", (req, res) => {
res.send(GetRegistrationPage(req.__));
});
ViewRouter.use(
"/login",
addCache,
ServeStatic("./views_repo/build/login", { cacheControl: false })
);
ViewRouter.use("/login", ServeStatic("./views_repo/build/login"));
ViewRouter.use(
"/user",
addCache,
ServeStatic("./views_repo/build/user", { cacheControl: false })
);
ViewRouter.use("/user", ServeStatic("./views_repo/build/user"));
ViewRouter.get("/code", (req, res) => {
res.setHeader("Cache-Control", "no-cache");
@ -66,7 +53,62 @@ ViewRouter.get(
}
);
ViewRouter.get("/auth", GetAuthRoute(true));
import GetAuthRoute from "../api/oauth/auth";
ViewRouter.get("/auth", GetAuthRoute(true))
// ViewRouter.get(
// "/auth",
// Stacker(GetUserMiddleware(false, true), async (req, res) => {
// let {
// scope,
// redirect_uri,
// state,
// client_id
// }: { [key: string]: string } = req.query;
// const sendError = type => {
// res.redirect((redirect_uri += `?error=${type}&state=${state}`));
// };
// let client = await Client.findOne({ client_id: client_id });
// if (!client) {
// return sendError("unauthorized_client");
// }
// let permissions: IPermission[] = [];
// let proms: PromiseLike<void>[] = [];
// if (scope) {
// for (let perm of scope.split(";").filter(e => e !== "read_user")) {
// proms.push(
// Permission.findById(perm).then(p => {
// if (!p) return Promise.reject(new Error());
// permissions.push(p);
// })
// );
// }
// }
// let err = false;
// await Promise.all(proms).catch(e => {
// err = true;
// });
// Logging.debug(err);
// if (err) {
// return sendError("invalid_scope");
// }
// let scopes = await Promise.all(
// permissions.map(async perm => {
// let client = await Client.findById(perm.client);
// return {
// name: perm.name,
// description: perm.description,
// logo: client.logo
// };
// })
// );
// res.send(GetAuthPage(req.__, client.name, scopes));
// })
// );
if (config.core.dev) {
const logo =
@ -78,20 +120,20 @@ if (config.core.dev) {
name: "Access Profile",
description:
"It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.",
logo: logo,
logo: logo
},
{
name: "Test 1",
description:
"This is not an real permission. This is used just to verify the layout",
logo: logo,
logo: logo
},
{
name: "Test 2",
description:
"This is not an real permission. This is used just to verify the layout",
logo: logo,
},
logo: logo
}
])
);
});

View File

@ -1,6 +1,6 @@
import request from "../../shared/request";
import getFormData from "../../shared/formdata";
Handlebars.registerHelper("humangender", function (value, options) {
Handlebars.registerHelper("humangender", function(value, options) {
switch (value) {
case 1:
return "male";
@ -14,8 +14,8 @@ Handlebars.registerHelper("humangender", function (value, options) {
}
});
// Deprecated since version 0.8.0
Handlebars.registerHelper("formatDate", function (datetime, format) {
// Deprecated since version 0.8.0
Handlebars.registerHelper("formatDate", function(datetime, format) {
return new Date(datetime).toLocaleString();
});
@ -26,9 +26,8 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
document.getElementById("sitename").innerText = title;
}
const cc = document.getElementById("custom_data")
const ccc = document.getElementById("custom_data_cont")
const cc = document.getElementById("custom_data");
const ccc = document.getElementById("custom_data_cont");
function setCustomCard(content) {
if (!content) {
@ -40,8 +39,8 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
}
}
const error_cont = document.getElementById("error_cont")
const error_msg = document.getElementById("error_msg")
const error_cont = document.getElementById("error_cont");
const error_msg = document.getElementById("error_msg");
function catchError(error) {
error_cont.style.display = "";
@ -50,40 +49,53 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
}
async function renderUser() {
console.log("Rendering User")
setTitle("User")
const listt = Handlebars.compile(document.getElementById("template-user-list").innerText)
console.log("Rendering User");
setTitle("User");
const listt = Handlebars.compile(
document.getElementById("template-user-list").innerText
);
async function loadList() {
let data = await request("/api/admin/user", "GET");
tableb.innerHTML = listt({
users: data
})
});
}
window.userOnChangeType = (id) => {
request("/api/admin/user?id=" + id, "PUT").then(() => loadList()).catch(catchError)
}
window.userOnChangeType = id => {
request("/api/admin/user?id=" + id, "PUT")
.then(() => loadList())
.catch(catchError);
};
window.deleteUser = (id) => {
request("/api/admin/user?id=" + id, "DELETE").then(() => loadList()).catch(catchError)
}
window.deleteUser = id => {
request("/api/admin/user?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
await loadList();
}
async function renderPermissions(client_id, client_name) {
const listt = Handlebars.compile(document.getElementById("template-permission-list").innerText);
const formt = Handlebars.compile(document.getElementById("template-permission-form").innerText);
const listt = Handlebars.compile(
document.getElementById("template-permission-list").innerText
);
const formt = Handlebars.compile(
document.getElementById("template-permission-form").innerText
);
setCustomCard();
async function loadList() {
try {
let data = await request("/api/admin/permission?client=" + client_id, "GET");
let data = await request(
"/api/admin/permission?client=" + client_id,
"GET"
);
tableb.innerHTML = listt({
client_id: client_id,
client_name: client_name,
permissions: data
})
});
} catch (err) {
catchError(err);
}
@ -91,11 +103,13 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
window.gotoClients = () => {
renderClient();
}
};
window.deletePermission = (id) => {
request("/api/admin/permission?id=" + id, "DELETE").then(() => loadList()).catch(catchError)
}
window.deletePermission = id => {
request("/api/admin/permission?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
window.createPermission = () => {
try {
@ -103,24 +117,30 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
} catch (err) {
console.log("Err", err);
}
}
};
window.createPermissionSubmit = (elm) => {
window.createPermissionSubmit = elm => {
console.log(elm);
let data = getFormData(elm);
console.log(data);
request("/api/admin/permission", "POST", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError)
}
await loadList()
request("/api/admin/permission", "POST", data)
.then(() => setCustomCard())
.then(() => loadList())
.catch(catchError);
};
await loadList();
}
async function renderClient() {
console.log("Rendering Client")
setTitle("Client")
console.log("Rendering Client");
setTitle("Client");
const listt = Handlebars.compile(document.getElementById("template-client-list").innerText)
const formt = Handlebars.compile(document.getElementById("template-client-form").innerText)
const listt = Handlebars.compile(
document.getElementById("template-client-list").innerText
);
const formt = Handlebars.compile(
document.getElementById("template-client-form").innerText
);
let clients = [];
async function loadList() {
@ -128,63 +148,77 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
clients = data;
tableb.innerHTML = listt({
clients: data
})
});
}
window.permissionsClient = (id) => {
window.permissionsClient = id => {
renderPermissions(id, clients.find(e => e._id === id).name);
}
};
window.deleteClient = (id) => {
request("/api/admin/client/id=" + id, "DELETE").then(() => loadList()).catch(catchError)
}
window.deleteClient = id => {
request("/api/admin/client?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
window.createClientSubmit = (elm) => {
window.createClientSubmit = elm => {
console.log(elm);
let data = getFormData(elm);
console.log(data);
let id = data.id;
delete data.id;
if (id && id !== "") {
request("/api/admin/client?id=" + id, "PUT", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError)
request("/api/admin/client?id=" + id, "PUT", data)
.then(() => setCustomCard())
.then(() => loadList())
.catch(catchError);
} else {
request("/api/admin/client", "POST", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError)
request("/api/admin/client", "POST", data)
.then(() => setCustomCard())
.then(() => loadList())
.catch(catchError);
}
}
};
window.createClient = () => {
setCustomCard(formt());
}
};
window.editClient = (id) => {
window.editClient = id => {
let client = clients.find(e => e._id === id);
if (!client) return catchError(new Error("Client does not exist!!"))
if (!client) return catchError(new Error("Client does not exist!!"));
setCustomCard(formt(client));
}
};
await loadList().catch(catchError);
}
async function renderRegCode() {
console.log("Rendering RegCode")
setTitle("RegCode")
console.log("Rendering RegCode");
setTitle("RegCode");
const listt = Handlebars.compile(document.getElementById("template-regcode-list").innerText)
const listt = Handlebars.compile(
document.getElementById("template-regcode-list").innerText
);
async function loadList() {
let data = await request("/api/admin/regcode", "GET");
tableb.innerHTML = listt({
regcodes: data
})
});
}
window.deleteRegcode = (id) => {
request("/api/admin/regcode?id=" + id, "DELETE").then(() => loadList()).catch(catchError)
}
window.deleteRegcode = id => {
request("/api/admin/regcode?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
window.createRegcode = () => {
request("/api/admin/regcode", "POST").then(() => loadList()).catch(catchError);
}
request("/api/admin/regcode", "POST")
.then(() => loadList())
.catch(catchError);
};
await loadList().catch(catchError);
}
@ -192,14 +226,14 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
const type = new URL(window.location.href).searchParams.get("type");
switch (type) {
case "client":
renderClient().catch(catchError)
break
renderClient().catch(catchError);
break;
case "regcode":
renderRegCode().catch(catchError)
renderRegCode().catch(catchError);
break;
case "user":
default:
renderUser().catch(catchError);
break;
}
})()
})();