diff --git a/web/src/app/Api.js b/web/src/app/Api.js index 8bb7770..471b410 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -1,4 +1,4 @@ -import {topicUrlJsonPoll, fetchLinesIterator, topicUrl} from "./utils"; +import {topicUrlJsonPoll, fetchLinesIterator, topicUrl, topicUrlAuth} from "./utils"; class Api { async poll(baseUrl, topic) { @@ -19,6 +19,20 @@ class Api { body: message }); } + + async auth(baseUrl, topic, user) { + const url = topicUrlAuth(baseUrl, topic); + console.log(`[Api] Checking auth for ${url}`); + const response = await fetch(url); + if (response.status >= 200 && response.status <= 299) { + return true; + } else if (!user && response.status === 404) { + return true; // Special case: Anonymous login to old servers return 404 since //auth doesn't exist + } else if (response.status === 401 || response.status === 403) { // See server/server.go + return false; + } + throw new Error(`Unexpected server response ${response.status}`); + } } const api = new Api(); diff --git a/web/src/app/Subscriptions.js b/web/src/app/Subscriptions.js index 916da3a..78a9ef8 100644 --- a/web/src/app/Subscriptions.js +++ b/web/src/app/Subscriptions.js @@ -10,8 +10,7 @@ class Subscriptions { get(subscriptionId) { const subscription = this.subscriptions.get(subscriptionId); - if (subscription === undefined) return null; - return subscription; + return (subscription) ? subscription : null; } update(subscription) { @@ -38,8 +37,7 @@ class Subscriptions { firstOrNull() { const first = this.subscriptions.values().next().value; - if (first === undefined) return null; - return first; + return (first) ? first : null; } size() { diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 416be6d..40b9f56 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -7,6 +7,7 @@ export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws` export const topicUrlWsWithSince = (baseUrl, topic, since) => `${topicUrlWs(baseUrl, topic)}?since=${since}`; export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`; export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`; +export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`; export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); export const shortTopicUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic)); diff --git a/web/src/components/NotificationList.js b/web/src/components/NotificationList.js index 3c3cc75..9facfb7 100644 --- a/web/src/components/NotificationList.js +++ b/web/src/components/NotificationList.js @@ -1,9 +1,9 @@ import Container from "@mui/material/Container"; -import {CardContent, CardHeader, Stack} from "@mui/material"; +import {CardContent, Stack} from "@mui/material"; import Card from "@mui/material/Card"; import Typography from "@mui/material/Typography"; import * as React from "react"; -import {formatTitle, formatMessage, unmatchedTags} from "../app/utils"; +import {formatMessage, formatTitle, unmatchedTags} from "../app/utils"; import IconButton from "@mui/material/IconButton"; import CloseIcon from '@mui/icons-material/Close'; diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js index 1a8388f..55240c7 100644 --- a/web/src/components/SubscribeDialog.js +++ b/web/src/components/SubscribeDialog.js @@ -8,47 +8,110 @@ import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import {useState} from "react"; import Subscription from "../app/Subscription"; +import {useMediaQuery} from "@mui/material"; +import theme from "./theme"; +import api from "../app/Api"; +import {topicUrl} from "../app/utils"; const defaultBaseUrl = "http://127.0.0.1" //const defaultBaseUrl = "https://ntfy.sh" const SubscribeDialog = (props) => { + const [baseUrl, setBaseUrl] = useState(defaultBaseUrl); // FIXME const [topic, setTopic] = useState(""); + const [showLoginPage, setShowLoginPage] = useState(false); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const handleCancel = () => { setTopic(''); props.onCancel(); } - const handleSubmit = () => { + const handleSubmit = async () => { + const success = await api.auth(baseUrl, topic, null); + if (!success) { + console.log(`[SubscribeDialog] Login required for ${topicUrl(baseUrl, topic)}`) + setShowLoginPage(true); + return; + } const subscription = new Subscription(defaultBaseUrl, topic); props.onSubmit(subscription); setTopic(''); } + return ( + + {!showLoginPage && } + {showLoginPage && setShowLoginPage(false)} + />} + + ); +}; + +const SubscribePage = (props) => { return ( <> - - Subscribe to topic - - - Topics may not be password-protected, so choose a name that's not easy to guess. - Once subscribed, you can PUT/POST notifications. - - setTopic(ev.target.value)} - type="text" - fullWidth - variant="standard" - /> - - - - - - + Subscribe to topic + + + Topics may not be password-protected, so choose a name that's not easy to guess. + Once subscribed, you can PUT/POST notifications. + + props.setTopic(ev.target.value)} + type="text" + fullWidth + variant="standard" + /> + + + + + + + ); +}; + +const LoginPage = (props) => { + return ( + <> + Login required + + + This topic is password-protected. Please enter username and + password to subscribe. + + + + + + + + ); };