diff --git a/server/server_account.go b/server/server_account.go index 28a6dfde..a3565e54 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -316,10 +316,13 @@ func (s *Server) handleAccountAccessAdd(w http.ResponseWriter, r *http.Request, if !topicRegex.MatchString(req.Topic) { return errHTTPBadRequestTopicInvalid } + // FIXME authorize: how do I know if v.user (= auth'd user) is allowed to write the ACL entries + everyoneRead := util.Contains([]string{"read-write", "rw", "read-only", "read", "ro"}, req.Everyone) + everyoneWrite := util.Contains([]string{"read-write", "rw", "write-only", "write", "wo"}, req.Everyone) if err := s.userManager.AllowAccess(v.user.Name, req.Topic, true, true); err != nil { return err } - if err := s.userManager.AllowAccess(user.Everyone, req.Topic, false, false); err != nil { + if err := s.userManager.AllowAccess(user.Everyone, req.Topic, everyoneRead, everyoneWrite); err != nil { return err } w.Header().Set("Content-Type", "application/json") diff --git a/server/types.go b/server/types.go index e274624d..97acfbb7 100644 --- a/server/types.go +++ b/server/types.go @@ -268,6 +268,6 @@ type apiAccountResponse struct { } type apiAccountAccessRequest struct { - Topic string `json:"topic"` - Access string `json:"access"` + Topic string `json:"topic"` + Everyone string `json:"everyone"` } diff --git a/user/manager.go b/user/manager.go index c4d4de8d..54912f17 100644 --- a/user/manager.go +++ b/user/manager.go @@ -47,7 +47,8 @@ const ( ); CREATE UNIQUE INDEX idx_user ON user (user); CREATE TABLE IF NOT EXISTS user_access ( - user_id INT NOT NULL, + user_id INT NOT NULL, + owner_user_id INT, topic TEXT NOT NULL, read INT NOT NULL, write INT NOT NULL, diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index 572f713c..a52a3be7 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -33,6 +33,7 @@ import Divider from "@mui/material/Divider"; import {Logout, Person, Settings} from "@mui/icons-material"; import ListItemIcon from "@mui/material/ListItemIcon"; import accountApi, {UnauthorizedError} from "../app/AccountApi"; +import PopupMenu from "./PopupMenu"; const ActionBar = (props) => { const { t } = useTranslation(); @@ -189,6 +190,7 @@ const SettingsIcons = (props) => { { } { ); }; -const PopupMenu = (props) => { - return ( - - {props.children} - - ); -}; - export default ActionBar; diff --git a/web/src/components/PopupMenu.js b/web/src/components/PopupMenu.js new file mode 100644 index 00000000..6d39d86e --- /dev/null +++ b/web/src/components/PopupMenu.js @@ -0,0 +1,47 @@ +import {Menu} from "@mui/material"; +import * as React from "react"; + +const PopupMenu = (props) => { + const horizontal = props.horizontal ?? "left"; + const arrow = (horizontal === "right") ? { right: 19 } : { left: 19 }; + return ( + + {props.children} + + ); +}; + +export default PopupMenu; diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js index 8f42869b..f5add414 100644 --- a/web/src/components/SubscribeDialog.js +++ b/web/src/components/SubscribeDialog.js @@ -20,6 +20,11 @@ import routes from "./routes"; import accountApi, {UnauthorizedError} from "../app/AccountApi"; import IconButton from "@mui/material/IconButton"; import PublicIcon from '@mui/icons-material/Public'; +import LockIcon from '@mui/icons-material/Lock'; +import PublicOffIcon from '@mui/icons-material/PublicOff'; +import MenuItem from "@mui/material/MenuItem"; +import PopupMenu from "./PopupMenu"; +import ListItemIcon from "@mui/material/ListItemIcon"; const publicBaseUrl = "https://ntfy.sh"; @@ -75,11 +80,15 @@ const SubscribePage = (props) => { const { t } = useTranslation(); const [anotherServerVisible, setAnotherServerVisible] = useState(false); const [errorText, setErrorText] = useState(""); + const [accessAnchorEl, setAccessAnchorEl] = useState(null); + const [access, setAccess] = useState("public"); const baseUrl = (anotherServerVisible) ? props.baseUrl : config.baseUrl; const topic = props.topic; const existingTopicUrls = props.subscriptions.map(s => topicUrl(s.baseUrl, s.topic)); - const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)])) + const existingBaseUrls = Array + .from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)])) .filter(s => s !== config.baseUrl); + const handleSubscribe = async () => { const user = await userManager.get(baseUrl); // May be undefined const username = (user) ? user.username : t("subscribe_dialog_error_user_anonymous"); @@ -97,10 +106,12 @@ const SubscribePage = (props) => { console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); props.onSuccess(); }; + const handleUseAnotherChanged = (e) => { props.setBaseUrl(""); setAnotherServerVisible(e.target.checked); }; + const subscribeButtonEnabled = (() => { if (anotherServerVisible) { const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); @@ -110,6 +121,7 @@ const SubscribePage = (props) => { return validTopic(topic) && !isExistingTopicUrl; } })(); + const updateBaseUrl = (ev, newVal) => { if (validUrl(newVal)) { props.setBaseUrl(newVal.replace(/\/$/, '')); // strip trailing slash after https?:// @@ -117,6 +129,7 @@ const SubscribePage = (props) => { props.setBaseUrl(newVal); } }; + return ( <> {t("subscribe_dialog_subscribe_title")} @@ -125,9 +138,13 @@ const SubscribePage = (props) => { {t("subscribe_dialog_subscribe_description")}
- - - + {session.exists() && + setAccessAnchorEl(ev.currentTarget)} color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}> + {access === "public" && } + {access === "public-read" && } + {access === "private" && } + + } { maxLength: 64, "aria-label": t("subscribe_dialog_subscribe_topic_placeholder") }} - /> - + /> + + setAccessAnchorEl(null)} + > + setAccess("private")} selected={access === "private"}> + + + + Only I can publish and subscribe + + setAccess("public-read")} selected={access === "public-read"}> + + + + I can publish, everyone can subscribe + + setAccess("public")} selected={access === "public"}> + + + + Everyone can publish and subscribe + +