Compare commits

...

2 Commits

Author SHA1 Message Date
Fabian Stamm
fa73bb9601 Switch to most recent views_repo
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-09 15:40:17 +02:00
Fabian Stamm
fbfe8c63ed First steps towards session support
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-09 14:20:38 +02:00
9 changed files with 323 additions and 202 deletions

View File

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

View File

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

View File

@ -97,7 +97,7 @@ const GetAuthRoute = (view = false) =>
} = req.query; } = req.query;
const sendError = type => { const sendError = type => {
if (redirect_uri === "$local") redirect_uri = "/code"; 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(";"); const scopes = scope.split(";");
@ -228,7 +228,7 @@ const GetAuthRoute = (view = false) =>
let redir = let redir =
client.redirect_url === "$local" ? "/code" : client.redirect_url; 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") { if (nored === "true") {
res.json({ res.json({
redirect_uri: ruri redirect_uri: ruri

View File

@ -1,7 +1,10 @@
import { Router } from "express" import { Router } from "express";
import Stacker from "../../../middlewares/stacker"; import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user"; 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 RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment"); import moment = require("moment");
import { upgradeToken } from "../helper"; import { upgradeToken } from "../helper";
@ -13,93 +16,125 @@ import config from "../../../../config";
const OTCRoute = Router(); const OTCRoute = Router();
OTCRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => { OTCRoute.post(
const { type } = req.query; "/",
if (type === "create") { Stacker(GetUserMiddleware(true, true), async (req, res) => {
//Generating new const { type } = req.query;
let secret = speakeasy.generateSecret({ if (type === "create") {
name: config.core.name, //Generating new
issuer: config.core.name let secret = speakeasy.generateSecret({
}); name: config.core.name,
let twofactor = TwoFactor.new(<IOTC>{ issuer: config.core.name,
user: req.user._id, });
type: TwoFATypes.OTC, let twofactor = TwoFactor.new(<IOTC>{
valid: false, user: req.user._id,
data: secret.base32 type: TwoFATypes.OTC,
}) valid: false,
let dataurl = await qrcode.toDataURL(secret.otpauth_url); data: secret.base32,
await TwoFactor.save(twofactor); });
res.json({ let dataurl = await qrcode.toDataURL(secret.otpauth_url);
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); 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 { } 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) { OTCRoute.put(
let [login_exp, special_exp] = await Promise.all([ "/",
upgradeToken(login), Stacker(
upgradeToken(special) GetUserMiddleware(true, false, undefined, false),
]); async (req, res) => {
res.json({ success: true, login_exp, special_exp }) let { login, special } = req.token;
} else { let { id, code } = req.body;
throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST); 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; export default OTCRoute;

View File

@ -1,11 +1,19 @@
import SafeMongo from "@hibas123/safe_mongo"; import SafeMongo from "@hibas123/safe_mongo";
import Config from "./config" import Config from "./config";
let dbname = "openauth"
let host = "localhost" let dbname = "openauth";
let host = "localhost";
if (Config.database) { if (Config.database) {
if (Config.database.database) dbname = Config.database.database; if (Config.database.database) dbname = Config.database.database;
if (Config.database.host) host = Config.database.host; if (Config.database.host) host = Config.database.host;
} }
if (Config.core.dev) dbname += "_dev"; if (Config.core.dev) dbname += "_dev";
const DB = new SafeMongo("mongodb://" + host, dbname);
const DB = new SafeMongo("mongodb://" + host, dbname, {
useUnifiedTopology: true,
useNewUrlParser: true,
} as any);
export default DB; export default DB;

3
src/express.d.ts vendored
View File

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

View File

@ -1,30 +1,25 @@
import { import { Router, IRouter, Request, static as ServeStatic } from "express";
IRouter, import GetLoginPage from "./login";
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 GetAuthPage from "./authorize"; import GetAuthPage from "./authorize";
import promiseMiddleware from "../helper/promiseMiddleware";
import config from "../config";
import * as Handlebars from "handlebars";
import GetRegistrationPage from "./register"; 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); Handlebars.registerHelper("appname", () => config.core.name);
const cacheTime = !config.core.dev const cacheTime = config.core.dev
? moment.duration(1, "month").asSeconds() ? moment.duration(1, "month").asSeconds()
: 1000; : 10;
const addCache: RequestHandler = (req, res, next) => {
res.setHeader("cache-control", "public, max-age=" + cacheTime);
next();
};
const ViewRouter: IRouter = Router(); const ViewRouter: IRouter = Router();
ViewRouter.get("/", UserMiddleware, (req, res) => { ViewRouter.get("/", UserMiddleware, (req, res) => {
@ -36,17 +31,9 @@ ViewRouter.get("/register", (req, res) => {
res.send(GetRegistrationPage(req.__)); res.send(GetRegistrationPage(req.__));
}); });
ViewRouter.use( ViewRouter.use("/login", ServeStatic("./views_repo/build/login"));
"/login",
addCache,
ServeStatic("./views_repo/build/login", { cacheControl: false })
);
ViewRouter.use( ViewRouter.use("/user", ServeStatic("./views_repo/build/user"));
"/user",
addCache,
ServeStatic("./views_repo/build/user", { cacheControl: false })
);
ViewRouter.get("/code", (req, res) => { ViewRouter.get("/code", (req, res) => {
res.setHeader("Cache-Control", "no-cache"); 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) { if (config.core.dev) {
const logo = const logo =
@ -78,20 +120,20 @@ if (config.core.dev) {
name: "Access Profile", name: "Access Profile",
description: 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.", "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", name: "Test 1",
description: description:
"This is not an real permission. This is used just to verify the layout", "This is not an real permission. This is used just to verify the layout",
logo: logo, logo: logo
}, },
{ {
name: "Test 2", name: "Test 2",
description: description:
"This is not an real permission. This is used just to verify the layout", "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 request from "../../shared/request";
import getFormData from "../../shared/formdata"; import getFormData from "../../shared/formdata";
Handlebars.registerHelper("humangender", function (value, options) { Handlebars.registerHelper("humangender", function(value, options) {
switch (value) { switch (value) {
case 1: case 1:
return "male"; return "male";
@ -15,7 +15,7 @@ Handlebars.registerHelper("humangender", function (value, options) {
}); });
// Deprecated since version 0.8.0 // Deprecated since version 0.8.0
Handlebars.registerHelper("formatDate", function (datetime, format) { Handlebars.registerHelper("formatDate", function(datetime, format) {
return new Date(datetime).toLocaleString(); return new Date(datetime).toLocaleString();
}); });
@ -26,9 +26,8 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
document.getElementById("sitename").innerText = title; document.getElementById("sitename").innerText = title;
} }
const cc = document.getElementById("custom_data");
const cc = document.getElementById("custom_data") const ccc = document.getElementById("custom_data_cont");
const ccc = document.getElementById("custom_data_cont")
function setCustomCard(content) { function setCustomCard(content) {
if (!content) { if (!content) {
@ -40,8 +39,8 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
} }
} }
const error_cont = document.getElementById("error_cont") const error_cont = document.getElementById("error_cont");
const error_msg = document.getElementById("error_msg") const error_msg = document.getElementById("error_msg");
function catchError(error) { function catchError(error) {
error_cont.style.display = ""; error_cont.style.display = "";
@ -50,40 +49,53 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
} }
async function renderUser() { async function renderUser() {
console.log("Rendering User") console.log("Rendering User");
setTitle("User") setTitle("User");
const listt = Handlebars.compile(document.getElementById("template-user-list").innerText) const listt = Handlebars.compile(
document.getElementById("template-user-list").innerText
);
async function loadList() { async function loadList() {
let data = await request("/api/admin/user", "GET"); let data = await request("/api/admin/user", "GET");
tableb.innerHTML = listt({ tableb.innerHTML = listt({
users: data users: data
}) });
} }
window.userOnChangeType = (id) => { window.userOnChangeType = id => {
request("/api/admin/user?id=" + id, "PUT").then(() => loadList()).catch(catchError) request("/api/admin/user?id=" + id, "PUT")
} .then(() => loadList())
.catch(catchError);
};
window.deleteUser = (id) => { window.deleteUser = id => {
request("/api/admin/user?id=" + id, "DELETE").then(() => loadList()).catch(catchError) request("/api/admin/user?id=" + id, "DELETE")
} .then(() => loadList())
.catch(catchError);
};
await loadList(); await loadList();
} }
async function renderPermissions(client_id, client_name) { async function renderPermissions(client_id, client_name) {
const listt = Handlebars.compile(document.getElementById("template-permission-list").innerText); const listt = Handlebars.compile(
const formt = Handlebars.compile(document.getElementById("template-permission-form").innerText); document.getElementById("template-permission-list").innerText
);
const formt = Handlebars.compile(
document.getElementById("template-permission-form").innerText
);
setCustomCard(); setCustomCard();
async function loadList() { async function loadList() {
try { 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({ tableb.innerHTML = listt({
client_id: client_id, client_id: client_id,
client_name: client_name, client_name: client_name,
permissions: data permissions: data
}) });
} catch (err) { } catch (err) {
catchError(err); catchError(err);
} }
@ -91,11 +103,13 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
window.gotoClients = () => { window.gotoClients = () => {
renderClient(); renderClient();
} };
window.deletePermission = (id) => { window.deletePermission = id => {
request("/api/admin/permission?id=" + id, "DELETE").then(() => loadList()).catch(catchError) request("/api/admin/permission?id=" + id, "DELETE")
} .then(() => loadList())
.catch(catchError);
};
window.createPermission = () => { window.createPermission = () => {
try { try {
@ -103,24 +117,30 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
} catch (err) { } catch (err) {
console.log("Err", err); console.log("Err", err);
} }
} };
window.createPermissionSubmit = elm => {
window.createPermissionSubmit = (elm) => {
console.log(elm); console.log(elm);
let data = getFormData(elm); let data = getFormData(elm);
console.log(data); console.log(data);
request("/api/admin/permission", "POST", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError) request("/api/admin/permission", "POST", data)
} .then(() => setCustomCard())
await loadList() .then(() => loadList())
.catch(catchError);
};
await loadList();
} }
async function renderClient() { async function renderClient() {
console.log("Rendering Client") console.log("Rendering Client");
setTitle("Client") setTitle("Client");
const listt = Handlebars.compile(document.getElementById("template-client-list").innerText) const listt = Handlebars.compile(
const formt = Handlebars.compile(document.getElementById("template-client-form").innerText) document.getElementById("template-client-list").innerText
);
const formt = Handlebars.compile(
document.getElementById("template-client-form").innerText
);
let clients = []; let clients = [];
async function loadList() { async function loadList() {
@ -128,63 +148,77 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
clients = data; clients = data;
tableb.innerHTML = listt({ tableb.innerHTML = listt({
clients: data clients: data
}) });
} }
window.permissionsClient = (id) => { window.permissionsClient = id => {
renderPermissions(id, clients.find(e => e._id === id).name); renderPermissions(id, clients.find(e => e._id === id).name);
} };
window.deleteClient = (id) => { window.deleteClient = id => {
request("/api/admin/client/id=" + id, "DELETE").then(() => loadList()).catch(catchError) request("/api/admin/client?id=" + id, "DELETE")
} .then(() => loadList())
.catch(catchError);
};
window.createClientSubmit = (elm) => { window.createClientSubmit = elm => {
console.log(elm); console.log(elm);
let data = getFormData(elm); let data = getFormData(elm);
console.log(data); console.log(data);
let id = data.id; let id = data.id;
delete data.id; delete data.id;
if (id && 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 { } 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 = () => { window.createClient = () => {
setCustomCard(formt()); setCustomCard(formt());
} };
window.editClient = (id) => { window.editClient = id => {
let client = clients.find(e => e._id === 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)); setCustomCard(formt(client));
} };
await loadList().catch(catchError); await loadList().catch(catchError);
} }
async function renderRegCode() { async function renderRegCode() {
console.log("Rendering RegCode") console.log("Rendering RegCode");
setTitle("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() { async function loadList() {
let data = await request("/api/admin/regcode", "GET"); let data = await request("/api/admin/regcode", "GET");
tableb.innerHTML = listt({ tableb.innerHTML = listt({
regcodes: data regcodes: data
}) });
} }
window.deleteRegcode = (id) => { window.deleteRegcode = id => {
request("/api/admin/regcode?id=" + id, "DELETE").then(() => loadList()).catch(catchError) request("/api/admin/regcode?id=" + id, "DELETE")
} .then(() => loadList())
.catch(catchError);
};
window.createRegcode = () => { window.createRegcode = () => {
request("/api/admin/regcode", "POST").then(() => loadList()).catch(catchError); request("/api/admin/regcode", "POST")
} .then(() => loadList())
.catch(catchError);
};
await 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"); const type = new URL(window.location.href).searchParams.get("type");
switch (type) { switch (type) {
case "client": case "client":
renderClient().catch(catchError) renderClient().catch(catchError);
break break;
case "regcode": case "regcode":
renderRegCode().catch(catchError) renderRegCode().catch(catchError);
break; break;
case "user": case "user":
default: default:
renderUser().catch(catchError); renderUser().catch(catchError);
break; break;
} }
})() })();

@ -1 +1 @@
Subproject commit 9668c203d2599a3201809bf5ca2e0746d25db713 Subproject commit e4d08dcbf93f58ff85f54f7770ae7db490ff14d6