Not really an improvemenNot really an improvementt
This commit is contained in:
		
							parent
							
								
									bd86e3d951
								
							
						
					
					
						commit
						3d921f4570
					
				
					 6 changed files with 107 additions and 53 deletions
				
			
		| 
						 | 
				
			
			@ -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")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,6 +48,7 @@ const (
 | 
			
		|||
		CREATE UNIQUE INDEX idx_user ON user (user);
 | 
			
		||||
		CREATE TABLE IF NOT EXISTS user_access (
 | 
			
		||||
			user_id INT NOT NULL,
 | 
			
		||||
			owner_user_id INT,			
 | 
			
		||||
			topic TEXT NOT NULL,
 | 
			
		||||
			read INT NOT NULL,
 | 
			
		||||
			write INT NOT NULL,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) => {
 | 
			
		|||
                <MoreVertIcon/>
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <PopupMenu
 | 
			
		||||
                horizontal="right"
 | 
			
		||||
                anchorEl={anchorEl}
 | 
			
		||||
                open={open}
 | 
			
		||||
                onClose={handleClose}
 | 
			
		||||
| 
						 | 
				
			
			@ -259,6 +261,7 @@ const ProfileIcon = () => {
 | 
			
		|||
                </Button>
 | 
			
		||||
            }
 | 
			
		||||
            <PopupMenu
 | 
			
		||||
                horizontal="right"
 | 
			
		||||
                anchorEl={anchorEl}
 | 
			
		||||
                open={open}
 | 
			
		||||
                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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										47
									
								
								web/src/components/PopupMenu.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								web/src/components/PopupMenu.js
									
										
									
									
									
										Normal 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
        <>
 | 
			
		||||
            <DialogTitle>{t("subscribe_dialog_subscribe_title")}</DialogTitle>
 | 
			
		||||
| 
						 | 
				
			
			@ -125,9 +138,13 @@ const SubscribePage = (props) => {
 | 
			
		|||
                    {t("subscribe_dialog_subscribe_description")}
 | 
			
		||||
                </DialogContentText>
 | 
			
		||||
                <div style={{display: 'flex'}} role="row">
 | 
			
		||||
                    <IconButton color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}>
 | 
			
		||||
                        <PublicIcon/>
 | 
			
		||||
                    </IconButton>
 | 
			
		||||
                    {session.exists() &&
 | 
			
		||||
                        <IconButton onClick={(ev) => setAccessAnchorEl(ev.currentTarget)} color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}>
 | 
			
		||||
                            {access === "public" && <PublicIcon/>}
 | 
			
		||||
                            {access === "public-read" && <PublicOffIcon/>}
 | 
			
		||||
                            {access === "private" && <LockIcon/>}
 | 
			
		||||
                        </IconButton>
 | 
			
		||||
                    }
 | 
			
		||||
                    <TextField
 | 
			
		||||
                        autoFocus
 | 
			
		||||
                        margin="dense"
 | 
			
		||||
| 
						 | 
				
			
			@ -142,10 +159,34 @@ const SubscribePage = (props) => {
 | 
			
		|||
                            maxLength: 64,
 | 
			
		||||
                            "aria-label": t("subscribe_dialog_subscribe_topic_placeholder")
 | 
			
		||||
                        }}
 | 
			
		||||
                        />
 | 
			
		||||
                        <Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
 | 
			
		||||
                            {t("subscribe_dialog_subscribe_button_generate_topic_name")}
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    />
 | 
			
		||||
                    <Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
 | 
			
		||||
                        {t("subscribe_dialog_subscribe_button_generate_topic_name")}
 | 
			
		||||
                    </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>
 | 
			
		||||
                <FormControlLabel
 | 
			
		||||
                    sx={{pt: 1}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue