Redirect UI if unauthorized API response
This commit is contained in:
		
							parent
							
								
									1b39ba70cb
								
							
						
					
					
						commit
						3aac1b2715
					
				
					 11 changed files with 148 additions and 77 deletions
				
			
		|  | @ -16,7 +16,7 @@ import DialogTitle from "@mui/material/DialogTitle"; | |||
| import DialogContent from "@mui/material/DialogContent"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import DialogActions from "@mui/material/DialogActions"; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| import routes from "./routes"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import {useNavigate, useOutletContext} from "react-router-dom"; | ||||
|  | @ -152,6 +152,10 @@ const ChangePassword = () => { | |||
|             console.debug(`[Account] Password changed`); | ||||
|         } catch (e) { | ||||
|             console.log(`[Account] Error changing password`, e); | ||||
|             if ((e instanceof UnauthorizedError)) { | ||||
|                 session.reset(); | ||||
|                 window.location.href = routes.login; | ||||
|             } | ||||
|             // TODO show error
 | ||||
|         } | ||||
|     }; | ||||
|  | @ -238,6 +242,10 @@ const DeleteAccount = () => { | |||
|             window.location.href = routes.app; | ||||
|         } catch (e) { | ||||
|             console.log(`[Account] Error deleting account`, e); | ||||
|             if ((e instanceof UnauthorizedError)) { | ||||
|                 session.reset(); | ||||
|                 window.location.href = routes.login; | ||||
|             } | ||||
|             // TODO show error
 | ||||
|         } | ||||
|     }; | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ import MenuList from '@mui/material/MenuList'; | |||
| import MoreVertIcon from "@mui/icons-material/MoreVert"; | ||||
| import NotificationsIcon from '@mui/icons-material/Notifications'; | ||||
| import NotificationsOffIcon from '@mui/icons-material/NotificationsOff'; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| import routes from "./routes"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import logo from "../img/ntfy.svg"; | ||||
|  | @ -118,7 +118,15 @@ const SettingsIcons = (props) => { | |||
|         handleClose(event); | ||||
|         await subscriptionManager.remove(props.subscription.id); | ||||
|         if (session.exists() && props.subscription.remoteId) { | ||||
|             await api.deleteAccountSubscription(config.baseUrl, session.token(), props.subscription.remoteId); | ||||
|             try { | ||||
|                 await api.deleteAccountSubscription(config.baseUrl, session.token(), props.subscription.remoteId); | ||||
|             } catch (e) { | ||||
|                 console.log(`[ActionBar] Error unsubscribing`, e); | ||||
|                 if ((e instanceof UnauthorizedError)) { | ||||
|                     session.reset(); | ||||
|                     window.location.href = routes.login; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         const newSelected = await subscriptionManager.first(); // May be undefined
 | ||||
|         if (newSelected) { | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ import {Backdrop, CircularProgress} from "@mui/material"; | |||
| import Home from "./Home"; | ||||
| import Login from "./Login"; | ||||
| import i18n from "i18next"; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| import prefs from "../app/Prefs"; | ||||
| import session from "../app/Session"; | ||||
| import Pricing from "./Pricing"; | ||||
|  | @ -96,8 +96,12 @@ const Layout = () => { | |||
| 
 | ||||
|     useEffect(() => { | ||||
|         (async () => { | ||||
|             const acc = await api.getAccount(config.baseUrl, session.token()); | ||||
|             if (acc) { | ||||
|             // TODO this should not live here
 | ||||
|             try { | ||||
|                 if (!session.token()) { | ||||
|                     return; | ||||
|                 } | ||||
|                 const acc = await api.getAccount(config.baseUrl, session.token()); | ||||
|                 setAccount(acc); | ||||
|                 if (acc.language) { | ||||
|                     await i18n.changeLanguage(acc.language); | ||||
|  | @ -116,6 +120,12 @@ const Layout = () => { | |||
|                 if (acc.subscriptions) { | ||||
|                     await subscriptionManager.syncFromRemote(acc.subscriptions); | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.log(`[App] Error fetching account`, e); | ||||
|                 if ((e instanceof UnauthorizedError)) { | ||||
|                     session.reset(); | ||||
|                     window.location.href = routes.login; | ||||
|                 } | ||||
|             } | ||||
|         })(); | ||||
|     }, []); | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ 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 api, {UnauthorizedError} from "../app/Api"; | ||||
| import routes from "./routes"; | ||||
| import session from "../app/Session"; | ||||
| import {NavLink} from "react-router-dom"; | ||||
|  | @ -22,17 +22,14 @@ const Login = () => { | |||
|         const user = { username, password }; | ||||
|         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")); | ||||
|             } | ||||
|             console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`); | ||||
|             session.store(user.username, token); | ||||
|             window.location.href = routes.app; | ||||
|         } catch (e) { | ||||
|             console.log(`[Login] User auth for user ${user.username} failed`, e); | ||||
|             if (e && e.message) { | ||||
|             if ((e instanceof UnauthorizedError)) { | ||||
|                 setError(t("Login failed: Invalid username or password")); | ||||
|             } else if (e.message) { | ||||
|                 setError(e.message); | ||||
|             } else { | ||||
|                 setError(t("Unknown error. Check logs for details.")) | ||||
|  |  | |||
|  | @ -34,8 +34,9 @@ import DialogActions from "@mui/material/DialogActions"; | |||
| import userManager from "../app/UserManager"; | ||||
| import {playSound, shuffle, sounds, validTopic, validUrl} from "../app/utils"; | ||||
| import {useTranslation} from "react-i18next"; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| 
 | ||||
| const Preferences = () => { | ||||
|     return ( | ||||
|  | @ -72,13 +73,11 @@ const Sound = () => { | |||
|     const sound = useLiveQuery(async () => prefs.sound()); | ||||
|     const handleChange = async (ev) => { | ||||
|         await prefs.setSound(ev.target.value); | ||||
|         if (session.exists()) { | ||||
|             await api.updateAccountSettings(config.baseUrl, session.token(), { | ||||
|                 notification: { | ||||
|                     sound: ev.target.value | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         await maybeUpdateAccountSettings({ | ||||
|             notification: { | ||||
|                 sound: ev.target.value | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     if (!sound) { | ||||
|         return null; // While loading
 | ||||
|  | @ -112,13 +111,11 @@ const MinPriority = () => { | |||
|     const minPriority = useLiveQuery(async () => prefs.minPriority()); | ||||
|     const handleChange = async (ev) => { | ||||
|         await prefs.setMinPriority(ev.target.value); | ||||
|         if (session.exists()) { | ||||
|             await api.updateAccountSettings(config.baseUrl, session.token(), { | ||||
|                 notification: { | ||||
|                     min_priority: ev.target.value | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         await maybeUpdateAccountSettings({ | ||||
|             notification: { | ||||
|                 min_priority: ev.target.value | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     if (!minPriority) { | ||||
|         return null; // While loading
 | ||||
|  | @ -162,13 +159,11 @@ const DeleteAfter = () => { | |||
|     const deleteAfter = useLiveQuery(async () => prefs.deleteAfter()); | ||||
|     const handleChange = async (ev) => { | ||||
|         await prefs.setDeleteAfter(ev.target.value); | ||||
|         if (session.exists()) { | ||||
|             await api.updateAccountSettings(config.baseUrl, session.token(), { | ||||
|                 notification: { | ||||
|                     delete_after: ev.target.value | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         await maybeUpdateAccountSettings({ | ||||
|             notification: { | ||||
|                 delete_after: ev.target.value | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     if (deleteAfter === null || deleteAfter === undefined) { // !deleteAfter will not work with "0"
 | ||||
|         return null; // While loading
 | ||||
|  | @ -466,11 +461,9 @@ const Language = () => { | |||
| 
 | ||||
|     const handleChange = async (ev) => { | ||||
|         await i18n.changeLanguage(ev.target.value); | ||||
|         if (session.exists()) { | ||||
|             await api.updateAccountSettings(config.baseUrl, session.token(), { | ||||
|                 language: ev.target.value | ||||
|             }); | ||||
|         } | ||||
|         await maybeUpdateAccountSettings({ | ||||
|             language: ev.target.value | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     // Remember: Flags are not languages. Don't put flags next to the language in the list.
 | ||||
|  | @ -670,4 +663,19 @@ const AccessControlDialog = (props) => { | |||
| }; | ||||
| */ | ||||
| 
 | ||||
| const maybeUpdateAccountSettings = async (payload) => { | ||||
|     if (!session.exists()) { | ||||
|         return; | ||||
|     } | ||||
|     try { | ||||
|         await api.updateAccountSettings(config.baseUrl, session.token(), payload); | ||||
|     } catch (e) { | ||||
|         console.log(`[Preferences] Error updating account settings`, e); | ||||
|         if ((e instanceof UnauthorizedError)) { | ||||
|             session.reset(); | ||||
|             window.location.href = routes.login; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export default Preferences; | ||||
|  |  | |||
|  | @ -22,11 +22,12 @@ import {basicAuth, formatBytes, maybeWithBasicAuth, topicShortUrl, topicUrl, val | |||
| import Box from "@mui/material/Box"; | ||||
| import AttachmentIcon from "./AttachmentIcon"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| import userManager from "../app/UserManager"; | ||||
| import EmojiPicker from "./EmojiPicker"; | ||||
| import {Trans, useTranslation} from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| 
 | ||||
| const PublishDialog = (props) => { | ||||
|     const { t } = useTranslation(); | ||||
|  | @ -178,7 +179,12 @@ const PublishDialog = (props) => { | |||
|             setAttachFileError(""); | ||||
|         } catch (e) { | ||||
|             console.log(`[PublishDialog] Retrieving attachment limits failed`, e); | ||||
|             setAttachFileError(""); // Reset error (rely on server-side checking)
 | ||||
|             if ((e instanceof UnauthorizedError)) { | ||||
|                 session.reset(); | ||||
|                 window.location.href = routes.login; | ||||
|             } else { | ||||
|                 setAttachFileError(""); // Reset error (rely on server-side checking)
 | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import * as React from 'react'; | |||
| import TextField from "@mui/material/TextField"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import api, {AccountCreateLimitReachedError, UsernameTakenError} from "../app/Api"; | ||||
| import api, {AccountCreateLimitReachedError, UnauthorizedError, UsernameTakenError} from "../app/Api"; | ||||
| import routes from "./routes"; | ||||
| import session from "../app/Session"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
|  | @ -24,14 +24,9 @@ const Signup = () => { | |||
|         try { | ||||
|             await api.createAccount(config.baseUrl, user.username, user.password); | ||||
|             const token = await api.login(config.baseUrl, user); | ||||
|             if (token) { | ||||
|                 console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`); | ||||
|                 session.store(user.username, token); | ||||
|                 window.location.href = routes.app; | ||||
|             } else { | ||||
|                 console.log(`[Signup] Signup for user ${user.username} failed, access denied`); | ||||
|                 setError(t("Login failed: Invalid username or password")); | ||||
|             } | ||||
|             console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`); | ||||
|             session.store(user.username, token); | ||||
|             window.location.href = routes.app; | ||||
|         } catch (e) { | ||||
|             console.log(`[Signup] Signup for user ${user.username} failed`, e); | ||||
|             if ((e instanceof UsernameTakenError)) { | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ import DialogContentText from '@mui/material/DialogContentText'; | |||
| import DialogTitle from '@mui/material/DialogTitle'; | ||||
| import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/material"; | ||||
| import theme from "./theme"; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| import {randomAlphanumericString, topicUrl, validTopic, validUrl} from "../app/utils"; | ||||
| import userManager from "../app/UserManager"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
|  | @ -16,6 +16,7 @@ import poller from "../app/Poller"; | |||
| import DialogFooter from "./DialogFooter"; | ||||
| import {useTranslation} from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| 
 | ||||
| const publicBaseUrl = "https://ntfy.sh"; | ||||
| 
 | ||||
|  | @ -25,14 +26,23 @@ const SubscribeDialog = (props) => { | |||
|     const [showLoginPage, setShowLoginPage] = useState(false); | ||||
|     const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||
|     const handleSuccess = async () => { | ||||
|         console.log(`[SubscribeDialog] Subscribing to topic ${topic}`); | ||||
|         const actualBaseUrl = (baseUrl) ? baseUrl : config.baseUrl; | ||||
|         const subscription = await subscriptionManager.add(actualBaseUrl, topic); | ||||
|         if (session.exists()) { | ||||
|             const remoteSubscription = await api.addAccountSubscription(config.baseUrl, session.token(), { | ||||
|                 base_url: actualBaseUrl, | ||||
|                 topic: topic | ||||
|             }); | ||||
|             await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id); | ||||
|             try { | ||||
|                 const remoteSubscription = await api.addAccountSubscription(config.baseUrl, session.token(), { | ||||
|                     base_url: actualBaseUrl, | ||||
|                     topic: topic | ||||
|                 }); | ||||
|                 await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id); | ||||
|             } catch (e) { | ||||
|                 console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e); | ||||
|                 if ((e instanceof UnauthorizedError)) { | ||||
|                     session.reset(); | ||||
|                     window.location.href = routes.login; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         poller.pollInBackground(subscription); // Dangle!
 | ||||
|         props.onSuccess(subscription); | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ import connectionManager from "../app/ConnectionManager"; | |||
| import poller from "../app/Poller"; | ||||
| import pruner from "../app/Pruner"; | ||||
| import session from "../app/Session"; | ||||
| import api from "../app/Api"; | ||||
| import api, {UnauthorizedError} from "../app/Api"; | ||||
| 
 | ||||
| /** | ||||
|  * Wire connectionManager and subscriptionManager so that subscriptions are updated when the connection | ||||
|  | @ -64,11 +64,19 @@ export const useAutoSubscribe = (subscriptions, selected) => { | |||
|             (async () => { | ||||
|                 const subscription = await subscriptionManager.add(baseUrl, params.topic); | ||||
|                 if (session.exists()) { | ||||
|                     const remoteSubscription = await api.addAccountSubscription(config.baseUrl, session.token(), { | ||||
|                         base_url: baseUrl, | ||||
|                         topic: params.topic | ||||
|                     }); | ||||
|                     await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id); | ||||
|                     try { | ||||
|                         const remoteSubscription = await api.addAccountSubscription(config.baseUrl, session.token(), { | ||||
|                             base_url: baseUrl, | ||||
|                             topic: params.topic | ||||
|                         }); | ||||
|                         await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id); | ||||
|                     } catch (e) { | ||||
|                         console.log(`[App] Auto-subscribing failed`, e); | ||||
|                         if ((e instanceof UnauthorizedError)) { | ||||
|                             session.reset(); | ||||
|                             window.location.href = routes.login; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 poller.pollInBackground(subscription); // Dangle!
 | ||||
|             })(); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue