Przeglądaj źródła

Disallow changing provisioned user and tokens

binwiederhier 6 miesięcy temu
rodzic
commit
bcfb50b35a

+ 7 - 5
server/server_account.go

@@ -85,6 +85,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
 		response.Username = u.Name
 		response.Role = string(u.Role)
 		response.SyncTopic = u.SyncTopic
+		response.Provisioned = u.Provisioned
 		if u.Prefs != nil {
 			if u.Prefs.Language != nil {
 				response.Language = *u.Prefs.Language
@@ -139,11 +140,12 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
 					lastOrigin = t.LastOrigin.String()
 				}
 				response.Tokens = append(response.Tokens, &apiAccountTokenResponse{
-					Token:      t.Value,
-					Label:      t.Label,
-					LastAccess: t.LastAccess.Unix(),
-					LastOrigin: lastOrigin,
-					Expires:    t.Expires.Unix(),
+					Token:       t.Value,
+					Label:       t.Label,
+					LastAccess:  t.LastAccess.Unix(),
+					LastOrigin:  lastOrigin,
+					Expires:     t.Expires.Unix(),
+					Provisioned: t.Provisioned,
 				})
 			}
 		}

+ 7 - 5
server/types.go

@@ -360,11 +360,12 @@ type apiAccountTokenUpdateRequest struct {
 }
 
 type apiAccountTokenResponse struct {
-	Token      string `json:"token"`
-	Label      string `json:"label,omitempty"`
-	LastAccess int64  `json:"last_access,omitempty"`
-	LastOrigin string `json:"last_origin,omitempty"`
-	Expires    int64  `json:"expires,omitempty"` // Unix timestamp
+	Token       string `json:"token"`
+	Label       string `json:"label,omitempty"`
+	LastAccess  int64  `json:"last_access,omitempty"`
+	LastOrigin  string `json:"last_origin,omitempty"`
+	Expires     int64  `json:"expires,omitempty"`     // Unix timestamp
+	Provisioned bool   `json:"provisioned,omitempty"` // True if this token was provisioned by the server config
 }
 
 type apiAccountPhoneNumberVerifyRequest struct {
@@ -426,6 +427,7 @@ type apiAccountResponse struct {
 	Username      string                     `json:"username"`
 	Role          string                     `json:"role,omitempty"`
 	SyncTopic     string                     `json:"sync_topic,omitempty"`
+	Provisioned   bool                       `json:"provisioned,omitempty"`
 	Language      string                     `json:"language,omitempty"`
 	Notification  *user.NotificationPrefs    `json:"notification,omitempty"`
 	Subscriptions []*user.Subscription       `json:"subscriptions,omitempty"`

+ 2 - 0
web/public/static/langs/en.json

@@ -212,6 +212,7 @@
   "account_basics_phone_numbers_dialog_check_verification_button": "Confirm code",
   "account_basics_phone_numbers_dialog_channel_sms": "SMS",
   "account_basics_phone_numbers_dialog_channel_call": "Call",
+  "account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted from the web app",
   "account_usage_title": "Usage",
   "account_usage_of_limit": "of {{limit}}",
   "account_usage_unlimited": "Unlimited",
@@ -291,6 +292,7 @@
   "account_tokens_table_current_session": "Current browser session",
   "account_tokens_table_copied_to_clipboard": "Access token copied",
   "account_tokens_table_cannot_delete_or_edit": "Cannot edit or delete current session token",
+  "account_tokens_table_cannot_delete_or_edit_provisioned_token": "Cannot edit or delete provisioned token",
   "account_tokens_table_create_token_button": "Create access token",
   "account_tokens_table_last_origin_tooltip": "From IP address {{ip}}, click to lookup",
   "account_tokens_dialog_title_create": "Create access token",

+ 0 - 8
web/src/app/errors.js

@@ -31,14 +31,6 @@ export class TopicReservedError extends Error {
   }
 }
 
-export class ProvisionedUserPasswordError extends Error {
-  static CODE = 40905; // errHTTPConflictTopicReserved
-
-  constructor() {
-    super("Cannot change the password of a provisioned user");
-  }
-}
-
 export class AccountCreateLimitReachedError extends Error {
   static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation
 

+ 42 - 10
web/src/components/Account.jsx

@@ -100,15 +100,13 @@ const Username = () => {
     <Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
       <div aria-labelledby={labelId}>
         {session.username()}
-        {account?.role === Role.ADMIN ? (
+        {account?.role === Role.ADMIN && (
           <>
             {" "}
             <Tooltip title={t("account_basics_username_admin_tooltip")}>
               <span style={{ cursor: "default" }}>👑</span>
             </Tooltip>
           </>
-        ) : (
-          ""
         )}
       </div>
     </Pref>
@@ -119,6 +117,7 @@ const ChangePassword = () => {
   const { t } = useTranslation();
   const [dialogKey, setDialogKey] = useState(0);
   const [dialogOpen, setDialogOpen] = useState(false);
+  const { account } = useContext(AccountContext);
   const labelId = "prefChangePassword";
 
   const handleDialogOpen = () => {
@@ -136,9 +135,19 @@ const ChangePassword = () => {
         <Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
           ⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤
         </Typography>
-        <IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
-          <EditIcon />
-        </IconButton>
+        {!account?.provisioned ? (
+          <IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
+            <EditIcon />
+          </IconButton>
+        ) : (
+          <Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
+            <span>
+              <IconButton disabled>
+                <EditIcon />
+              </IconButton>
+            </span>
+          </Tooltip>
+        )}
       </div>
       <ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
     </Pref>
@@ -888,7 +897,7 @@ const TokensTable = (props) => {
               </div>
             </TableCell>
             <TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
-              {token.token !== session.token() && (
+              {token.token !== session.token() && !token.provisioned && (
                 <>
                   <IconButton onClick={() => handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}>
                     <EditIcon />
@@ -910,6 +919,18 @@ const TokensTable = (props) => {
                   </span>
                 </Tooltip>
               )}
+              {token.provisioned && (
+                <Tooltip title={t("account_tokens_table_cannot_delete_or_edit_provisioned_token")}>
+                  <span>
+                    <IconButton disabled>
+                      <EditIcon />
+                    </IconButton>
+                    <IconButton disabled>
+                      <CloseIcon />
+                    </IconButton>
+                  </span>
+                </Tooltip>
+              )}
             </TableCell>
           </TableRow>
         ))}
@@ -1048,6 +1069,7 @@ const DeleteAccount = () => {
   const { t } = useTranslation();
   const [dialogKey, setDialogKey] = useState(0);
   const [dialogOpen, setDialogOpen] = useState(false);
+  const { account } = useContext(AccountContext);
 
   const handleDialogOpen = () => {
     setDialogKey((prev) => prev + 1);
@@ -1061,9 +1083,19 @@ const DeleteAccount = () => {
   return (
     <Pref title={t("account_delete_title")} description={t("account_delete_description")}>
       <div>
-        <Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
-          {t("account_delete_title")}
-        </Button>
+        {!account?.provisioned ? (
+          <Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
+            {t("account_delete_title")}
+          </Button>
+        ) : (
+          <Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
+            <span>
+              <Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} disabled>
+                {t("account_delete_title")}
+              </Button>
+            </span>
+          </Tooltip>
+        )}
       </div>
       <DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
     </Pref>