1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2024-09-27 19:12:00 +02:00

Not really an improvemenNot really an improvementt

This commit is contained in:
binwiederhier 2022-12-31 09:31:46 -05:00
parent bd86e3d951
commit 3d921f4570
6 changed files with 107 additions and 53 deletions

View file

@ -316,10 +316,13 @@ func (s *Server) handleAccountAccessAdd(w http.ResponseWriter, r *http.Request,
if !topicRegex.MatchString(req.Topic) { if !topicRegex.MatchString(req.Topic) {
return errHTTPBadRequestTopicInvalid 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 { if err := s.userManager.AllowAccess(v.user.Name, req.Topic, true, true); err != nil {
return err 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 return err
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")

View file

@ -268,6 +268,6 @@ type apiAccountResponse struct {
} }
type apiAccountAccessRequest struct { type apiAccountAccessRequest struct {
Topic string `json:"topic"` Topic string `json:"topic"`
Access string `json:"access"` Everyone string `json:"everyone"`
} }

View file

@ -47,7 +47,8 @@ const (
); );
CREATE UNIQUE INDEX idx_user ON user (user); CREATE UNIQUE INDEX idx_user ON user (user);
CREATE TABLE IF NOT EXISTS user_access ( 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, topic TEXT NOT NULL,
read INT NOT NULL, read INT NOT NULL,
write INT NOT NULL, write INT NOT NULL,

View file

@ -33,6 +33,7 @@ import Divider from "@mui/material/Divider";
import {Logout, Person, Settings} from "@mui/icons-material"; import {Logout, Person, Settings} from "@mui/icons-material";
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import accountApi, {UnauthorizedError} from "../app/AccountApi"; import accountApi, {UnauthorizedError} from "../app/AccountApi";
import PopupMenu from "./PopupMenu";
const ActionBar = (props) => { const ActionBar = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -189,6 +190,7 @@ const SettingsIcons = (props) => {
<MoreVertIcon/> <MoreVertIcon/>
</IconButton> </IconButton>
<PopupMenu <PopupMenu
horizontal="right"
anchorEl={anchorEl} anchorEl={anchorEl}
open={open} open={open}
onClose={handleClose} onClose={handleClose}
@ -259,6 +261,7 @@ const ProfileIcon = () => {
</Button> </Button>
} }
<PopupMenu <PopupMenu
horizontal="right"
anchorEl={anchorEl} anchorEl={anchorEl}
open={open} open={open}
onClose={handleClose} onClose={handleClose}
@ -287,45 +290,4 @@ const ProfileIcon = () => {
); );
}; };
const PopupMenu = (props) => {
return (
<Menu
anchorEl={props.anchorEl}
open={props.open}
onClose={props.onClose}
onClick={props.onClose}
PaperProps={{
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&:before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 19,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
{props.children}
</Menu>
);
};
export default ActionBar; export default ActionBar;

View file

@ -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 (
<Menu
anchorEl={props.anchorEl}
open={props.open}
onClose={props.onClose}
onClick={props.onClose}
PaperProps={{
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&:before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
...arrow
},
},
}}
transformOrigin={{ horizontal: horizontal, vertical: 'top' }}
anchorOrigin={{ horizontal: horizontal, vertical: 'bottom' }}
>
{props.children}
</Menu>
);
};
export default PopupMenu;

View file

@ -20,6 +20,11 @@ import routes from "./routes";
import accountApi, {UnauthorizedError} from "../app/AccountApi"; import accountApi, {UnauthorizedError} from "../app/AccountApi";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import PublicIcon from '@mui/icons-material/Public'; 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"; const publicBaseUrl = "https://ntfy.sh";
@ -75,11 +80,15 @@ const SubscribePage = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [anotherServerVisible, setAnotherServerVisible] = useState(false); const [anotherServerVisible, setAnotherServerVisible] = useState(false);
const [errorText, setErrorText] = useState(""); const [errorText, setErrorText] = useState("");
const [accessAnchorEl, setAccessAnchorEl] = useState(null);
const [access, setAccess] = useState("public");
const baseUrl = (anotherServerVisible) ? props.baseUrl : config.baseUrl; const baseUrl = (anotherServerVisible) ? props.baseUrl : config.baseUrl;
const topic = props.topic; const topic = props.topic;
const existingTopicUrls = props.subscriptions.map(s => topicUrl(s.baseUrl, s.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); .filter(s => s !== config.baseUrl);
const handleSubscribe = async () => { const handleSubscribe = async () => {
const user = await userManager.get(baseUrl); // May be undefined const user = await userManager.get(baseUrl); // May be undefined
const username = (user) ? user.username : t("subscribe_dialog_error_user_anonymous"); 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}`); console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`);
props.onSuccess(); props.onSuccess();
}; };
const handleUseAnotherChanged = (e) => { const handleUseAnotherChanged = (e) => {
props.setBaseUrl(""); props.setBaseUrl("");
setAnotherServerVisible(e.target.checked); setAnotherServerVisible(e.target.checked);
}; };
const subscribeButtonEnabled = (() => { const subscribeButtonEnabled = (() => {
if (anotherServerVisible) { if (anotherServerVisible) {
const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic));
@ -110,6 +121,7 @@ const SubscribePage = (props) => {
return validTopic(topic) && !isExistingTopicUrl; return validTopic(topic) && !isExistingTopicUrl;
} }
})(); })();
const updateBaseUrl = (ev, newVal) => { const updateBaseUrl = (ev, newVal) => {
if (validUrl(newVal)) { if (validUrl(newVal)) {
props.setBaseUrl(newVal.replace(/\/$/, '')); // strip trailing slash after https?:// props.setBaseUrl(newVal.replace(/\/$/, '')); // strip trailing slash after https?://
@ -117,6 +129,7 @@ const SubscribePage = (props) => {
props.setBaseUrl(newVal); props.setBaseUrl(newVal);
} }
}; };
return ( return (
<> <>
<DialogTitle>{t("subscribe_dialog_subscribe_title")}</DialogTitle> <DialogTitle>{t("subscribe_dialog_subscribe_title")}</DialogTitle>
@ -125,9 +138,13 @@ const SubscribePage = (props) => {
{t("subscribe_dialog_subscribe_description")} {t("subscribe_dialog_subscribe_description")}
</DialogContentText> </DialogContentText>
<div style={{display: 'flex'}} role="row"> <div style={{display: 'flex'}} role="row">
<IconButton color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}> {session.exists() &&
<PublicIcon/> <IconButton onClick={(ev) => setAccessAnchorEl(ev.currentTarget)} color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}>
</IconButton> {access === "public" && <PublicIcon/>}
{access === "public-read" && <PublicOffIcon/>}
{access === "private" && <LockIcon/>}
</IconButton>
}
<TextField <TextField
autoFocus autoFocus
margin="dense" margin="dense"
@ -142,10 +159,34 @@ const SubscribePage = (props) => {
maxLength: 64, maxLength: 64,
"aria-label": t("subscribe_dialog_subscribe_topic_placeholder") "aria-label": t("subscribe_dialog_subscribe_topic_placeholder")
}} }}
/> />
<Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}> <Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
{t("subscribe_dialog_subscribe_button_generate_topic_name")} {t("subscribe_dialog_subscribe_button_generate_topic_name")}
</Button> </Button>
<PopupMenu
anchorEl={accessAnchorEl}
open={!!accessAnchorEl}
onClose={() => setAccessAnchorEl(null)}
>
<MenuItem onClick={() => setAccess("private")} selected={access === "private"}>
<ListItemIcon>
<LockIcon fontSize="small" />
</ListItemIcon>
Only I can publish and subscribe
</MenuItem>
<MenuItem onClick={() => setAccess("public-read")} selected={access === "public-read"}>
<ListItemIcon>
<PublicOffIcon fontSize="small" />
</ListItemIcon>
I can publish, everyone can subscribe
</MenuItem>
<MenuItem onClick={() => setAccess("public")} selected={access === "public"}>
<ListItemIcon>
<PublicIcon fontSize="small" />
</ListItemIcon>
Everyone can publish and subscribe
</MenuItem>
</PopupMenu>
</div> </div>
<FormControlLabel <FormControlLabel
sx={{pt: 1}} sx={{pt: 1}}