UX: Minor fixes to passkey handling ()

- don't try to guess the name of the manager (too many options)
- improve error message when registration is not allowed
- output error in console when registration fails
- minor fix to rename dialog layout
- hides action buttons in DiscourseHub (because adding passkeys there is not possible)
- adds acceptance test to ensure action buttons are hidden for admins seeing another user's profile
This commit is contained in:
Penar Musaraj 2023-10-18 11:46:51 -04:00 committed by GitHub
parent 585bb0df27
commit a125c9e63e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 56 additions and 23 deletions
app/assets
javascripts/discourse
app/components/user-preferences
tests
stylesheets/common/base
config/locales

@ -25,20 +25,11 @@ export default class UserPasskeys extends Component {
lastUsedPrefix = I18n.t("user.passkeys.last_used_prefix"); lastUsedPrefix = I18n.t("user.passkeys.last_used_prefix");
neverUsed = I18n.t("user.passkeys.never_used"); neverUsed = I18n.t("user.passkeys.never_used");
isCurrentUser() { get showActions() {
return this.currentUser.id === this.args.model.id; return (
} this.currentUser.id === this.args.model.id &&
!this.capabilities.isAppWebview
passkeyDefaultName() { );
if (this.capabilities.isSafari) {
return I18n.t("user.passkeys.name.icloud_keychain");
}
if (this.capabilities.isAndroid || this.capabilities.isChrome) {
return I18n.t("user.passkeys.name.google_password_manager");
}
return I18n.t("user.passkeys.name.default");
} }
async createPasskey() { async createPasskey() {
@ -89,7 +80,7 @@ export default class UserPasskeys extends Component {
type: credential.type, type: credential.type,
attestation: bufferToBase64(credential.response.attestationObject), attestation: bufferToBase64(credential.response.attestationObject),
clientData: bufferToBase64(credential.response.clientDataJSON), clientData: bufferToBase64(credential.response.clientDataJSON),
name: this.passkeyDefaultName(), name: I18n.t("user.passkeys.name.default"),
}; };
const registrationResponse = await this.args.model.registerPasskey( const registrationResponse = await this.args.model.registerPasskey(
@ -111,6 +102,8 @@ export default class UserPasskeys extends Component {
bodyComponentModel: registrationResponse, bodyComponentModel: registrationResponse,
}); });
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.error(error);
this.errorMessage = this.errorMessage =
error.name === "InvalidStateError" error.name === "InvalidStateError"
? I18n.t("user.passkeys.already_added_error") ? I18n.t("user.passkeys.already_added_error")
@ -221,7 +214,7 @@ export default class UserPasskeys extends Component {
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{#if this.isCurrentUser}} {{#if this.showActions}}
<div class="passkey-right"> <div class="passkey-right">
<div class="actions"> <div class="actions">
<PasskeyOptionsDropdown <PasskeyOptionsDropdown
@ -239,15 +232,15 @@ export default class UserPasskeys extends Component {
{{/each}} {{/each}}
</div> </div>
<div class="controls pref-passkeys__add"> {{#if this.showActions}}
{{#if this.isCurrentUser}} <div class="controls pref-passkeys__add">
<DButton <DButton
@action={{this.addPasskey}} @action={{this.addPasskey}}
@icon="plus" @icon="plus"
@label="user.passkeys.add_passkey" @label="user.passkeys.add_passkey"
/> />
{{/if}} </div>
</div> {{/if}}
</div> </div>
</template> </template>
} }

@ -177,4 +177,33 @@ acceptance("User Preferences - Security", function (needs) {
"displays a dialog to confirm the user's identity before adding a passkey" "displays a dialog to confirm the user's identity before adding a passkey"
); );
}); });
test("Viewing Passkeys - another user has a key", async function (assert) {
this.siteSettings.experimental_passkeys = true;
// user charlie has passkeys in fixtures
await visit("/u/charlie/preferences/security");
assert.strictEqual(
query(".pref-passkeys__rows .row-passkey__name").innerText.trim(),
"iCloud Keychain",
"displays the passkey name"
);
assert
.dom(".row-passkey__created-date")
.exists("displays the created at date for the passkey");
assert
.dom(".row-passkey__used-date")
.exists("displays the last used at date for the passkey");
assert
.dom(".pref-passkeys__add")
.doesNotExist("does not show add passkey button");
assert
.dom(".passkey-options-dropdown")
.doesNotExist("does not show passkey options dropdown");
});
}); });

@ -2724,6 +2724,14 @@ export default {
text_size_seq: 0, text_size_seq: 0,
timezone: "America/Los_Angeles", timezone: "America/Los_Angeles",
}, },
user_passkeys: [
{
id: 2,
name: "iCloud Keychain",
last_used: "2023-10-10T20:03:20.986Z",
created_at: "2023-10-09T20:01:37.578Z",
},
],
}, },
}, },
"/u/charlie/card.json": { "/u/charlie/card.json": {

@ -806,3 +806,8 @@
max-width: 500px; max-width: 500px;
} }
} }
.rename-passkey__message {
max-width: 500px;
margin-bottom: 2em;
}

@ -1518,15 +1518,13 @@ en:
rename_passkey_instructions: "Pick a passkey name that will easily identify it for you, for example, use the name of your password manager." rename_passkey_instructions: "Pick a passkey name that will easily identify it for you, for example, use the name of your password manager."
name: name:
default: "Main Passkey" default: "Main Passkey"
google_password_manager: "Google Password Manager"
icloud_keychain: "iCloud Keychain"
save: "Save" save: "Save"
title: "Passkeys" title: "Passkeys"
short_description: "Passkeys are password replacements that validate your identity biometrically (e.g. touch, faceID) or via a device PIN/password." short_description: "Passkeys are password replacements that validate your identity biometrically (e.g. touch, faceID) or via a device PIN/password."
added_prefix: "Added" added_prefix: "Added"
last_used_prefix: "Last Used" last_used_prefix: "Last Used"
never_used: "Never Used" never_used: "Never Used"
not_allowed_error: "The passkey registration process either timed out or was cancelled." not_allowed_error: "The passkey registration process either timed out, was cancelled or is not allowed."
already_added_error: "You have already registered this passkey. You don’t have to register it again." already_added_error: "You have already registered this passkey. You don’t have to register it again."
change_about: change_about: