diff --git a/server/file_cache.go b/server/file_cache.go index 88de935d..9eae7ea6 100644 --- a/server/file_cache.go +++ b/server/file_cache.go @@ -26,7 +26,7 @@ type fileCache struct { mu sync.Mutex } -func newFileCache(dir string, totalSizeLimit int64, fileSizeLimit int64) (*fileCache, error) { +func newFileCache(dir string, totalSizeLimit int64) (*fileCache, error) { if err := os.MkdirAll(dir, 0700); err != nil { return nil, err } @@ -38,7 +38,6 @@ func newFileCache(dir string, totalSizeLimit int64, fileSizeLimit int64) (*fileC dir: dir, totalSizeCurrent: size, totalSizeLimit: totalSizeLimit, - fileSizeLimit: fileSizeLimit, }, nil } @@ -55,7 +54,7 @@ func (c *fileCache) Write(id string, in io.Reader, limiters ...util.Limiter) (in return 0, err } defer f.Close() - limiters = append(limiters, util.NewFixedLimiter(c.Remaining()), util.NewFixedLimiter(c.fileSizeLimit)) + limiters = append(limiters, util.NewFixedLimiter(c.Remaining())) limitWriter := util.NewLimitWriter(f, limiters...) size, err := io.Copy(limitWriter, in) if err != nil { diff --git a/server/server.go b/server/server.go index 835d6948..cbe1ca62 100644 --- a/server/server.go +++ b/server/server.go @@ -36,15 +36,17 @@ import ( /* TODO + use token auth in "SubscribeDialog" + upload files based on user limit publishXHR + poll should pick current user, not from userManager expire tokens auto-refresh tokens from UI reserve topics rate limit for signup (2 per 24h) handle invalid session token - update disallowed topics purge accounts that were not logged into in X sync subscription display name + store users Pages: - Home - Password reset @@ -103,7 +105,7 @@ var ( staticRegex = regexp.MustCompile(`^/static/.+`) docsRegex = regexp.MustCompile(`^/docs(|/.*)$`) fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`) - disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app + disallowedTopics = []string{"docs", "static", "file", "app", "account", "settings", "pricing", "signup", "login", "reset-password"} // If updated, also update in Android and web app urlRegex = regexp.MustCompile(`^https?://`) //go:embed site @@ -152,7 +154,7 @@ func New(conf *Config) (*Server, error) { } var fileCache *fileCache if conf.AttachmentCacheDir != "" { - fileCache, err = newFileCache(conf.AttachmentCacheDir, conf.AttachmentTotalSizeLimit, conf.AttachmentFileSizeLimit) + fileCache, err = newFileCache(conf.AttachmentCacheDir, conf.AttachmentTotalSizeLimit) if err != nil { return nil, err } @@ -423,9 +425,13 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi w.Header().Set("Content-Type", "text/javascript") _, err := io.WriteString(w, fmt.Sprintf(`// Generated server configuration var config = { + baseUrl: window.location.origin, appRoot: "%s", - disallowedTopics: [%s] -};`, appRoot, disallowedTopicsStr)) + enableLogin: %t, + enableSignup: %t, + enableResetPassword: %t, + disallowedTopics: [%s], +};`, appRoot, s.config.EnableLogin, s.config.EnableSignup, s.config.EnableResetPassword, disallowedTopicsStr)) return err } @@ -799,7 +805,12 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, if m.Message == "" { m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name) } - m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(stats.AttachmentTotalSizeRemaining)) + limiters := []util.Limiter{ + v.BandwidthLimiter(), + util.NewFixedLimiter(stats.AttachmentFileSizeLimit), + util.NewFixedLimiter(stats.AttachmentTotalSizeRemaining), + } + m.Attachment.Size, err = s.fileCache.Write(m.ID, body, limiters...) if err == util.ErrLimitReached { return errHTTPEntityTooLargeAttachmentTooLarge } else if err != nil { diff --git a/web/public/config.js b/web/public/config.js index 76c02041..c25cddc2 100644 --- a/web/public/config.js +++ b/web/public/config.js @@ -1,9 +1,15 @@ -// Configuration injected by the ntfy server. +// THIS FILE IS JUST AN EXAMPLE // -// This file is just an example. It is removed during the build process. -// The actual config is dynamically generated server-side. +// It is removed during the build process. The actual config is dynamically +// generated server-side and served by the ntfy server. +// +// During web development, you may change values here for rapid testing. var config = { + baseUrl: "http://localhost:2586", // window.location.origin FIXME update before merging appRoot: "/app", - disallowedTopics: ["docs", "static", "file", "app", "settings"] + enableLogin: true, + enableSignup: true, + enableResetPassword: false, + disallowedTopics: ["docs", "static", "file", "app", "account", "settings", "pricing", "signup", "login", "reset-password"] }; diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index 691ad52a..7ca3082a 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -144,7 +144,9 @@ "account_type_default": "Default", "account_type_unlimited": "Unlimited", "account_type_none": "None", - "account_type_hobbyist": "Hobbyist", + "account_type_pro": "Pro", + "account_type_business": "Business", + "account_type_business_plus": "Business Plus", "prefs_notifications_title": "Notifications", "prefs_notifications_sound_title": "Notification sound", "prefs_notifications_sound_description_none": "Notifications do not play any sound when they arrive", diff --git a/web/src/app/Api.js b/web/src/app/Api.js index 3d753b8f..1f93dc15 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -125,7 +125,9 @@ class Api { const response = await fetch(url, { headers: maybeWithBasicAuth({}, user) }); - if (response.status !== 200) { + if (response.status === 401 || response.status === 403) { + return false; + } else if (response.status !== 200) { throw new Error(`Unexpected server response ${response.status}`); } const json = await response.json(); diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 66c2b48d..fc2ad85f 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -47,7 +47,7 @@ export const disallowedTopic = (topic) => { export const topicDisplayName = (subscription) => { if (subscription.displayName) { return subscription.displayName; - } else if (subscription.baseUrl === window.location.origin) { + } else if (subscription.baseUrl === config.baseUrl) { return subscription.topic; } return topicShortUrl(subscription.baseUrl, subscription.topic); diff --git a/web/src/components/Account.js b/web/src/components/Account.js index d694a0ec..1294e212 100644 --- a/web/src/components/Account.js +++ b/web/src/components/Account.js @@ -147,7 +147,7 @@ const ChangePassword = () => { }; const handleDialogSubmit = async (newPassword) => { try { - await api.changePassword("http://localhost:2586", session.token(), newPassword); + await api.changePassword(config.baseUrl, session.token(), newPassword); setDialogOpen(false); console.debug(`[Account] Password changed`); } catch (e) { @@ -230,7 +230,7 @@ const DeleteAccount = () => { }; const handleDialogSubmit = async (newPassword) => { try { - await api.deleteAccount("http://localhost:2586", session.token()); + await api.deleteAccount(config.baseUrl, session.token()); setDialogOpen(false); console.debug(`[Account] Account deleted`); // TODO delete local storage diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index 97025002..c5af9d92 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -118,7 +118,7 @@ const SettingsIcons = (props) => { handleClose(event); await subscriptionManager.remove(props.subscription.id); if (session.exists() && props.subscription.remoteId) { - await api.deleteAccountSubscription("http://localhost:2586", session.token(), props.subscription.remoteId); + await api.deleteAccountSubscription(config.baseUrl, session.token(), props.subscription.remoteId); } const newSelected = await subscriptionManager.first(); // May be undefined if (newSelected) { @@ -259,9 +259,8 @@ const ProfileIcon = (props) => { const handleClose = () => { setAnchorEl(null); }; - const handleLogout = async () => { - await api.logout("http://localhost:2586"/*window.location.origin*/, session.token()); + await api.logout(config.baseUrl, session.token()); session.reset(); window.location.href = routes.app; }; @@ -273,11 +272,11 @@ const ProfileIcon = (props) => { <AccountCircleIcon/> </IconButton> } - {!session.exists() && - <> - <Button color="inherit" variant="outlined" onClick={() => navigate(routes.login)}>Sign in</Button> - <Button color="inherit" variant="outlined" onClick={() => navigate(routes.signup)}>Sign up</Button> - </> + {!session.exists() && config.enableLogin && + <Button color="inherit" variant="text" onClick={() => navigate(routes.login)} sx={{m: 1}}>Sign in</Button> + } + {!session.exists() && config.enableSignup && + <Button color="inherit" variant="outlined" onClick={() => navigate(routes.signup)}>Sign up</Button> } <Menu anchorEl={anchorEl} diff --git a/web/src/components/App.js b/web/src/components/App.js index eb2fba2f..360f8215 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -87,7 +87,7 @@ const Layout = () => { const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0; const [selected] = (subscriptions || []).filter(s => { return (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) - || (window.location.origin === s.baseUrl && params.topic === s.topic) + || (config.baseUrl === s.baseUrl && params.topic === s.topic) }); useConnectionListeners(subscriptions, users); @@ -96,7 +96,7 @@ const Layout = () => { useEffect(() => { (async () => { - const acc = await api.getAccount("http://localhost:2586", session.token()); + const acc = await api.getAccount(config.baseUrl, session.token()); if (acc) { setAccount(acc); if (acc.language) { diff --git a/web/src/components/AvatarBox.js b/web/src/components/AvatarBox.js new file mode 100644 index 00000000..3d32997e --- /dev/null +++ b/web/src/components/AvatarBox.js @@ -0,0 +1,29 @@ +import * as React from 'react'; +import {Avatar} from "@mui/material"; +import Box from "@mui/material/Box"; +import logo from "../img/ntfy2.svg"; + +const AvatarBox = (props) => { + return ( + <Box + sx={{ + display: 'flex', + flexGrow: 1, + justifyContent: 'center', + flexDirection: 'column', + alignContent: 'center', + alignItems: 'center', + height: '100vh' + }} + > + <Avatar + sx={{ m: 2, width: 64, height: 64, borderRadius: 3 }} + src={logo} + variant="rounded" + /> + {props.children} + </Box> + ); +} + +export default AvatarBox; diff --git a/web/src/components/Login.js b/web/src/components/Login.js index e2ab80b5..0a6b6f6a 100644 --- a/web/src/components/Login.js +++ b/web/src/components/Login.js @@ -1,17 +1,20 @@ import * as React from 'react'; -import {Avatar, Checkbox, FormControlLabel, Grid, Link} from "@mui/material"; import Typography from "@mui/material/Typography"; -import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import api from "../app/Api"; import routes from "./routes"; import session from "../app/Session"; -import logo from "../img/ntfy2.svg"; import {NavLink} from "react-router-dom"; +import AvatarBox from "./AvatarBox"; +import {useTranslation} from "react-i18next"; +import {useState} from "react"; const Login = () => { + const { t } = useTranslation(); + const [error, setError] = useState(""); const handleSubmit = async (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); @@ -19,31 +22,36 @@ const Login = () => { username: data.get('username'), password: data.get('password'), } - const token = await api.login("http://localhost:2586"/*window.location.origin*/, user); - console.log(`[Api] User auth for user ${user.username} successful, token is ${token}`); - session.store(user.username, token); - window.location.href = routes.app; + try { + const token = await api.login(config.baseUrl, user); + if (token) { + console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`); + session.store(user.username, token); + window.location.href = routes.app; + } else { + console.log(`[Login] User auth for user ${user.username} failed, access denied`); + setError(t("Login failed: Invalid username or password")); + } + } catch (e) { + console.log(`[Login] User auth for user ${user.username} failed`, e); + if (e && e.message) { + setError(e.message); + } else { + setError(t("Unknown error. Check logs for details.")) + } + } }; - + if (!config.enableLogin) { + return ( + <AvatarBox> + <Typography sx={{ typography: 'h6' }}>{t("Login is disabled")}</Typography> + </AvatarBox> + ); + } return ( - <Box - sx={{ - display: 'flex', - flexGrow: 1, - justifyContent: 'center', - flexDirection: 'column', - alignContent: 'center', - alignItems: 'center', - height: '100vh' - }} - > - <Avatar - sx={{ m: 2, width: 64, height: 64, borderRadius: 3 }} - src={logo} - variant="rounded" - /> + <AvatarBox> <Typography sx={{ typography: 'h6' }}> - Sign in to your ntfy account + {t("Sign in to your ntfy account")} </Typography> <Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}> <TextField @@ -51,7 +59,7 @@ const Login = () => { required fullWidth id="username" - label="Username" + label={t("Username")} name="username" autoFocus /> @@ -60,7 +68,7 @@ const Login = () => { required fullWidth name="password" - label="Password" + label={t("Password")} type="password" id="password" autoComplete="current-password" @@ -71,14 +79,25 @@ const Login = () => { variant="contained" sx={{mt: 2, mb: 2}} > - Sign in + {t("Sign in")} </Button> + {error && + <Box sx={{ + mb: 1, + display: 'flex', + flexGrow: 1, + justifyContent: 'center', + }}> + <WarningAmberIcon color="error" sx={{mr: 1}}/> + <Typography sx={{color: 'error.main'}}>{error}</Typography> + </Box> + } <Box sx={{width: "100%"}}> - <div style={{float: "left"}}><NavLink to={routes.resetPassword} variant="body1">Reset password</NavLink></div> - <div style={{float: "right"}}><NavLink to={routes.signup} variant="body1">Sign up</NavLink></div> + {config.enableResetPassword && <div style={{float: "left"}}><NavLink to={routes.resetPassword} variant="body1">{t("Reset password")}</NavLink></div>} + {config.enableSignup && <div style={{float: "right"}}><NavLink to={routes.signup} variant="body1">{t("Sign up")}</NavLink></div>} </Box> </Box> - </Box> + </AvatarBox> ); } diff --git a/web/src/components/Messaging.js b/web/src/components/Messaging.js index 4ba1203f..d4d89aa5 100644 --- a/web/src/components/Messaging.js +++ b/web/src/components/Messaging.js @@ -38,7 +38,7 @@ const Messaging = (props) => { <PublishDialog key={`publishDialog${dialogKey}`} // Resets dialog when canceled/closed openMode={dialogOpenMode} - baseUrl={subscription?.baseUrl ?? window.location.origin} + baseUrl={subscription?.baseUrl ?? config.baseUrl} topic={subscription?.topic ?? ""} message={message} onClose={handleDialogClose} @@ -83,7 +83,7 @@ const MessageBar = (props) => { margin="dense" placeholder={t("message_bar_type_message")} aria-label={t("message_bar_type_message")} - role="textbox" + role="textbox" type="text" fullWidth variant="standard" diff --git a/web/src/components/Preferences.js b/web/src/components/Preferences.js index fe09e05f..476063ce 100644 --- a/web/src/components/Preferences.js +++ b/web/src/components/Preferences.js @@ -73,7 +73,7 @@ const Sound = () => { const handleChange = async (ev) => { await prefs.setSound(ev.target.value); if (session.exists()) { - await api.updateAccountSettings("http://localhost:2586", session.token(), { + await api.updateAccountSettings(config.baseUrl, session.token(), { notification: { sound: ev.target.value } @@ -113,7 +113,7 @@ const MinPriority = () => { const handleChange = async (ev) => { await prefs.setMinPriority(ev.target.value); if (session.exists()) { - await api.updateAccountSettings("http://localhost:2586", session.token(), { + await api.updateAccountSettings(config.baseUrl, session.token(), { notification: { min_priority: ev.target.value } @@ -163,7 +163,7 @@ const DeleteAfter = () => { const handleChange = async (ev) => { await prefs.setDeleteAfter(ev.target.value); if (session.exists()) { - await api.updateAccountSettings("http://localhost:2586", session.token(), { + await api.updateAccountSettings(config.baseUrl, session.token(), { notification: { delete_after: ev.target.value } @@ -467,7 +467,7 @@ const Language = () => { const handleChange = async (ev) => { await i18n.changeLanguage(ev.target.value); if (session.exists()) { - await api.updateAccountSettings("http://localhost:2586", session.token(), { + await api.updateAccountSettings(config.baseUrl, session.token(), { language: ev.target.value }); } diff --git a/web/src/components/ResetPassword.js b/web/src/components/ResetPassword.js index 9d25e624..bcf635eb 100644 --- a/web/src/components/ResetPassword.js +++ b/web/src/components/ResetPassword.js @@ -1,14 +1,11 @@ import * as React from 'react'; -import {Avatar, Link} from "@mui/material"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; -import api from "../app/Api"; import routes from "./routes"; -import session from "../app/Session"; -import logo from "../img/ntfy2.svg"; import Typography from "@mui/material/Typography"; import {NavLink} from "react-router-dom"; +import AvatarBox from "./AvatarBox"; const ResetPassword = () => { const handleSubmit = async (event) => { @@ -16,22 +13,7 @@ const ResetPassword = () => { }; return ( - <Box - sx={{ - display: 'flex', - flexGrow: 1, - justifyContent: 'center', - flexDirection: 'column', - alignContent: 'center', - alignItems: 'center', - height: '100vh' - }} - > - <Avatar - sx={{ m: 2, width: 64, height: 64, borderRadius: 3 }} - src={logo} - variant="rounded" - /> + <AvatarBox> <Typography sx={{ typography: 'h6' }}> Reset password </Typography> @@ -59,7 +41,7 @@ const ResetPassword = () => { < Return to sign in </NavLink> </Typography> - </Box> + </AvatarBox> ); } diff --git a/web/src/components/Signup.js b/web/src/components/Signup.js index 624a35c0..3b10050e 100644 --- a/web/src/components/Signup.js +++ b/web/src/components/Signup.js @@ -1,52 +1,41 @@ import * as React from 'react'; -import {Avatar, Link} from "@mui/material"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import api from "../app/Api"; import routes from "./routes"; import session from "../app/Session"; -import logo from "../img/ntfy2.svg"; import Typography from "@mui/material/Typography"; import {NavLink} from "react-router-dom"; +import AvatarBox from "./AvatarBox"; +import {useTranslation} from "react-i18next"; const Signup = () => { + const { t } = useTranslation(); const handleSubmit = async (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); - const username = data.get('username'); - const password = data.get('password'); const user = { - username: username, - password: password - }; // FIXME omg so awful - - await api.createAccount("http://localhost:2586"/*window.location.origin*/, username, password); - const token = await api.login("http://localhost:2586"/*window.location.origin*/, user); + username: data.get('username'), + password: data.get('password') + }; + await api.createAccount(config.baseUrl, user.username, user.password); + const token = await api.login(config.baseUrl, user); console.log(`[Api] User auth for user ${user.username} successful, token is ${token}`); session.store(user.username, token); window.location.href = routes.app; }; - + if (!config.enableSignup) { + return ( + <AvatarBox> + <Typography sx={{ typography: 'h6' }}>{t("Signup is disabled")}</Typography> + </AvatarBox> + ); + } return ( - <Box - sx={{ - display: 'flex', - flexGrow: 1, - justifyContent: 'center', - flexDirection: 'column', - alignContent: 'center', - alignItems: 'center', - height: '100vh' - }} - > - <Avatar - sx={{ m: 2, width: 64, height: 64, borderRadius: 3 }} - src={logo} - variant="rounded" - /> + <AvatarBox> <Typography sx={{ typography: 'h6' }}> - Create a ntfy account + {t("Create a ntfy account")} </Typography> <Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}> <TextField @@ -83,15 +72,17 @@ const Signup = () => { variant="contained" sx={{mt: 2, mb: 2}} > - Sign up + {t("Sign up")} </Button> </Box> - <Typography sx={{mb: 4}}> - <NavLink to={routes.login} variant="body1"> - Already have an account? Sign in! - </NavLink> - </Typography> - </Box> + {config.enableLogin && + <Typography sx={{mb: 4}}> + <NavLink to={routes.login} variant="body1"> + {t("Already have an account? Sign in!")} + </NavLink> + </Typography> + } + </AvatarBox> ); } diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js index a5e75a4a..948717f5 100644 --- a/web/src/components/SubscribeDialog.js +++ b/web/src/components/SubscribeDialog.js @@ -25,10 +25,10 @@ const SubscribeDialog = (props) => { const [showLoginPage, setShowLoginPage] = useState(false); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const handleSuccess = async () => { - const actualBaseUrl = (baseUrl) ? baseUrl : window.location.origin; + const actualBaseUrl = (baseUrl) ? baseUrl : config.baseUrl; const subscription = await subscriptionManager.add(actualBaseUrl, topic); if (session.exists()) { - const remoteSubscription = await api.addAccountSubscription("http://localhost:2586", session.token(), { + const remoteSubscription = await api.addAccountSubscription(config.baseUrl, session.token(), { base_url: actualBaseUrl, topic: topic }); @@ -63,11 +63,11 @@ const SubscribePage = (props) => { const { t } = useTranslation(); const [anotherServerVisible, setAnotherServerVisible] = useState(false); const [errorText, setErrorText] = useState(""); - const baseUrl = (anotherServerVisible) ? props.baseUrl : window.location.origin; + 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)])) - .filter(s => s !== window.location.origin); + .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"); @@ -94,7 +94,7 @@ const SubscribePage = (props) => { const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl; } else { - const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(window.location.origin, topic)); + const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.baseUrl, topic)); return validTopic(topic) && !isExistingTopicUrl; } })(); @@ -152,7 +152,7 @@ const SubscribePage = (props) => { renderInput={ (params) => <TextField {...params} - placeholder={window.location.origin} + placeholder={config.baseUrl} variant="standard" aria-label={t("subscribe_dialog_subscribe_base_url_label")} /> @@ -172,7 +172,7 @@ const LoginPage = (props) => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [errorText, setErrorText] = useState(""); - const baseUrl = (props.baseUrl) ? props.baseUrl : window.location.origin; + const baseUrl = (props.baseUrl) ? props.baseUrl : config.baseUrl; const topic = props.topic; const handleLogin = async () => { const user = {baseUrl, username, password}; diff --git a/web/src/components/hooks.js b/web/src/components/hooks.js index eb526931..f5c7d332 100644 --- a/web/src/components/hooks.js +++ b/web/src/components/hooks.js @@ -59,12 +59,12 @@ export const useAutoSubscribe = (subscriptions, selected) => { setHasRun(true); const eligible = params.topic && !selected && !disallowedTopic(params.topic); if (eligible) { - const baseUrl = (params.baseUrl) ? expandSecureUrl(params.baseUrl) : window.location.origin; + const baseUrl = (params.baseUrl) ? expandSecureUrl(params.baseUrl) : config.baseUrl; console.log(`[App] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`); (async () => { const subscription = await subscriptionManager.add(baseUrl, params.topic); if (session.exists()) { - const remoteSubscription = await api.addAccountSubscription("http://localhost:2586", session.token(), { + const remoteSubscription = await api.addAccountSubscription(config.baseUrl, session.token(), { base_url: baseUrl, topic: params.topic }); diff --git a/web/src/components/routes.js b/web/src/components/routes.js index 4802a602..3e07f0fb 100644 --- a/web/src/components/routes.js +++ b/web/src/components/routes.js @@ -1,6 +1,8 @@ import config from "../app/config"; import {shortUrl} from "../app/utils"; +// Remember to also update the "disallowedTopics" list! + const routes = { home: "/", pricing: "/pricing", @@ -13,7 +15,7 @@ const routes = { subscription: "/:topic", subscriptionExternal: "/:baseUrl/:topic", forSubscription: (subscription) => { - if (subscription.baseUrl !== window.location.origin) { + if (subscription.baseUrl !== config.baseUrl) { return `/${shortUrl(subscription.baseUrl)}/${subscription.topic}`; } return `/${subscription.topic}`;