Generate a device name based on the client name and user agent
This commit is contained in:
@@ -203,6 +203,7 @@ where
|
||||
Encrypter: FromRef<S>,
|
||||
reqwest::Client: FromRef<S>,
|
||||
SiteConfig: FromRef<S>,
|
||||
Templates: FromRef<S>,
|
||||
Arc<dyn HomeserverConnection>: FromRef<S>,
|
||||
BoxClock: FromRequestParts<S>,
|
||||
BoxRng: FromRequestParts<S>,
|
||||
|
||||
@@ -18,6 +18,7 @@ use mas_axum_utils::{
|
||||
use mas_data_model::{
|
||||
AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, SiteConfig, TokenType,
|
||||
};
|
||||
use mas_i18n::DataLocale;
|
||||
use mas_keystore::{Encrypter, Keystore};
|
||||
use mas_matrix::HomeserverConnection;
|
||||
use mas_oidc_client::types::scope::ScopeToken;
|
||||
@@ -31,6 +32,7 @@ use mas_storage::{
|
||||
},
|
||||
user::BrowserSessionRepository,
|
||||
};
|
||||
use mas_templates::{DeviceNameContext, TemplateContext, Templates};
|
||||
use oauth2_types::{
|
||||
errors::{ClientError, ClientErrorCode},
|
||||
pkce::CodeChallengeError,
|
||||
@@ -261,6 +263,8 @@ impl IntoResponse for RouteError {
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_error_for_route!(mas_i18n::DataError);
|
||||
impl_from_error_for_route!(mas_templates::TemplateError);
|
||||
impl_from_error_for_route!(mas_storage::RepositoryError);
|
||||
impl_from_error_for_route!(mas_policy::EvaluationError);
|
||||
impl_from_error_for_route!(super::IdTokenSignatureError);
|
||||
@@ -281,6 +285,7 @@ pub(crate) async fn post(
|
||||
State(homeserver): State<Arc<dyn HomeserverConnection>>,
|
||||
State(site_config): State<SiteConfig>,
|
||||
State(encrypter): State<Encrypter>,
|
||||
State(templates): State<Templates>,
|
||||
policy: Policy,
|
||||
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
||||
client_authorization: ClientAuthorization<AccessTokenRequest>,
|
||||
@@ -334,6 +339,7 @@ pub(crate) async fn post(
|
||||
&site_config,
|
||||
repo,
|
||||
&homeserver,
|
||||
&templates,
|
||||
user_agent,
|
||||
)
|
||||
.await?
|
||||
@@ -415,6 +421,7 @@ async fn authorization_code_grant(
|
||||
site_config: &SiteConfig,
|
||||
mut repo: BoxRepository,
|
||||
homeserver: &Arc<dyn HomeserverConnection>,
|
||||
templates: &Templates,
|
||||
user_agent: Option<String>,
|
||||
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
|
||||
// Check that the client is allowed to use this grant type
|
||||
@@ -482,6 +489,11 @@ async fn authorization_code_grant(
|
||||
.await?
|
||||
.ok_or(RouteError::NoSuchOAuthSession(session_id))?;
|
||||
|
||||
// Generate a device name
|
||||
let lang: DataLocale = authz_grant.locale.as_deref().unwrap_or("en").parse()?;
|
||||
let ctx = DeviceNameContext::new(client.clone(), user_agent.clone()).with_language(lang);
|
||||
let device_name = templates.render_device_name(&ctx)?;
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
session = repo
|
||||
.oauth2_session()
|
||||
@@ -567,7 +579,7 @@ async fn authorization_code_grant(
|
||||
for scope in &*session.scope {
|
||||
if let Some(device) = Device::from_scope_token(scope) {
|
||||
homeserver
|
||||
.create_device(&mxid, device.as_str(), None)
|
||||
.create_device(&mxid, device.as_str(), Some(&device_name))
|
||||
.await
|
||||
.map_err(RouteError::ProvisionDeviceFailed)?;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod translator;
|
||||
pub use icu_calendar;
|
||||
pub use icu_datetime;
|
||||
pub use icu_locid::locale;
|
||||
pub use icu_provider::DataLocale;
|
||||
pub use icu_provider::{DataError, DataLocale};
|
||||
|
||||
pub use self::{
|
||||
sprintf::{Argument, ArgumentList, Message},
|
||||
|
||||
@@ -1564,6 +1564,39 @@ impl TemplateContext for AccountInactiveContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `device_name.txt` template
|
||||
#[derive(Serialize)]
|
||||
pub struct DeviceNameContext {
|
||||
client: Client,
|
||||
raw_user_agent: String,
|
||||
}
|
||||
|
||||
impl DeviceNameContext {
|
||||
/// Constructs a new context with a client and user agent
|
||||
#[must_use]
|
||||
pub fn new(client: Client, user_agent: Option<String>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
raw_user_agent: user_agent.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateContext for DeviceNameContext {
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Client::samples(now, rng)
|
||||
.into_iter()
|
||||
.map(|client| DeviceNameContext {
|
||||
client,
|
||||
raw_user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36".to_owned(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `form_post.html` template
|
||||
#[derive(Serialize)]
|
||||
pub struct FormPostContext<T> {
|
||||
|
||||
@@ -35,13 +35,13 @@ mod macros;
|
||||
pub use self::{
|
||||
context::{
|
||||
AccountInactiveContext, ApiDocContext, AppContext, CompatSsoContext, ConsentContext,
|
||||
DeviceConsentContext, DeviceLinkContext, DeviceLinkFormField, EmailRecoveryContext,
|
||||
EmailVerificationContext, EmptyContext, ErrorContext, FormPostContext, IndexContext,
|
||||
LoginContext, LoginFormField, NotFoundContext, PasswordRegisterContext,
|
||||
PolicyViolationContext, PostAuthContext, PostAuthContextInner, RecoveryExpiredContext,
|
||||
RecoveryFinishContext, RecoveryFinishFormField, RecoveryProgressContext,
|
||||
RecoveryStartContext, RecoveryStartFormField, RegisterContext, RegisterFormField,
|
||||
RegisterStepsDisplayNameContext, RegisterStepsDisplayNameFormField,
|
||||
DeviceConsentContext, DeviceLinkContext, DeviceLinkFormField, DeviceNameContext,
|
||||
EmailRecoveryContext, EmailVerificationContext, EmptyContext, ErrorContext,
|
||||
FormPostContext, IndexContext, LoginContext, LoginFormField, NotFoundContext,
|
||||
PasswordRegisterContext, PolicyViolationContext, PostAuthContext, PostAuthContextInner,
|
||||
RecoveryExpiredContext, RecoveryFinishContext, RecoveryFinishFormField,
|
||||
RecoveryProgressContext, RecoveryStartContext, RecoveryStartFormField, RegisterContext,
|
||||
RegisterFormField, RegisterStepsDisplayNameContext, RegisterStepsDisplayNameFormField,
|
||||
RegisterStepsEmailInUseContext, RegisterStepsVerifyEmailContext,
|
||||
RegisterStepsVerifyEmailFormField, SiteBranding, SiteConfigExt, SiteFeatures,
|
||||
TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, UpstreamRegisterFormField,
|
||||
@@ -417,6 +417,9 @@ register_templates! {
|
||||
|
||||
/// Render the 'account logged out' page
|
||||
pub fn render_account_logged_out(WithLanguage<WithCsrf<AccountInactiveContext>>) { "pages/account/logged_out.html" }
|
||||
|
||||
/// Render the automatic device name for OAuth 2.0 client
|
||||
pub fn render_device_name(WithLanguage<DeviceNameContext>) { "device_name.txt" }
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
@@ -459,6 +462,7 @@ impl Templates {
|
||||
check::render_upstream_oauth2_link_mismatch(self, now, rng)?;
|
||||
check::render_upstream_oauth2_suggest_link(self, now, rng)?;
|
||||
check::render_upstream_oauth2_do_register(self, now, rng)?;
|
||||
check::render_device_name(self, now, rng)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
28
templates/device_name.txt
Normal file
28
templates/device_name.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
{#
|
||||
Copyright 2024, 2025 New Vector Ltd.
|
||||
Copyright 2021-2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
-#}
|
||||
|
||||
{%- set _ = translator(lang) -%}
|
||||
|
||||
{%- set client_name = client.client_name or client.client_id -%}
|
||||
{%- set user_agent = raw_user_agent | parse_user_agent() -%}
|
||||
|
||||
{%- set device_name -%}
|
||||
{%- if user_agent.model -%}
|
||||
{{- user_agent.model -}}
|
||||
{%- elif user_agent.name -%}
|
||||
{%- if user_agent.os -%}
|
||||
{{- _("mas.device_display_name.name_for_platform", name=user_agent.name, platform=user_agent.os) -}}
|
||||
{%- else -%}
|
||||
{{- user_agent.name -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{{- _("mas.device_display_name.unknown_device") -}}
|
||||
{%- endif -%}
|
||||
{%- endset -%}
|
||||
|
||||
{{- _("mas.device_display_name.client_on_device", client_name=client_name, device_name=device_name) -}}
|
||||
@@ -6,11 +6,11 @@
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"@cancel": {
|
||||
"context": "pages/consent.html:69:11-29, pages/device_consent.html:126:13-31, pages/policy_violation.html:44:13-31"
|
||||
"context": "pages/consent.html:69:11-29, pages/device_consent.html:127:13-31, pages/policy_violation.html:44:13-31"
|
||||
},
|
||||
"continue": "Continue",
|
||||
"@continue": {
|
||||
"context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:123:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:74:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48"
|
||||
"context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:124:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:74:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48"
|
||||
},
|
||||
"create_account": "Create Account",
|
||||
"@create_account": {
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"sign_out": "Sign out",
|
||||
"@sign_out": {
|
||||
"context": "pages/account/logged_out.html:22:28-48, pages/consent.html:65:28-48, pages/device_consent.html:135:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46"
|
||||
"context": "pages/account/logged_out.html:22:28-48, pages/consent.html:65:28-48, pages/device_consent.html:136:30-50, pages/index.html:28:28-48, pages/policy_violation.html:38:28-48, pages/sso.html:45:28-48, pages/upstream_oauth2/link_mismatch.html:24:24-44, pages/upstream_oauth2/suggest_link.html:32:26-46"
|
||||
},
|
||||
"skip": "Skip",
|
||||
"@skip": {
|
||||
@@ -195,37 +195,37 @@
|
||||
},
|
||||
"heading": "Allow access to your account?",
|
||||
"@heading": {
|
||||
"context": "pages/consent.html:25:27-51, pages/device_consent.html:27:29-53"
|
||||
"context": "pages/consent.html:25:27-51, pages/device_consent.html:28:29-53"
|
||||
},
|
||||
"make_sure_you_trust": "Make sure that you trust <span>%(client_name)s</span>.",
|
||||
"@make_sure_you_trust": {
|
||||
"context": "pages/consent.html:38:81-142, pages/device_consent.html:103:83-144"
|
||||
"context": "pages/consent.html:38:81-142, pages/device_consent.html:104:83-144"
|
||||
},
|
||||
"this_will_allow": "This will allow <span>%(client_name)s</span> to:",
|
||||
"@this_will_allow": {
|
||||
"context": "pages/consent.html:28:11-68, pages/device_consent.html:93:13-70"
|
||||
"context": "pages/consent.html:28:11-68, pages/device_consent.html:94:13-70"
|
||||
},
|
||||
"you_may_be_sharing": "You may be sharing sensitive information with this site or app.",
|
||||
"@you_may_be_sharing": {
|
||||
"context": "pages/consent.html:39:7-42, pages/device_consent.html:104:9-44"
|
||||
"context": "pages/consent.html:39:7-42, pages/device_consent.html:105:9-44"
|
||||
}
|
||||
},
|
||||
"device_card": {
|
||||
"access_requested": "Access requested",
|
||||
"@access_requested": {
|
||||
"context": "pages/device_consent.html:81:34-71"
|
||||
"context": "pages/device_consent.html:82:34-71"
|
||||
},
|
||||
"device_code": "Code",
|
||||
"@device_code": {
|
||||
"context": "pages/device_consent.html:85:34-66"
|
||||
"context": "pages/device_consent.html:86:34-66"
|
||||
},
|
||||
"generic_device": "Device",
|
||||
"@generic_device": {
|
||||
"context": "pages/device_consent.html:69:22-57"
|
||||
"context": "pages/device_consent.html:70:22-57"
|
||||
},
|
||||
"ip_address": "IP address",
|
||||
"@ip_address": {
|
||||
"context": "pages/device_consent.html:76:36-67"
|
||||
"context": "pages/device_consent.html:77:36-67"
|
||||
}
|
||||
},
|
||||
"device_code_link": {
|
||||
@@ -241,29 +241,45 @@
|
||||
"device_consent": {
|
||||
"another_device_access": "Another device wants to access your account.",
|
||||
"@another_device_access": {
|
||||
"context": "pages/device_consent.html:92:13-58"
|
||||
"context": "pages/device_consent.html:93:13-58"
|
||||
},
|
||||
"denied": {
|
||||
"description": "You denied access to %(client_name)s. You can close this window.",
|
||||
"@description": {
|
||||
"context": "pages/device_consent.html:146:27-94"
|
||||
"context": "pages/device_consent.html:147:27-94"
|
||||
},
|
||||
"heading": "Access denied",
|
||||
"@heading": {
|
||||
"context": "pages/device_consent.html:145:29-67"
|
||||
"context": "pages/device_consent.html:146:29-67"
|
||||
}
|
||||
},
|
||||
"granted": {
|
||||
"description": "You granted access to %(client_name)s. You can close this window.",
|
||||
"@description": {
|
||||
"context": "pages/device_consent.html:157:27-95"
|
||||
"context": "pages/device_consent.html:158:27-95"
|
||||
},
|
||||
"heading": "Access granted",
|
||||
"@heading": {
|
||||
"context": "pages/device_consent.html:156:29-68"
|
||||
"context": "pages/device_consent.html:157:29-68"
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_display_name": {
|
||||
"client_on_device": "%(client_name)s on %(device_name)s",
|
||||
"@client_on_device": {
|
||||
"context": "device_name.txt:28:4-99",
|
||||
"description": "The automatic device name generated for a client, e.g. 'Element on iPhone'"
|
||||
},
|
||||
"name_for_platform": "%(name)s for %(platform)s",
|
||||
"@name_for_platform": {
|
||||
"context": "device_name.txt:19:10-102",
|
||||
"description": "Part of the automatic device name for the platfom, e.g. 'Safari for macOS'"
|
||||
},
|
||||
"unknown_device": "Unknown device",
|
||||
"@unknown_device": {
|
||||
"context": "device_name.txt:24:8-51"
|
||||
}
|
||||
},
|
||||
"email_in_use": {
|
||||
"description": "If you have forgotten your account credentials, you can recover your account. You can also start over and use a different email address.",
|
||||
"@description": {
|
||||
@@ -469,7 +485,7 @@
|
||||
},
|
||||
"not_you": "Not %(username)s?",
|
||||
"@not_you": {
|
||||
"context": "pages/consent.html:62:11-67, pages/device_consent.html:132:13-69, pages/sso.html:42:11-67",
|
||||
"context": "pages/consent.html:62:11-67, pages/device_consent.html:133:13-69, pages/sso.html:42:11-67",
|
||||
"description": "Suggestions for the user to log in as a different user"
|
||||
},
|
||||
"or_separator": "Or",
|
||||
|
||||
Reference in New Issue
Block a user