diff --git a/server/server.go b/server/server.go index c607c039..c35e7876 100644 --- a/server/server.go +++ b/server/server.go @@ -45,6 +45,7 @@ import ( reset daily limits for users Account usage not updated "in real time" max token issue limit + user db startup queries -> foreign keys Sync: - "mute" setting - figure out what settings are "web" or "phone" @@ -101,6 +102,7 @@ var ( accountPasswordPath = "/v1/account/password" accountSettingsPath = "/v1/account/settings" accountSubscriptionPath = "/v1/account/subscription" + accountAccessPath = "/v1/account/access" accountSubscriptionSingleRegex = regexp.MustCompile(`^/v1/account/subscription/([-_A-Za-z0-9]{16})$`) matrixPushPath = "/_matrix/push/v1/notify" staticRegex = regexp.MustCompile(`^/static/.+`) @@ -357,6 +359,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit return s.ensureUser(s.handleAccountSubscriptionChange)(w, r, v) } else if r.Method == http.MethodDelete && accountSubscriptionSingleRegex.MatchString(r.URL.Path) { return s.ensureUser(s.handleAccountSubscriptionDelete)(w, r, v) + } else if r.Method == http.MethodPost && r.URL.Path == accountAccessPath { + return s.ensureUser(s.handleAccountAccessAdd)(w, r, v) } else if r.Method == http.MethodGet && r.URL.Path == matrixPushPath { return s.handleMatrixDiscovery(w) } else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) { diff --git a/server/server_account.go b/server/server_account.go index d0e37890..28a6dfde 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -307,3 +307,22 @@ func (s *Server) handleAccountSubscriptionDelete(w http.ResponseWriter, r *http. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this return nil } + +func (s *Server) handleAccountAccessAdd(w http.ResponseWriter, r *http.Request, v *visitor) error { + req, err := readJSONWithLimit[apiAccountAccessRequest](r.Body, jsonBodyBytesLimit) + if err != nil { + return err + } + if !topicRegex.MatchString(req.Topic) { + return errHTTPBadRequestTopicInvalid + } + 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 { + return err + } + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this + return nil +} diff --git a/server/types.go b/server/types.go index 9110d3e0..e274624d 100644 --- a/server/types.go +++ b/server/types.go @@ -266,3 +266,8 @@ type apiAccountResponse struct { Limits *apiAccountLimits `json:"limits,omitempty"` Stats *apiAccountStats `json:"stats,omitempty"` } + +type apiAccountAccessRequest struct { + Topic string `json:"topic"` + Access string `json:"access"` +} diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index f6908243..14527331 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -4,6 +4,7 @@ "signup_form_password": "Password", "signup_form_confirm_password": "Confirm password", "signup_form_button_submit": "Sign up", + "signup_form_toggle_password_visibility": "Toggle password visibility", "signup_already_have_account": "Already have an account? Sign in!", "signup_disabled": "Signup is disabled", "signup_error_username_taken": "Username {{username}} is already taken", @@ -224,6 +225,7 @@ "prefs_users_add_button": "Add user", "prefs_users_edit_button": "Edit user", "prefs_users_delete_button": "Delete user", + "prefs_users_table_cannot_delete_or_edit": "Cannot delete or edit logged in user", "prefs_users_table_user_header": "User", "prefs_users_table_base_url_header": "Service URL", "prefs_users_dialog_title_add": "Add user", diff --git a/web/src/app/UserManager.js b/web/src/app/UserManager.js index 4f3da862..f22d3d6c 100644 --- a/web/src/app/UserManager.js +++ b/web/src/app/UserManager.js @@ -18,7 +18,7 @@ class UserManager { } async save(user) { - if (user.baseUrl === config.baseUrl) { + if (session.exists() && user.baseUrl === config.baseUrl) { return; } await db.users.put(user); diff --git a/web/src/components/Login.js b/web/src/components/Login.js index dd6456b9..8362f8fa 100644 --- a/web/src/components/Login.js +++ b/web/src/components/Login.js @@ -11,12 +11,17 @@ import {NavLink} from "react-router-dom"; import AvatarBox from "./AvatarBox"; import {useTranslation} from "react-i18next"; import accountApi, {UnauthorizedError} from "../app/AccountApi"; +import IconButton from "@mui/material/IconButton"; +import {InputAdornment} from "@mui/material"; +import {Visibility, VisibilityOff} from "@mui/icons-material"; const Login = () => { const { t } = useTranslation(); const [error, setError] = useState(""); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const handleSubmit = async (event) => { event.preventDefault(); const user = { username, password }; @@ -66,11 +71,25 @@ const Login = () => { fullWidth name="password" label={t("signup_form_password")} - type="password" + type={showPassword ? "text" : "password"} id="password" value={password} onChange={ev => setPassword(ev.target.value.trim())} autoComplete="current-password" + InputProps={{ + endAdornment: ( + + setShowPassword(!showPassword)} + onMouseDown={(ev) => ev.preventDefault()} + edge="end" + > + {showPassword ? : } + + + ) + }} />