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 ? : }
+
+
+ )
+ }}
/>