add "request key" mechanism to protect resources such as auth
This commit is contained in:
parent
0c6c88f7f5
commit
15d22f261c
4 changed files with 101 additions and 57 deletions
|
@ -16,21 +16,13 @@
|
|||
});
|
||||
};
|
||||
|
||||
const doSuperuserPrompt = () => {
|
||||
overlayStore.open("prompt", {
|
||||
heading: "Become Superuser",
|
||||
valueName: "Superuser Key",
|
||||
async onSubmit(value) {
|
||||
const { ok } = await request("POST", apiRoute("users/self/promote"), true, {
|
||||
key: value
|
||||
});
|
||||
if (ok) {
|
||||
overlayStore.open("toast", { message: "You have been promoted to superuser" });
|
||||
} else {
|
||||
overlayStore.open("toast", { message: "Failed to promote to superuser" });
|
||||
}
|
||||
}
|
||||
});
|
||||
const doSuperuserPrompt = async () => {
|
||||
const { ok } = await request("POST", apiRoute("users/self/promote"), true);
|
||||
if (ok) {
|
||||
overlayStore.open("toast", { message: "You have been promoted to superuser" });
|
||||
} else {
|
||||
overlayStore.open("toast", { message: "Failed to promote to superuser" });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { getItem } from "./storage";
|
||||
// TODO: circular dependency
|
||||
import { overlayStore } from "./stores";
|
||||
|
||||
export function compatibleFetch(endpoint, options) {
|
||||
if (window.fetch && typeof window.fetch === "function") {
|
||||
|
@ -30,43 +32,71 @@ export function compatibleFetch(endpoint, options) {
|
|||
}
|
||||
}
|
||||
|
||||
export default async function(method, endpoint, auth=true, body=null) {
|
||||
const options = {
|
||||
method,
|
||||
};
|
||||
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
options.headers = {
|
||||
...options.headers || {},
|
||||
"Content-Type": "application/json"
|
||||
export default function doRequest(method, endpoint, auth=true, body=null, _keyEntryDepth=false) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const options = {
|
||||
method,
|
||||
};
|
||||
}
|
||||
|
||||
if (auth) {
|
||||
const token = getItem("auth:token");
|
||||
if (token) {
|
||||
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
options.headers = {
|
||||
...options.headers || {},
|
||||
"Authorization": `Bearer ${token}`
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await compatibleFetch(endpoint, options);
|
||||
return {
|
||||
success: true,
|
||||
json: res.status === 204 ? null : await res.json(),
|
||||
ok: res.ok,
|
||||
status: res.status
|
||||
|
||||
if (auth) {
|
||||
const token = getItem("auth:token");
|
||||
if (token) {
|
||||
options.headers = {
|
||||
...options.headers || {},
|
||||
"Authorization": `Bearer ${token}`
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
json: null,
|
||||
ok: false,
|
||||
status: null
|
||||
|
||||
try {
|
||||
const res = await compatibleFetch(endpoint, options);
|
||||
const json = res.status === 204 ? {} : await res.json();
|
||||
|
||||
if (res.status === 403 && json.code && json.code === 6006 && !_keyEntryDepth) {
|
||||
// This endpoint is password-protected
|
||||
overlayStore.open("prompt", {
|
||||
heading: "Enter Key For Resource",
|
||||
valueName: "Key",
|
||||
async onSubmit(value) {
|
||||
const response = await doRequest(method, endpoint, auth, {
|
||||
...(body || {}),
|
||||
requestKey: value
|
||||
}, true);
|
||||
resolve(response);
|
||||
},
|
||||
onClose() {
|
||||
resolve({
|
||||
success: true,
|
||||
json,
|
||||
ok: res.ok,
|
||||
status: res.status
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return resolve({
|
||||
success: true,
|
||||
json,
|
||||
ok: res.ok,
|
||||
status: res.status
|
||||
});
|
||||
} catch (e) {
|
||||
return resolve({
|
||||
success: false,
|
||||
json: null,
|
||||
ok: false,
|
||||
status: null
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ export const errors = {
|
|||
BAD_AUTH: { code: 6003, message: "Bad authentication" },
|
||||
NOT_FOUND: { code: 6004, message: "Not found" },
|
||||
FORBIDDEN_DUE_TO_MISSING_PERMISSIONS: { code: 6005, message: "Forbidden due to missing permission(s)" },
|
||||
BAD_SUPERUSER_KEY: { code: 6006, message: "Bad superuser key" },
|
||||
BAD_REQUEST_KEY: { code: 6006, message: "Bad request key" },
|
||||
GOT_NO_DATABASE_DATA: { code: 7001, message: "Unexpectedly got no data from database" },
|
||||
FEATURE_DISABLED: { code: 7002, message: "This feature is disabled" },
|
||||
INTERNAL_ERROR: { code: 7003, message: "Internal server error" }
|
||||
|
|
|
@ -2,14 +2,13 @@ import { errors } from "../../../errors";
|
|||
import { query } from "../../../database";
|
||||
import express from "express";
|
||||
import { body, validationResult } from "express-validator";
|
||||
import { compare, hash } from "bcrypt";
|
||||
import { compare, hash, hashSync } from "bcrypt";
|
||||
import { authenticateRoute, signToken } from "../../../auth";
|
||||
import { dispatch } from "../../../gateway";
|
||||
import { GatewayPayloadType } from "../../../gateway/gatewaypayloadtype";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const superuserKey = process.env.SUPERUSER_KEY || "";
|
||||
const superuserKey = process.env.SUPERUSER_KEY ? hashSync(process.env.SUPERUSER_KEY, 10) : null;
|
||||
const authRequestKey = process.env.AUTH_REQUEST_KEY ? hashSync(process.env.AUTH_REQUEST_KEY, 10) : null;
|
||||
|
||||
router.post(
|
||||
"/register",
|
||||
|
@ -20,6 +19,16 @@ router.post(
|
|||
return res.status(403).json({ ...errors.FEATURE_DISABLED });
|
||||
}
|
||||
|
||||
if (authRequestKey) {
|
||||
if (!req.body.requestKey) {
|
||||
return res.status(403).json({ ...errors.BAD_REQUEST_KEY });
|
||||
}
|
||||
const result = await compare(req.body.requestKey, authRequestKey);
|
||||
if (!result) {
|
||||
return res.status(403).json({ ...errors.BAD_REQUEST_KEY });
|
||||
}
|
||||
}
|
||||
|
||||
const validationErrors = validationResult(req);
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
|
||||
|
@ -52,6 +61,16 @@ router.post(
|
|||
body("username").isLength({ min: 3, max: 32 }).isAlphanumeric("en-US", { ignore: " _-" }),
|
||||
body("password").isLength({ min: 8, max: 1000 }),
|
||||
async (req, res) => {
|
||||
if (authRequestKey) {
|
||||
if (!req.body.requestKey) {
|
||||
return res.status(403).json({ ...errors.BAD_REQUEST_KEY });
|
||||
}
|
||||
const result = await compare(req.body.requestKey, authRequestKey);
|
||||
if (!result) {
|
||||
return res.status(403).json({ ...errors.BAD_REQUEST_KEY });
|
||||
}
|
||||
}
|
||||
|
||||
const validationErrors = validationResult(req);
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.status(400).json({ ...errors.BAD_LOGIN_CREDENTIALS });
|
||||
|
@ -85,17 +104,20 @@ router.get(
|
|||
router.post(
|
||||
"/self/promote",
|
||||
authenticateRoute(),
|
||||
body("key").isLength({ min: 1, max: 3000 }),
|
||||
body("requestKey").isLength({ min: 1, max: 3000 }),
|
||||
async (req, res) => {
|
||||
if (!superuserKey) {
|
||||
return res.status(403).json({ ...errors.FEATURE_DISABLED });
|
||||
}
|
||||
|
||||
const validationErrors = validationResult(req);
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.status(403).json({ ...errors.BAD_SUPERUSER_KEY });
|
||||
return res.status(403).json({ ...errors.BAD_REQUEST_KEY });
|
||||
}
|
||||
|
||||
const { key } = req.body;
|
||||
const matches = await compare(req.body.requestKey, superuserKey);
|
||||
|
||||
if (superuserKey && superuserKey.length >= 1 && key === superuserKey && req.user) {
|
||||
if (matches) {
|
||||
const updateUserResult = await query("UPDATE users SET is_superuser = true WHERE id = $1", [req.user.id]);
|
||||
if (!updateUserResult || updateUserResult.rowCount < 1) {
|
||||
return res.status(500).json({
|
||||
|
@ -105,7 +127,7 @@ router.post(
|
|||
return res.status(200).json({});
|
||||
}
|
||||
|
||||
return res.status(403).json({ ...errors.BAD_SUPERUSER_KEY });
|
||||
return res.status(403).json({ ...errors.BAD_REQUEST_KEY });
|
||||
}
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue