Skip to content

Commit

Permalink
Allow to change username (#21152)
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten authored Jun 24, 2024
1 parent 23fcdf8 commit 6a30419
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 17 deletions.
11 changes: 11 additions & 0 deletions src/data/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ export const adminChangePassword = (
password,
});

export const adminChangeUsername = (
hass: HomeAssistant,
userId: string,
username: string
) =>
hass.callWS<void>({
type: "config/auth_provider/homeassistant/admin_change_username",
user_id: userId,
username,
});

export const deleteAllRefreshTokens = (
hass: HomeAssistant,
token_type?: RefreshTokenType,
Expand Down
85 changes: 74 additions & 11 deletions src/panels/config/person/dialog-person-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../../../components/ha-formfield";
import "../../../components/ha-picture-upload";
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
import "../../../components/ha-textfield";
import { adminChangeUsername } from "../../../data/auth";
import { PersonMutableParams } from "../../../data/person";
import {
deleteUser,
Expand All @@ -19,10 +20,11 @@ import {
import {
showAlertDialog,
showConfirmationDialog,
showPromptDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
import { ValueChangedEvent, HomeAssistant } from "../../../types";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant, ValueChangedEvent } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showAddUserDialog } from "../users/show-dialog-add-user";
import { showAdminChangePasswordDialog } from "../users/show-dialog-admin-change-password";
Expand Down Expand Up @@ -136,9 +138,9 @@ class DialogPersonDetail extends LitElement {
></ha-picture-upload>
<ha-formfield
.label=${this.hass!.localize(
.label=${`${this.hass!.localize(
"ui.panel.config.person.detail.allow_login"
)}
)}${this._user ? ` (${this._user.username})` : ""}`}
>
<ha-switch
@change=${this._allowLoginChanged}
Expand Down Expand Up @@ -244,13 +246,21 @@ class DialogPersonDetail extends LitElement {
</mwc-button>
${this._user && this.hass.user?.is_owner
? html`<mwc-button
slot="secondaryAction"
@click=${this._changePassword}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
slot="secondaryAction"
@click=${this._changeUsername}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_username"
)}
</mwc-button>
<mwc-button
slot="secondaryAction"
@click=${this._changePassword}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
`
: nothing}
Expand Down Expand Up @@ -292,11 +302,14 @@ class DialogPersonDetail extends LitElement {
userAddedCallback: async (user?: User) => {
if (user) {
target.checked = true;
if (this._params!.entry) {
await this._params!.updateEntry({ user_id: user.id });
}
this._params?.refreshUsers();
this._user = user;
this._userId = user.id;
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._localOnly = user.local_only;
this._params?.refreshUsers();
}
},
name: this._name,
Expand All @@ -320,6 +333,9 @@ class DialogPersonDetail extends LitElement {
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
this._localOnly = undefined;
}
}

Expand Down Expand Up @@ -349,6 +365,53 @@ class DialogPersonDetail extends LitElement {
showAdminChangePasswordDialog(this, { userId: this._user.id });
}

private async _changeUsername() {
if (!this._user) {
return;
}
const credential = this._user.credentials.find(
(cred) => cred.type === "homeassistant"
);
if (!credential) {
showAlertDialog(this, {
title: "No Home Assistant credentials found.",
});
return;
}

const newUsername = await showPromptDialog(this, {
inputLabel: this.hass.localize(
"ui.panel.config.users.change_username.new_username"
),
confirmText: this.hass.localize(
"ui.panel.config.users.change_username.change"
),
title: this.hass.localize(
"ui.panel.config.users.change_username.caption"
),
defaultValue: this._user.username!,
});
if (newUsername) {
try {
await adminChangeUsername(this.hass, this._user.id, newUsername);
this._params?.refreshUsers();
this._user = { ...this._user, username: newUsername };
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.config.users.change_username.username_changed"
),
});
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.change_username.failed"
),
text: err.message,
});
}
}
}

private async _updateEntry() {
this._submitting = true;
try {
Expand Down
70 changes: 64 additions & 6 deletions src/panels/config/users/dialog-user-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import "../../../components/ha-label";
import "../../../components/ha-svg-icon";
import "../../../components/ha-switch";
import "../../../components/ha-textfield";
import { adminChangeUsername } from "../../../data/auth";
import {
computeUserBadges,
SYSTEM_GROUP_ID_ADMIN,
SYSTEM_GROUP_ID_USER,
} from "../../../data/user";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import {
showAlertDialog,
showPromptDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { showAdminChangePasswordDialog } from "./show-dialog-admin-change-password";
Expand Down Expand Up @@ -172,11 +176,15 @@ class DialogUserDetail extends LitElement {
`
: ""}
${!user.system_generated && this.hass.user?.is_owner
? html`<mwc-button @click=${this._changePassword}>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
? html`<mwc-button @click=${this._changeUsername}>
${this.hass.localize(
"ui.panel.config.users.editor.change_username"
)} </mwc-button
><mwc-button @click=${this._changePassword}>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
</div>
Expand Down Expand Up @@ -250,6 +258,56 @@ class DialogUserDetail extends LitElement {
}
}

private async _changeUsername() {
const credential = this._params?.entry.credentials.find(
(cred) => cred.type === "homeassistant"
);
if (!credential) {
showAlertDialog(this, {
title: "No Home Assistant credentials found.",
});
return;
}
const newUsername = await showPromptDialog(this, {
inputLabel: this.hass.localize(
"ui.panel.config.users.change_username.new_username"
),
confirmText: this.hass.localize(
"ui.panel.config.users.change_username.change"
),
title: this.hass.localize(
"ui.panel.config.users.change_username.caption"
),
defaultValue: this._params!.entry.username!,
});
if (newUsername) {
try {
await adminChangeUsername(
this.hass,
this._params!.entry.id,
newUsername
);
this._params = {
...this._params!,
entry: { ...this._params!.entry, username: newUsername },
};
this._params.replaceEntry(this._params.entry);
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.config.users.change_username.username_changed"
),
});
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.change_username.failed"
),
text: err.message,
});
}
}
}

private async _changePassword() {
const credential = this._params?.entry.credentials.find(
(cred) => cred.type === "homeassistant"
Expand Down
5 changes: 5 additions & 0 deletions src/panels/config/users/ha-config-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ export class HaConfigUsers extends LitElement {

showUserDetailDialog(this, {
entry,
replaceEntry: (newEntry: User) => {
this._users = this._users!.map((ent) =>
ent.id === newEntry.id ? newEntry : ent
);
},
updateEntry: async (values) => {
const updated = await updateUser(this.hass!, entry!.id, values);
this._users = this._users!.map((ent) =>
Expand Down
1 change: 1 addition & 0 deletions src/panels/config/users/show-dialog-user-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { UpdateUserParams, User } from "../../../data/user";
export interface UserDetailDialogParams {
entry: User;
updateEntry: (updates: Partial<UpdateUserParams>) => Promise<unknown>;
replaceEntry: (entry: User) => void;
removeEntry: () => Promise<boolean>;
}

Expand Down
8 changes: 8 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4382,6 +4382,7 @@
"name": "Display name",
"username": "Username",
"change_password": "Change password",
"change_username": "Change username",
"activate_user": "Activate user",
"deactivate_user": "Deactivate user",
"delete_user": "Delete user",
Expand Down Expand Up @@ -4415,6 +4416,13 @@
"change": "Change",
"password_no_match": "Passwords don't match",
"password_changed": "The password has been changed successfully."
},
"change_username": {
"caption": "Change username",
"new_username": "New username",
"change": "Change",
"username_changed": "The username has been changed successfully.",
"failed": "Failed to change username"
}
},
"application_credentials": {
Expand Down

0 comments on commit 6a30419

Please sign in to comment.