diff --git a/core/src/routes/deployments.ts b/core/src/routes/deployments.ts
index b95adc166..3ea8d225e 100644
--- a/core/src/routes/deployments.ts
+++ b/core/src/routes/deployments.ts
@@ -66,6 +66,18 @@ const deployments = fp((app: FastifyInstance, _: {}, done: () => void) => {
res.send("could not find deployment");
return;
}
+ const user = await app.users.findById(
+ req.user.id,
+ "username permissions"
+ );
+ if (
+ !user ||
+ (user?.permissions! < 2 && !deployment.owners.includes(user.username))
+ ) {
+ res.status(403);
+ res.send("user does not have permissions to view this information");
+ return;
+ }
const onCore = deployment.serverID === app.core._id;
const server = onCore
? app.core
@@ -90,13 +102,25 @@ const deployments = fp((app: FastifyInstance, _: {}, done: () => void) => {
const { tail } = req.query as { tail?: number };
const deployment = await app.deployments.findById(
id,
- "serverID containerName"
+ "serverID containerName owners"
);
if (!deployment) {
res.status(400);
res.send("could not find deployment");
return;
}
+ const user = await app.users.findById(
+ req.user.id,
+ "username permissions"
+ );
+ if (
+ !user ||
+ (user?.permissions! < 2 && !deployment.owners.includes(user.username))
+ ) {
+ res.status(403);
+ res.send("user does not have permissions to view this log");
+ return;
+ }
const onCore = deployment.serverID === app.core._id;
const server = onCore
? app.core
@@ -141,6 +165,7 @@ const deployments = fp((app: FastifyInstance, _: {}, done: () => void) => {
) {
res.status(403);
res.send("user not authorized for this action");
+ return;
}
const server = await app.servers.findById(deployment.serverID!);
if (!server) {
diff --git a/core/src/routes/updates.ts b/core/src/routes/updates.ts
index 15fb4af5f..dd4a7df2c 100644
--- a/core/src/routes/updates.ts
+++ b/core/src/routes/updates.ts
@@ -1,5 +1,6 @@
import { FastifyInstance } from "fastify";
import fp from "fastify-plugin";
+import removeServer from "../messages/servers/remove";
const updates = fp((app: FastifyInstance, _: {}, done: () => void) => {
app.get(
@@ -13,18 +14,109 @@ const updates = fp((app: FastifyInstance, _: {}, done: () => void) => {
serverID?: string;
deploymentID?: string;
};
- const updates = await app.updates.getMostRecent(
- 10,
- buildID
- ? { buildID }
- : deploymentID
- ? { deploymentID }
- : serverID
- ? { serverID }
- : {},
- offset
+ const user = await app.users.findById(
+ req.user.id,
+ "username permissions"
);
- res.send(updates);
+ if (!user) {
+ res.status(400);
+ res.send("user not found");
+ return;
+ }
+ if (user.permissions! < 1) {
+ res.status(403);
+ res.send("user does not have permission to access this information");
+ return;
+ }
+ if (user.permissions! > 1) {
+ const updates = await app.updates.getMostRecent(
+ 10,
+ buildID
+ ? { buildID }
+ : deploymentID
+ ? { deploymentID }
+ : serverID
+ ? { serverID }
+ : {},
+ offset
+ );
+ res.send(updates);
+ return;
+ }
+ if (buildID) {
+ const build = await app.builds.findById(buildID, "owners");
+ if (!build || !build.owners.includes(user.username)) {
+ res.status(403);
+ res.send("user does not have permission to access this data");
+ return;
+ }
+ const updates = await app.updates.getMostRecent(
+ 10,
+ { buildID },
+ offset
+ );
+ res.send(updates);
+ } else if (deploymentID) {
+ const deployment = await app.deployments.findById(
+ deploymentID,
+ "owners"
+ );
+ if (!deployment || !deployment.owners.includes(user.username)) {
+ res.status(403);
+ res.send("user does not have permission to access this data");
+ return;
+ }
+ const updates = await app.updates.getMostRecent(
+ 10,
+ { deploymentID },
+ offset
+ );
+ res.send(updates);
+ } else if (serverID) {
+ const server = await app.servers.findById(serverID, "owners");
+ if (!server || !server.owners.includes(user.username)) {
+ res.status(403);
+ res.send("user does not have permission to access this data");
+ return;
+ }
+ const updates = await app.updates.getMostRecent(
+ 10,
+ { serverID },
+ offset
+ );
+ res.send(updates);
+ } else {
+ const deploymentIDs = (
+ await app.deployments.find(
+ { owners: { $elemMatch: { $eq: user.username } } },
+ "_id"
+ )
+ ).map((dep) => dep._id);
+ const buildIDs = (
+ await app.builds.find(
+ { owners: { $elemMatch: { $eq: user.username } } },
+ "_id"
+ )
+ ).map((build) => build._id);
+ const serverIDs = (
+ await app.servers.find(
+ { owners: { $elemMatch: { $eq: user.username } } },
+ "_id"
+ )
+ ).map((server) => server._id);
+ const updates = await app.updates.getMostRecent(
+ 10,
+ {
+ $or: [
+ { deploymentID: { $in: deploymentIDs } },
+ { buildID: { $in: buildIDs } },
+ { serverID: { $in: serverIDs } },
+ ],
+ },
+ offset
+ );
+ res.send(updates);
+ }
}
);
done();
diff --git a/frontend/src/components/builds/Build.tsx b/frontend/src/components/builds/Build.tsx
index e455a457e..4ce0b7175 100644
--- a/frontend/src/components/builds/Build.tsx
+++ b/frontend/src/components/builds/Build.tsx
@@ -2,6 +2,7 @@ import { Component, Show } from "solid-js";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useTheme } from "../../state/ThemeProvider";
+import { useUser } from "../../state/UserProvider";
import { combineClasses } from "../../util/helpers";
import NotFound from "../NotFound";
import Grid from "../util/layout/Grid";
@@ -16,6 +17,16 @@ const Build: Component<{}> = (p) => {
const build = () => builds.get(selected.id())!;
const { themeClass } = useTheme();
const { isMobile } = useAppDimensions();
+ const { permissions, username } = useUser();
+ const userCanUpdate = () => {
+ if (permissions() > 1) {
+ return true;
+ } else if (permissions() > 0 && build()!.owners.includes(username()!)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
return (
}>
@@ -24,7 +35,7 @@ const Build: Component<{}> = (p) => {
-
+
diff --git a/frontend/src/components/builds/Header.tsx b/frontend/src/components/builds/Header.tsx
index b4202da1d..0de07f7ee 100644
--- a/frontend/src/components/builds/Header.tsx
+++ b/frontend/src/components/builds/Header.tsx
@@ -23,6 +23,15 @@ const Header: Component<{}> = (p) => {
const { isMobile } = useAppDimensions();
const [showUpdates, toggleShowUpdates] =
useLocalStorageToggle("show-updates");
+ const userCanUpdate = () => {
+ if (permissions() > 1) {
+ return true;
+ } else if (permissions() > 0 && build()!.owners.includes(username()!)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
return (
<>
= (p) => {
alignItems="center"
style={{
position: "relative",
- cursor: isMobile() ? "pointer" : undefined,
+ cursor: isMobile() && userCanUpdate() ? "pointer" : undefined,
}}
onClick={() => {
- if (isMobile()) toggleShowUpdates();
+ if (isMobile() && userCanUpdate()) toggleShowUpdates();
}}
>
{build().name}
{getSub(build())}
- = 2 || build().owners.includes(username()!)}>
+
= (p) => {
-
+
updates{" "}
= (p) => {
-
+
>
diff --git a/frontend/src/components/deployment/Deployment.tsx b/frontend/src/components/deployment/Deployment.tsx
index 132a4c1bf..2c2ae9a37 100644
--- a/frontend/src/components/deployment/Deployment.tsx
+++ b/frontend/src/components/deployment/Deployment.tsx
@@ -2,13 +2,14 @@ import { Component, Show } from "solid-js";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useTheme } from "../../state/ThemeProvider";
+import { useUser } from "../../state/UserProvider";
import { combineClasses } from "../../util/helpers";
import NotFound from "../NotFound";
import Grid from "../util/layout/Grid";
-import Loading from "../util/loading/Loading";
import Actions from "./Actions";
import { ActionStateProvider } from "./ActionStateProvider";
import Header from "./Header";
+import { ConfigProvider } from "./tabs/config/Provider";
import DeploymentTabs from "./tabs/Tabs";
import Updates from "./Updates";
@@ -18,6 +19,19 @@ const Deployment: Component<{}> = (p) => {
const server = () => deployment() && servers.get(deployment()?.serverID!);
const { themeClass } = useTheme();
const { isMobile } = useAppDimensions();
+ const { permissions, username } = useUser();
+ const userCanUpdate = () => {
+ if (permissions() > 1) {
+ return true;
+ } else if (
+ permissions() > 0 &&
+ deployment()!.owners.includes(username()!)
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ };
return (
= (p) => {
-
+
{/* right / tabs */}
-
+
+ you do not have permission to view this deployment
+
+ }
+ >
+
+
+
+
diff --git a/frontend/src/components/deployment/Header.tsx b/frontend/src/components/deployment/Header.tsx
index 131f49239..7213cb91c 100644
--- a/frontend/src/components/deployment/Header.tsx
+++ b/frontend/src/components/deployment/Header.tsx
@@ -39,6 +39,19 @@ const Header: Component<{ exiting?: boolean }> = (p) => {
const { isMobile } = useAppDimensions();
const [showUpdates, toggleShowUpdates] =
useLocalStorageToggle("show-updates");
+
+ const userCanUpdate = () => {
+ if (permissions() > 1) {
+ return true;
+ } else if (
+ permissions() > 0 &&
+ deployment()!.owners.includes(username()!)
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ };
return (
<>
= (p) => {
class={combineClasses("card shadow", themeClass())}
style={{
position: "relative",
- cursor: isMobile() ? "pointer" : undefined,
+ cursor: (isMobile() && userCanUpdate()) ? "pointer" : undefined,
}}
onClick={() => {
- if (isMobile()) toggleShowUpdates();
+ if (isMobile() && userCanUpdate()) toggleShowUpdates();
}}
>
@@ -104,7 +117,7 @@ const Header: Component<{ exiting?: boolean }> = (p) => {
{status()}
-
+
updates{" "}
= (p) => {
-
+
>
diff --git a/frontend/src/components/deployment/Updates.tsx b/frontend/src/components/deployment/Updates.tsx
index 83c92064f..05980a8a1 100644
--- a/frontend/src/components/deployment/Updates.tsx
+++ b/frontend/src/components/deployment/Updates.tsx
@@ -14,12 +14,13 @@ const Updates: Component<{}> = (p) => {
const selectedUpdates = useArray(() =>
getUpdates({ deploymentID: selected.id() })
);
- const unsub = ws.subscribe([ADD_UPDATE], ({ update }) => {
- if (update.deploymentID === selected.id()) {
- selectedUpdates.add(update);
- }
- });
- onCleanup(unsub);
+ onCleanup(
+ ws.subscribe([ADD_UPDATE], ({ update }) => {
+ if (update.deploymentID === selected.id()) {
+ selectedUpdates.add(update);
+ }
+ })
+ );
const [noMoreUpdates, setNoMore] = createSignal(false);
const loadMore = async () => {
const offset = selectedUpdates.collection()?.length;
diff --git a/frontend/src/components/deployment/tabs/Tabs.tsx b/frontend/src/components/deployment/tabs/Tabs.tsx
index c1393ff5c..13d04cab3 100644
--- a/frontend/src/components/deployment/tabs/Tabs.tsx
+++ b/frontend/src/components/deployment/tabs/Tabs.tsx
@@ -9,7 +9,6 @@ import {
import Tabs from "../../util/tabs/Tabs";
import Config from "./config/Config";
import Log from "./log/Log";
-import { ConfigProvider } from "./config/Provider";
import { useAppState } from "../../../state/StateProvider";
import { getDeploymentLog } from "../../../util/query";
import Icon from "../../util/Icon";
@@ -33,7 +32,7 @@ const DeploymentTabs: Component<{}> = () => {
deployment()!.status === "not deployed"
? "not deployed"
: (deployment()!.status as ContainerStatus).State;
- const load = async () => {
+ const loadLog = async () => {
console.log("load log");
if (deployment()?.status !== "not deployed") {
const log = await getDeploymentLog(selected.id(), logTail());
@@ -42,75 +41,74 @@ const DeploymentTabs: Component<{}> = () => {
setLog({});
}
};
- createEffect(load);
- const unsub = ws.subscribe([ADD_UPDATE], ({ update }: { update: Update }) => {
- if (
- update.deploymentID === selected.id() &&
- (update.operation === DEPLOY ||
- update.operation === START_CONTAINER ||
- update.operation === STOP_CONTAINER ||
- update.operation === DELETE_CONTAINER)
- ) {
- // console.log("updating log");
- setTimeout(() => {
- getDeploymentLog(selected.id()).then(setLog);
- }, 2000);
- }
- });
- onCleanup(unsub);
+ createEffect(loadLog);
+ onCleanup(
+ ws.subscribe([ADD_UPDATE], ({ update }: { update: Update }) => {
+ if (
+ update.deploymentID === selected.id() &&
+ (update.operation === DEPLOY ||
+ update.operation === START_CONTAINER ||
+ update.operation === STOP_CONTAINER ||
+ update.operation === DELETE_CONTAINER)
+ ) {
+ // console.log("updating log");
+ setTimeout(() => {
+ getDeploymentLog(selected.id()).then(setLog);
+ }, 2000);
+ }
+ })
+ );
const { themeClass } = useTheme();
return (
-
- ,
+ },
+ status() !== "not deployed" && [
{
- title: "config",
- element: ,
+ title: "log",
+ element: (
+
+ ),
},
- status() !== "not deployed" && [
- {
- title: "log",
- element: (
-
- ),
- },
- {
- title: "error log",
- titleElement: (
-
- error log{" "}
-
-
-
-
- ),
- element: (
-
- ),
- },
- ],
- ].flat()}
- localStorageKey="deployment-tab"
- />
-
+ status() !== "not deployed" && {
+ title: "error log",
+ titleElement: (
+
+ error log{" "}
+
+
+
+
+ ),
+ element: (
+
+ ),
+ },
+ ],
+ ].flat()}
+ localStorageKey="deployment-tab"
+ />
);
};
diff --git a/frontend/src/components/server/Header.tsx b/frontend/src/components/server/Header.tsx
index 66bb7b32c..5a4c8bfb5 100644
--- a/frontend/src/components/server/Header.tsx
+++ b/frontend/src/components/server/Header.tsx
@@ -27,6 +27,15 @@ const Header: Component<{}> = (p) => {
const { isMobile } = useAppDimensions();
const [showUpdates, toggleShowUpdates] =
useLocalStorageToggle("show-updates");
+ const userCanUpdate = () => {
+ if (permissions() > 1) {
+ return true;
+ } else if (permissions() > 0 && server()!.owners.includes(username()!)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
return (
<>
= (p) => {
alignItems="center"
style={{
position: "relative",
- cursor: isMobile() ? "pointer" : undefined,
+ cursor: isMobile() && userCanUpdate() ? "pointer" : undefined,
}}
onClick={() => {
- if (isMobile()) toggleShowUpdates();
+ if (isMobile() && userCanUpdate()) toggleShowUpdates();
}}
>
@@ -64,7 +73,7 @@ const Header: Component<{}> = (p) => {
-
+
updates{" "}
= (p) => {
-
+
>
diff --git a/frontend/src/components/server/Server.tsx b/frontend/src/components/server/Server.tsx
index 4495a792f..632fd4083 100644
--- a/frontend/src/components/server/Server.tsx
+++ b/frontend/src/components/server/Server.tsx
@@ -2,6 +2,7 @@ import { Component, Show } from "solid-js";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import { useTheme } from "../../state/ThemeProvider";
+import { useUser } from "../../state/UserProvider";
import { combineClasses } from "../../util/helpers";
import NotFound from "../NotFound";
import Grid from "../util/layout/Grid";
@@ -16,6 +17,16 @@ const Server: Component<{}> = (p) => {
const server = () => servers.get(selected.id())!;
const { themeClass } = useTheme();
const { isMobile } = useAppDimensions();
+ const { permissions, username } = useUser();
+ const userCanUpdate = () => {
+ if (permissions() > 1) {
+ return true;
+ } else if (permissions() > 0 && server()!.owners.includes(username()!)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
return (
}>
@@ -24,7 +35,7 @@ const Server: Component<{}> = (p) => {
-
+
diff --git a/frontend/src/state/socket.ts b/frontend/src/state/socket.ts
index 284100679..cf92bc22f 100644
--- a/frontend/src/state/socket.ts
+++ b/frontend/src/state/socket.ts
@@ -23,16 +23,13 @@ import { State } from "./StateProvider";
import { createSignal } from "solid-js";
import ReconnectingWebSocket from "reconnecting-websocket";
-function socket(
- user: User,
- state: State,
-) {
+function socket(user: User, state: State) {
const ws = new ReconnectingWebSocket(WS_URL);
const [isOpen, setOpen] = createSignal(false);
ws.addEventListener("open", () => {
- console.log("connection opened")
+ console.log("connection opened");
ws.send(JSON.stringify({ token: client.token }));
setOpen(true);
});
@@ -154,6 +151,19 @@ function handleMessage(
/* Updates */
case ADD_UPDATE:
const { update } = message as { update: Update };
+ if (
+ (update.deploymentID &&
+ !deployments
+ .get(update.deploymentID)
+ ?.owners.includes(user.username)) ||
+ (update.buildID &&
+ !builds.get(update.buildID)?.owners.includes(user.username)) ||
+ (update.serverID &&
+ !servers.get(update.serverID)?.owners.includes(user.username))
+ ) {
+ // dont respond to updates outside of users scope. not airtight for protecting sensitive data.
+ return;
+ }
updates.add(update);
pushNotification(
update.isError ? "bad" : "good",