mirror of
https://github.com/discourse/discourse.git
synced 2025-04-14 04:50:48 +08:00

Adds UI elements for registering a passkey and logging in with it. The feature is still in an early stage, interested parties that want to try it can use the `experimental_passkeys` site setting (via Rails console). See PR for more details. --------- Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
131 lines
3.4 KiB
JavaScript
131 lines
3.4 KiB
JavaScript
import Controller from "@ember/controller";
|
|
import { action } from "@ember/object";
|
|
import { gt } from "@ember/object/computed";
|
|
import { inject as service } from "@ember/service";
|
|
import AuthTokenModal from "discourse/components/modal/auth-token";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import logout from "discourse/lib/logout";
|
|
import { userPath } from "discourse/lib/url";
|
|
import { isWebauthnSupported } from "discourse/lib/webauthn";
|
|
import CanCheckEmails from "discourse/mixins/can-check-emails";
|
|
import discourseComputed from "discourse-common/utils/decorators";
|
|
import I18n from "I18n";
|
|
|
|
// Number of tokens shown by default.
|
|
const DEFAULT_AUTH_TOKENS_COUNT = 2;
|
|
|
|
export default Controller.extend(CanCheckEmails, {
|
|
modal: service(),
|
|
passwordProgress: null,
|
|
subpageTitle: I18n.t("user.preferences_nav.security"),
|
|
showAllAuthTokens: false,
|
|
|
|
get canUsePasskeys() {
|
|
return (
|
|
!this.siteSettings.enable_discourse_connect &&
|
|
this.siteSettings.enable_local_logins &&
|
|
this.siteSettings.experimental_passkeys &&
|
|
isWebauthnSupported()
|
|
);
|
|
},
|
|
|
|
@discourseComputed("model.is_anonymous")
|
|
canChangePassword(isAnonymous) {
|
|
if (isAnonymous) {
|
|
return false;
|
|
} else {
|
|
return (
|
|
!this.siteSettings.enable_discourse_connect &&
|
|
this.siteSettings.enable_local_logins
|
|
);
|
|
}
|
|
},
|
|
|
|
@discourseComputed("showAllAuthTokens", "model.user_auth_tokens")
|
|
authTokens(showAllAuthTokens, tokens) {
|
|
tokens.sort((a, b) => {
|
|
if (a.is_active) {
|
|
return -1;
|
|
} else if (b.is_active) {
|
|
return 1;
|
|
} else {
|
|
return b.seen_at.localeCompare(a.seen_at);
|
|
}
|
|
});
|
|
|
|
return showAllAuthTokens
|
|
? tokens
|
|
: tokens.slice(0, DEFAULT_AUTH_TOKENS_COUNT);
|
|
},
|
|
|
|
canShowAllAuthTokens: gt(
|
|
"model.user_auth_tokens.length",
|
|
DEFAULT_AUTH_TOKENS_COUNT
|
|
),
|
|
|
|
@action
|
|
changePassword(event) {
|
|
event?.preventDefault();
|
|
if (!this.passwordProgress) {
|
|
this.set("passwordProgress", I18n.t("user.change_password.in_progress"));
|
|
return this.model
|
|
.changePassword()
|
|
.then(() => {
|
|
// password changed
|
|
this.setProperties({
|
|
changePasswordProgress: false,
|
|
passwordProgress: I18n.t("user.change_password.success"),
|
|
});
|
|
})
|
|
.catch(() => {
|
|
// password failed to change
|
|
this.setProperties({
|
|
changePasswordProgress: false,
|
|
passwordProgress: I18n.t("user.change_password.error"),
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
@action
|
|
toggleShowAllAuthTokens(event) {
|
|
event?.preventDefault();
|
|
this.toggleProperty("showAllAuthTokens");
|
|
},
|
|
|
|
@action
|
|
revokeAuthToken(token, event) {
|
|
event?.preventDefault();
|
|
ajax(
|
|
userPath(
|
|
`${this.get("model.username_lower")}/preferences/revoke-auth-token`
|
|
),
|
|
{
|
|
type: "POST",
|
|
data: token ? { token_id: token.id } : {},
|
|
}
|
|
)
|
|
.then(() => {
|
|
if (!token) {
|
|
logout();
|
|
} // All sessions revoked
|
|
})
|
|
.catch(popupAjaxError);
|
|
},
|
|
|
|
actions: {
|
|
save() {
|
|
this.set("saved", false);
|
|
|
|
return this.model
|
|
.then(() => this.set("saved", true))
|
|
.catch(popupAjaxError);
|
|
},
|
|
|
|
showToken(token) {
|
|
this.modal.show(AuthTokenModal, { model: token });
|
|
},
|
|
},
|
|
});
|