mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-01-18 16:17:26 +01:00
Disallow changing provisioned user and tokens
This commit is contained in:
@@ -85,6 +85,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
|
|||||||
response.Username = u.Name
|
response.Username = u.Name
|
||||||
response.Role = string(u.Role)
|
response.Role = string(u.Role)
|
||||||
response.SyncTopic = u.SyncTopic
|
response.SyncTopic = u.SyncTopic
|
||||||
|
response.Provisioned = u.Provisioned
|
||||||
if u.Prefs != nil {
|
if u.Prefs != nil {
|
||||||
if u.Prefs.Language != nil {
|
if u.Prefs.Language != nil {
|
||||||
response.Language = *u.Prefs.Language
|
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()
|
lastOrigin = t.LastOrigin.String()
|
||||||
}
|
}
|
||||||
response.Tokens = append(response.Tokens, &apiAccountTokenResponse{
|
response.Tokens = append(response.Tokens, &apiAccountTokenResponse{
|
||||||
Token: t.Value,
|
Token: t.Value,
|
||||||
Label: t.Label,
|
Label: t.Label,
|
||||||
LastAccess: t.LastAccess.Unix(),
|
LastAccess: t.LastAccess.Unix(),
|
||||||
LastOrigin: lastOrigin,
|
LastOrigin: lastOrigin,
|
||||||
Expires: t.Expires.Unix(),
|
Expires: t.Expires.Unix(),
|
||||||
|
Provisioned: t.Provisioned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,11 +360,12 @@ type apiAccountTokenUpdateRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type apiAccountTokenResponse struct {
|
type apiAccountTokenResponse struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Label string `json:"label,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
LastAccess int64 `json:"last_access,omitempty"`
|
LastAccess int64 `json:"last_access,omitempty"`
|
||||||
LastOrigin string `json:"last_origin,omitempty"`
|
LastOrigin string `json:"last_origin,omitempty"`
|
||||||
Expires int64 `json:"expires,omitempty"` // Unix timestamp
|
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 {
|
type apiAccountPhoneNumberVerifyRequest struct {
|
||||||
@@ -426,6 +427,7 @@ type apiAccountResponse struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Role string `json:"role,omitempty"`
|
Role string `json:"role,omitempty"`
|
||||||
SyncTopic string `json:"sync_topic,omitempty"`
|
SyncTopic string `json:"sync_topic,omitempty"`
|
||||||
|
Provisioned bool `json:"provisioned,omitempty"`
|
||||||
Language string `json:"language,omitempty"`
|
Language string `json:"language,omitempty"`
|
||||||
Notification *user.NotificationPrefs `json:"notification,omitempty"`
|
Notification *user.NotificationPrefs `json:"notification,omitempty"`
|
||||||
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
|
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
|
||||||
|
|||||||
@@ -212,6 +212,7 @@
|
|||||||
"account_basics_phone_numbers_dialog_check_verification_button": "Confirm code",
|
"account_basics_phone_numbers_dialog_check_verification_button": "Confirm code",
|
||||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||||
"account_basics_phone_numbers_dialog_channel_call": "Call",
|
"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_title": "Usage",
|
||||||
"account_usage_of_limit": "of {{limit}}",
|
"account_usage_of_limit": "of {{limit}}",
|
||||||
"account_usage_unlimited": "Unlimited",
|
"account_usage_unlimited": "Unlimited",
|
||||||
@@ -291,6 +292,7 @@
|
|||||||
"account_tokens_table_current_session": "Current browser session",
|
"account_tokens_table_current_session": "Current browser session",
|
||||||
"account_tokens_table_copied_to_clipboard": "Access token copied",
|
"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": "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_create_token_button": "Create access token",
|
||||||
"account_tokens_table_last_origin_tooltip": "From IP address {{ip}}, click to lookup",
|
"account_tokens_table_last_origin_tooltip": "From IP address {{ip}}, click to lookup",
|
||||||
"account_tokens_dialog_title_create": "Create access token",
|
"account_tokens_dialog_title_create": "Create access token",
|
||||||
|
|||||||
@@ -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 {
|
export class AccountCreateLimitReachedError extends Error {
|
||||||
static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation
|
static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation
|
||||||
|
|
||||||
|
|||||||
@@ -100,15 +100,13 @@ const Username = () => {
|
|||||||
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
||||||
<div aria-labelledby={labelId}>
|
<div aria-labelledby={labelId}>
|
||||||
{session.username()}
|
{session.username()}
|
||||||
{account?.role === Role.ADMIN ? (
|
{account?.role === Role.ADMIN && (
|
||||||
<>
|
<>
|
||||||
{" "}
|
{" "}
|
||||||
<Tooltip title={t("account_basics_username_admin_tooltip")}>
|
<Tooltip title={t("account_basics_username_admin_tooltip")}>
|
||||||
<span style={{ cursor: "default" }}>👑</span>
|
<span style={{ cursor: "default" }}>👑</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Pref>
|
</Pref>
|
||||||
@@ -119,6 +117,7 @@ const ChangePassword = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dialogKey, setDialogKey] = useState(0);
|
const [dialogKey, setDialogKey] = useState(0);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const { account } = useContext(AccountContext);
|
||||||
const labelId = "prefChangePassword";
|
const labelId = "prefChangePassword";
|
||||||
|
|
||||||
const handleDialogOpen = () => {
|
const handleDialogOpen = () => {
|
||||||
@@ -136,9 +135,19 @@ const ChangePassword = () => {
|
|||||||
<Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
|
<Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
|
||||||
⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤
|
⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
{!account?.provisioned ? (
|
||||||
<EditIcon />
|
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
||||||
</IconButton>
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
|
||||||
|
<span>
|
||||||
|
<IconButton disabled>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
<ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
||||||
</Pref>
|
</Pref>
|
||||||
@@ -888,7 +897,7 @@ const TokensTable = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
|
<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")}>
|
<IconButton onClick={() => handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
@@ -910,6 +919,18 @@ const TokensTable = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</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>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
@@ -1048,6 +1069,7 @@ const DeleteAccount = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dialogKey, setDialogKey] = useState(0);
|
const [dialogKey, setDialogKey] = useState(0);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const { account } = useContext(AccountContext);
|
||||||
|
|
||||||
const handleDialogOpen = () => {
|
const handleDialogOpen = () => {
|
||||||
setDialogKey((prev) => prev + 1);
|
setDialogKey((prev) => prev + 1);
|
||||||
@@ -1061,9 +1083,19 @@ const DeleteAccount = () => {
|
|||||||
return (
|
return (
|
||||||
<Pref title={t("account_delete_title")} description={t("account_delete_description")}>
|
<Pref title={t("account_delete_title")} description={t("account_delete_description")}>
|
||||||
<div>
|
<div>
|
||||||
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
|
{!account?.provisioned ? (
|
||||||
{t("account_delete_title")}
|
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
|
||||||
</Button>
|
{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>
|
</div>
|
||||||
<DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
<DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
||||||
</Pref>
|
</Pref>
|
||||||
|
|||||||
Reference in New Issue
Block a user