mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-07 17:59:47 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a40290514 | ||
|
|
226a3917af | ||
|
|
7920127391 | ||
|
|
65ff23bd48 | ||
|
|
3e8bf4a3c7 | ||
|
|
399005abc6 | ||
|
|
7fdc7f9216 | ||
|
|
c677847e29 | ||
|
|
795cdd3740 | ||
|
|
9894e2a6d1 | ||
|
|
c8a9eb41c5 | ||
|
|
e431a13a1a | ||
|
|
9a3496ae70 | ||
|
|
4cf743883a | ||
|
|
2d719e45fb | ||
|
|
0b456e3492 | ||
|
|
eee7a22e26 |
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:43.272.6@sha256:e9dee374e7a32827af434362c6e503aa179168a96a8212cc4a5c64bb5c550142
|
||||
image: ghcr.io/renovatebot/renovate:43.281.1@sha256:34c2dd58f58e8976be2024a24fec23bbee805f0bf887837d9aaee7daeb09ccfc
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
Generated
+173
-162
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -344,7 +344,7 @@ version = "1.1.1"
|
||||
[workspace.dependencies.ruma]
|
||||
# version = "0.14.1"
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "04d5d68841c3c8d71e5fe6f7899ccccad94274c3"
|
||||
rev = "3ad047126b321d5fdcf170e1f2e545fed820cf75"
|
||||
features = [
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
@@ -503,7 +503,7 @@ default-features = false
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.syn]
|
||||
version = "2.0"
|
||||
version = "3.0"
|
||||
default-features = false
|
||||
features = ["full", "extra-traits"]
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Dehydrated devices are now visible in the account panel. Contributed by @ginger.
|
||||
@@ -0,0 +1 @@
|
||||
Added an admin command to issue an access token for a bot account, to allow legacy bots to function while legacy authentication is disabled.
|
||||
@@ -0,0 +1 @@
|
||||
Re-introduced admin room registration alerts that were accidentally removed in the OAuth2 update.
|
||||
@@ -3,7 +3,7 @@
|
||||
use service::registration_tokens::TokenExpires;
|
||||
|
||||
impl crate::Context<'_> {
|
||||
pub(super) async fn issue_token(&self, expires: super::TokenExpires) -> Result {
|
||||
pub(super) async fn issue_registration_token(&self, expires: super::TokenExpires) -> Result {
|
||||
let expires = {
|
||||
if expires.immortal {
|
||||
None
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
pub enum TokenCommand {
|
||||
/// Issue a new registration token
|
||||
#[clap(name = "issue")]
|
||||
IssueToken {
|
||||
IssueRegistrationToken {
|
||||
/// When this token will expire.
|
||||
#[command(flatten)]
|
||||
expires: TokenExpires,
|
||||
|
||||
+31
-14
@@ -20,7 +20,7 @@
|
||||
tag::{TagEvent, TagEventContent, TagInfo},
|
||||
},
|
||||
};
|
||||
use service::users::{AccountStatus, HashedPassword};
|
||||
use service::users::{AccountStatus, DeviceToken, HashedPassword};
|
||||
|
||||
use crate::{
|
||||
get_room_info,
|
||||
@@ -59,13 +59,42 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
|
||||
self.services
|
||||
.users
|
||||
.create_local_account(&user_id, Some(HashedPassword::new(password)?), None)
|
||||
.create_local_account(
|
||||
&user_id,
|
||||
Some(HashedPassword::new(password)?),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("Created user {user_id} with password `{password}`"))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn issue_access_token(&self, username: String, password: String) -> Result {
|
||||
let user_id = parse_active_local_user_id(self.services, &username).await?;
|
||||
|
||||
let user_id = self
|
||||
.services
|
||||
.users
|
||||
.check_password(&user_id, &password)
|
||||
.await?;
|
||||
let token = DeviceToken::new_random();
|
||||
|
||||
let device_id = self
|
||||
.services
|
||||
.users
|
||||
.create_device(&user_id, None, Some(token.clone()), None, None)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!(
|
||||
"Created device `{device_id}` with access token `{}` for {user_id}",
|
||||
token.into_token()
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result {
|
||||
// Validate user id
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
@@ -102,7 +131,6 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
}
|
||||
|
||||
pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -123,7 +151,6 @@ pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -935,7 +962,6 @@ pub(super) async fn force_leave_remote_room(
|
||||
}
|
||||
|
||||
pub(super) async fn lock(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -956,7 +982,6 @@ pub(super) async fn lock(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn unlock(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
self.services.users.unlock_account(&user_id).await;
|
||||
@@ -966,7 +991,6 @@ pub(super) async fn unlock(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn logout(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -992,7 +1016,6 @@ pub(super) async fn logout(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn disable_login(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -1011,7 +1034,6 @@ pub(super) async fn disable_login(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn enable_login(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
self.services.users.enable_login(&user_id);
|
||||
@@ -1020,7 +1042,6 @@ pub(super) async fn enable_login(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn get_email(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
match self
|
||||
@@ -1039,8 +1060,6 @@ pub(super) async fn get_email(&self, user_id: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn get_user_by_email(&self, email: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let Ok(email) = Address::try_from(email) else {
|
||||
return Err!("Invalid email address.");
|
||||
};
|
||||
@@ -1063,8 +1082,6 @@ pub(super) async fn get_user_by_email(&self, email: String) -> Result {
|
||||
}
|
||||
|
||||
pub(super) async fn change_email(&self, user_id: String, email: Option<String>) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let Ok(new_email) = email.map(Address::try_from).transpose() else {
|
||||
return Err!("Invalid email address.");
|
||||
|
||||
@@ -18,6 +18,15 @@ pub enum UserCommand {
|
||||
password: Option<String>,
|
||||
},
|
||||
|
||||
/// Issue an access token for a user. This command will not work on
|
||||
/// shadow users, such as appservice puppets or accounts imported from
|
||||
/// an identity provider.
|
||||
#[clap(name = "issue-token")]
|
||||
IssueAccessToken {
|
||||
username: String,
|
||||
password: String,
|
||||
},
|
||||
|
||||
/// Reset user password
|
||||
ResetPassword {
|
||||
/// Log out existing sessions
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
};
|
||||
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
|
||||
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use crate::{Ruma, router::ClientIdentity};
|
||||
|
||||
pub(crate) mod register;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Result, debug_info, info,
|
||||
utils::{self},
|
||||
};
|
||||
use conduwuit::{Err, Result, debug_info, info};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use lettre::{Address, message::Mailbox};
|
||||
@@ -24,7 +21,6 @@
|
||||
users::{DeviceToken, HashedPassword},
|
||||
};
|
||||
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use crate::{Ruma, client_ip::ClientIp};
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
@@ -99,7 +95,13 @@ pub(crate) async fn register_route(
|
||||
|
||||
services
|
||||
.users
|
||||
.create_local_account(&user_id, Some(password), identity.email)
|
||||
.create_local_account(
|
||||
&user_id,
|
||||
Some(password),
|
||||
identity.email,
|
||||
Some(&client),
|
||||
body.initial_device_display_name.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
user_id
|
||||
@@ -114,26 +116,21 @@ pub(crate) async fn register_route(
|
||||
)));
|
||||
}
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let new_token = DeviceToken::new_random();
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
let device_id = services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
body.device_id.clone(),
|
||||
Some(new_token.clone()),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
(Some(new_token), Some(device_id))
|
||||
} else {
|
||||
// Don't create a device for inhibited logins
|
||||
|
||||
@@ -88,7 +88,7 @@ pub(crate) async fn update_device_route(
|
||||
.users
|
||||
.create_device(
|
||||
sender_user,
|
||||
&body.device_id,
|
||||
Some(body.device_id.clone()),
|
||||
None,
|
||||
body.display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
|
||||
@@ -90,8 +90,5 @@
|
||||
pub(super) use voip::*;
|
||||
pub(super) use well_known::*;
|
||||
|
||||
/// generated device ID length
|
||||
const DEVICE_ID_LENGTH: usize = 10;
|
||||
|
||||
/// generated user access token length
|
||||
const TOKEN_LENGTH: usize = 32;
|
||||
|
||||
+11
-16
@@ -3,7 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Result, debug, err, info,
|
||||
utils::{self, ReadyExt, stream::BroadbandExt},
|
||||
utils::{ReadyExt, stream::BroadbandExt},
|
||||
warn,
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
@@ -30,7 +30,6 @@
|
||||
};
|
||||
use service::users::DeviceToken;
|
||||
|
||||
use super::DEVICE_ID_LENGTH;
|
||||
use crate::{Ruma, client_ip::ClientIp};
|
||||
|
||||
/// # `GET /_matrix/client/v3/login`
|
||||
@@ -189,43 +188,39 @@ pub(crate) async fn login_route(
|
||||
},
|
||||
};
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate a new token for the device
|
||||
let token = DeviceToken::new_random();
|
||||
|
||||
// Determine if device_id was provided and exists in the db for this user
|
||||
let device_exists = if body.device_id.is_some() {
|
||||
let existing_device_id = if let Some(device_id) = &body.device_id {
|
||||
services
|
||||
.users
|
||||
.all_device_ids(&user_id)
|
||||
.ready_any(|v| v == device_id)
|
||||
.ready_find(|v| v == device_id)
|
||||
.await
|
||||
} else {
|
||||
false
|
||||
None
|
||||
};
|
||||
|
||||
if device_exists {
|
||||
let device_id = if let Some(existing_device_id) = existing_device_id {
|
||||
services
|
||||
.users
|
||||
.set_token(&user_id, &device_id, token.clone())
|
||||
.set_token(&user_id, &existing_device_id, token.clone())
|
||||
.await?;
|
||||
|
||||
existing_device_id
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
body.device_id.clone(),
|
||||
Some(token.clone()),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
.await?
|
||||
};
|
||||
|
||||
// send client well-known if specified so the client knows to reconfigure itself
|
||||
let client_discovery_info: Option<DiscoveryInfo> = services
|
||||
|
||||
+14
-14
@@ -547,7 +547,7 @@ async fn create_session(
|
||||
.iter()
|
||||
.find_map(|scope| {
|
||||
if let Scope::Device(device_id) = scope {
|
||||
Some(device_id)
|
||||
Some(device_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -557,7 +557,7 @@ async fn create_session(
|
||||
if self
|
||||
.services
|
||||
.users
|
||||
.get_device_metadata(&authorizing_user, device_id)
|
||||
.get_device_metadata(&authorizing_user, &device_id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@@ -567,11 +567,11 @@ async fn create_session(
|
||||
));
|
||||
}
|
||||
|
||||
self.services
|
||||
let device_id = self.services
|
||||
.users
|
||||
.create_device(
|
||||
&authorizing_user,
|
||||
device_id,
|
||||
Some(device_id),
|
||||
Some(access_token.clone()),
|
||||
client_name,
|
||||
None,
|
||||
@@ -581,8 +581,16 @@ async fn create_session(
|
||||
// failure during authentication, which should(?) be impossible(?)
|
||||
.expect("failed to create device");
|
||||
|
||||
info!(
|
||||
?client_id,
|
||||
?authorizing_user,
|
||||
?device_id,
|
||||
?requested_scopes,
|
||||
"Created new oauth session"
|
||||
);
|
||||
|
||||
self.db.userdeviceid_oauthsessioninfo.put(
|
||||
(&authorizing_user, device_id),
|
||||
(&authorizing_user, &device_id),
|
||||
Json(SessionInfo {
|
||||
client_id: client_id.clone(),
|
||||
current_refresh_token: refresh_token.clone(),
|
||||
@@ -595,18 +603,10 @@ async fn create_session(
|
||||
Json(RefreshTokenInfo {
|
||||
client_id: client_id.clone(),
|
||||
user_id: authorizing_user.clone(),
|
||||
device_id: device_id.to_owned(),
|
||||
device_id,
|
||||
}),
|
||||
);
|
||||
|
||||
info!(
|
||||
?client_id,
|
||||
?authorizing_user,
|
||||
?device_id,
|
||||
?requested_scopes,
|
||||
"Created new oauth session"
|
||||
);
|
||||
|
||||
Ok(TokenResponse {
|
||||
access_token: access_token.into_token(),
|
||||
token_type: TokenType::Bearer,
|
||||
|
||||
@@ -367,7 +367,7 @@ pub async fn complete_session(
|
||||
// Create a new shadow user
|
||||
self.services
|
||||
.users
|
||||
.create_local_account(&user_id, None, None)
|
||||
.create_local_account(&user_id, None, None, None, None)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to create a shadow user for {user_id}: {err}");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, OwnedServerSigningKeyId,
|
||||
events::room::policy::POLICY_SERVER_ED25519_SIGNING_KEY_ID,
|
||||
room_version_rules::SignaturesRules,
|
||||
signatures::{VerificationError, required_server_signatures_to_verify_event},
|
||||
};
|
||||
@@ -27,6 +28,9 @@ pub(super) fn required_keys(
|
||||
.cloned()
|
||||
.map(TryInto::try_into)
|
||||
.filter_map(Result::ok)
|
||||
.filter(|key_id: &OwnedServerSigningKeyId| {
|
||||
key_id.as_str() != POLICY_SERVER_ED25519_SIGNING_KEY_ID
|
||||
})
|
||||
.for_each(|key_id| entry.push(key_id));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use conduwuit::{
|
||||
Err, Result, debug_error, debug_warn, err, error, info, trace,
|
||||
@@ -136,6 +139,8 @@ pub async fn create_local_account(
|
||||
user_id: &UserId,
|
||||
password: Option<HashedPassword>,
|
||||
email: Option<Address>,
|
||||
client: Option<&IpAddr>,
|
||||
device_name: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.create_shadow_account(user_id).await?;
|
||||
|
||||
@@ -143,6 +148,22 @@ pub async fn create_local_account(
|
||||
self.convert_to_local_account(user_id, password).await?;
|
||||
}
|
||||
|
||||
if let Some(client) = client {
|
||||
let notice = if let Some(device_name) = device_name {
|
||||
format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and \
|
||||
device display name \"{device_name}\".",
|
||||
)
|
||||
} else {
|
||||
format!("New user \"{user_id}\" registered on this server from IP {client}.")
|
||||
};
|
||||
info!("{notice}");
|
||||
|
||||
if self.services.config.admin_room_notices {
|
||||
self.services.admin.notice(¬ice).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Set an initial display name
|
||||
{
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
@@ -40,7 +40,7 @@ pub async fn set_dehydrated_device(&self, user_id: &UserId, request: Request) ->
|
||||
|
||||
self.create_device(
|
||||
user_id,
|
||||
&request.device_id,
|
||||
Some(request.device_id.clone()),
|
||||
None,
|
||||
request.initial_device_display_name.clone(),
|
||||
None,
|
||||
|
||||
@@ -42,32 +42,39 @@ pub fn into_token(self) -> String { self.token }
|
||||
impl super::Service {
|
||||
/// Adds a new device to a user.
|
||||
///
|
||||
/// If no `device_id` is provided, a random one will be generated.
|
||||
///
|
||||
/// If no `token` is provided, the device will not be able to be logged
|
||||
/// into.
|
||||
pub async fn create_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
device_id: Option<OwnedDeviceId>,
|
||||
token: Option<DeviceToken>,
|
||||
initial_device_display_name: Option<String>,
|
||||
client_ip: Option<String>,
|
||||
) -> Result<()> {
|
||||
) -> Result<OwnedDeviceId> {
|
||||
const DEVICE_ID_LENGTH: usize = 10;
|
||||
|
||||
self.status(user_id).await.ensure_active()?;
|
||||
|
||||
let key = (user_id, device_id);
|
||||
let mut device = Device::new(device_id.into());
|
||||
let device_id =
|
||||
device_id.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
let mut device = Device::new(device_id.clone());
|
||||
device.display_name = initial_device_display_name;
|
||||
device.last_seen_ip = client_ip;
|
||||
device.last_seen_ts = Some(MilliSecondsSinceUnixEpoch::now());
|
||||
|
||||
let key = (user_id, &device_id);
|
||||
increment(&self.db.userid_devicelistversion, user_id.as_bytes());
|
||||
self.db.userdeviceid_metadata.put(key, Json(device));
|
||||
|
||||
if let Some(token) = token {
|
||||
self.set_token(user_id, device_id, token).await?;
|
||||
self.set_token(user_id, &device_id, token).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(device_id)
|
||||
}
|
||||
|
||||
/// Removes a device from a user.
|
||||
|
||||
@@ -70,6 +70,7 @@ enum AccountBody {
|
||||
email_requirement: EmailRequirement,
|
||||
email: Option<String>,
|
||||
devices: Vec<DeviceCard>,
|
||||
dehydrated_device_id: Option<OwnedDeviceId>,
|
||||
},
|
||||
Locked,
|
||||
}
|
||||
@@ -132,7 +133,8 @@ async fn get_account(
|
||||
oidc_enabled: services.oidc.enabled(),
|
||||
email_requirement,
|
||||
email,
|
||||
devices: device_cards
|
||||
devices: device_cards,
|
||||
dehydrated_device_id,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{collections::BTreeMap, time::SystemTime};
|
||||
use std::{collections::BTreeMap, net::IpAddr, time::SystemTime};
|
||||
|
||||
use axum::{
|
||||
Extension, Router,
|
||||
@@ -6,6 +6,7 @@
|
||||
response::{Redirect, Response},
|
||||
routing::{get, on},
|
||||
};
|
||||
use conduwuit_api::client_ip::ClientIp;
|
||||
use conduwuit_core::{config::TermsDocument, warn};
|
||||
use conduwuit_service::{
|
||||
mailer::messages,
|
||||
@@ -116,6 +117,7 @@ struct CompletedRegistration {
|
||||
|
||||
async fn route_register(
|
||||
State(services): State<crate::State>,
|
||||
ClientIp(client): ClientIp, // NOTE: Required for metadata.
|
||||
Extension(context): Extension<TemplateContext>,
|
||||
session_store: Session,
|
||||
Expect(Query(query)): Expect<Query<RegisterQuery>>,
|
||||
@@ -144,6 +146,7 @@ async fn route_register(
|
||||
session_store,
|
||||
form,
|
||||
query.next.clone(),
|
||||
&client,
|
||||
)
|
||||
.boxed()
|
||||
.await?
|
||||
@@ -276,6 +279,7 @@ struct RegisterEmailValidateQuery {
|
||||
|
||||
async fn get_register_email_validate(
|
||||
State(services): State<crate::State>,
|
||||
ClientIp(client): ClientIp, // NOTE: Required for metadata.
|
||||
Extension(context): Extension<TemplateContext>,
|
||||
session_store: Session,
|
||||
Expect(Query(RegisterEmailValidateQuery {
|
||||
@@ -303,8 +307,14 @@ async fn get_register_email_validate(
|
||||
let email = session.consume();
|
||||
|
||||
response!(
|
||||
complete_registration(&services, session_store, completed_registration, Some(email))
|
||||
.await?
|
||||
complete_registration(
|
||||
&services,
|
||||
session_store,
|
||||
completed_registration,
|
||||
Some(email),
|
||||
&client
|
||||
)
|
||||
.await?
|
||||
)
|
||||
}
|
||||
|
||||
@@ -314,6 +324,7 @@ async fn begin_registration(
|
||||
session_store: Session,
|
||||
form: RegistrationForm,
|
||||
next: Option<LoginTarget>,
|
||||
client: &IpAddr,
|
||||
) -> Result<Result<Response, ValidationErrors>> {
|
||||
let open_registration = services
|
||||
.config
|
||||
@@ -496,7 +507,8 @@ async fn begin_registration(
|
||||
} else {
|
||||
// If email isn't required we can immediately complete registration
|
||||
Ok(response!(
|
||||
complete_registration(services, session_store, completed_registration, None).await?
|
||||
complete_registration(services, session_store, completed_registration, None, client)
|
||||
.await?
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -511,10 +523,11 @@ async fn complete_registration(
|
||||
next,
|
||||
}: CompletedRegistration,
|
||||
email: Option<Address>,
|
||||
client: &IpAddr,
|
||||
) -> Result<Redirect> {
|
||||
services
|
||||
.users
|
||||
.create_local_account(&user_id, Some(password_hash), email)
|
||||
.create_local_account(&user_id, Some(password_hash), email, Some(client), None)
|
||||
.await?;
|
||||
|
||||
if let Some(registration_token) = registration_token {
|
||||
|
||||
@@ -67,12 +67,15 @@ pub(super) async fn for_local_user(services: &Services, user_id: &UserId) -> Sel
|
||||
pub(super) fn for_device(
|
||||
client_metadata: Option<&ClientMetadata>,
|
||||
display_name: Option<&str>,
|
||||
dehydrated: bool,
|
||||
) -> Self {
|
||||
let avatar_src = client_metadata
|
||||
.and_then(|metadata| metadata.logo_uri.as_ref())
|
||||
.map(|uri| uri.as_str().to_owned());
|
||||
|
||||
let avatar_type = if let Some(avatar_src) = avatar_src {
|
||||
let avatar_type = if dehydrated {
|
||||
AvatarType::Initial('⊡')
|
||||
} else if let Some(avatar_src) = avatar_src {
|
||||
AvatarType::Image(avatar_src)
|
||||
} else if let Some(initial) = display_name.and_then(|name| name.chars().next()) {
|
||||
if client_metadata.is_some() {
|
||||
@@ -130,6 +133,7 @@ pub(super) struct DeviceCard {
|
||||
pub last_active: String,
|
||||
pub oauth_metadata: Option<ClientMetadata>,
|
||||
pub style: DeviceCardStyle,
|
||||
pub dehydrated: bool,
|
||||
}
|
||||
|
||||
impl HtmlSafe for DeviceCard {}
|
||||
@@ -163,12 +167,21 @@ pub(super) async fn for_device(
|
||||
}
|
||||
.await;
|
||||
|
||||
let dehydrated_device_id = services
|
||||
.users
|
||||
.get_dehydrated_device(user_id)
|
||||
.await
|
||||
.ok()
|
||||
.map(|device| device.device_id);
|
||||
let dehydrated = dehydrated_device_id.as_ref() == Some(&device.device_id);
|
||||
|
||||
let display_name = oauth_metadata
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.client_name.clone())
|
||||
.or_else(|| device.display_name.clone());
|
||||
|
||||
let avatar = Avatar::for_device(oauth_metadata.as_ref(), display_name.as_deref());
|
||||
let avatar =
|
||||
Avatar::for_device(oauth_metadata.as_ref(), display_name.as_deref(), dehydrated);
|
||||
|
||||
let last_active = device.last_seen_ts.map_or_else(
|
||||
|| "unknown".to_owned(),
|
||||
@@ -190,6 +203,7 @@ pub(super) async fn for_device(
|
||||
last_active,
|
||||
oauth_metadata,
|
||||
style,
|
||||
dehydrated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
<div class="card">
|
||||
{{ avatar }}
|
||||
<div class="info">
|
||||
<div class="name">
|
||||
<span>
|
||||
{% if let Some(display_name) = display_name %}
|
||||
{{ display_name }}
|
||||
{% else %}
|
||||
Unknown device
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if style == DeviceCardStyle::Detailed %}
|
||||
<span class="id">
|
||||
<span class="mobile-hidden">•</span>
|
||||
<ul class="bullet-separated">
|
||||
<li>{{ device_id }}</li>
|
||||
<li>
|
||||
{% if let Some(metadata) = oauth_metadata %}
|
||||
<a href="{{ metadata.client_uri }}">Client website</a>
|
||||
{% else %}
|
||||
legacy
|
||||
{% endif %}
|
||||
</li>
|
||||
</span>
|
||||
{% if dehydrated %}
|
||||
<div class="name">Dehydrated device</div>
|
||||
{% else %}
|
||||
<div class="name">
|
||||
<span>
|
||||
{% if let Some(display_name) = display_name %}
|
||||
{{ display_name }}
|
||||
{% else %}
|
||||
Unknown device
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if style == DeviceCardStyle::Detailed %}
|
||||
<span class="id">
|
||||
<span class="mobile-hidden">•</span>
|
||||
<ul class="bullet-separated">
|
||||
<li>{{ device_id }}</li>
|
||||
<li>
|
||||
{% if let Some(metadata) = oauth_metadata %}
|
||||
<a href="{{ metadata.client_uri }}">Client website</a>
|
||||
{% else %}
|
||||
legacy
|
||||
{% endif %}
|
||||
</li>
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Last active: {{ last_active }}
|
||||
</div>
|
||||
<div>
|
||||
{% if style != DeviceCardStyle::Detailed %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/device/{{ device_id }}/">Details</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
Last active: {{ last_active }}
|
||||
</div>
|
||||
<div>
|
||||
{% if style != DeviceCardStyle::Detailed %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/device/{{ device_id }}/">Details</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ Your account
|
||||
<h1>Manage your account</h1>
|
||||
{{ user_card }}
|
||||
{% match body %}
|
||||
{% when AccountBody::Unlocked { suspended, email_requirement, email, devices, oidc_enabled } %}
|
||||
{% when AccountBody::Unlocked { suspended, email_requirement, email, devices, oidc_enabled, dehydrated_device_id } %}
|
||||
{% if suspended %}
|
||||
<p class="card danger">
|
||||
⚠️ Your account has been suspended by your homeserver's administrator.
|
||||
@@ -52,6 +52,9 @@ Your account
|
||||
and sign in to start chatting on Matrix.
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% if let Some(dehydrated_device_id) = dehydrated_device_id %}
|
||||
<small>⊡ Your account has a dehydrated device. <a href="device/{{ dehydrated_device_id | urlencode_strict }}/">View details</a></small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
@@ -27,6 +27,8 @@ Device information
|
||||
{% if let Some((_, session_info)) = client_metadata %}
|
||||
This device has permission to:
|
||||
{{ ClientScopes { scopes: session_info.scopes.clone() } }}
|
||||
{% else if device_card.dehydrated %}
|
||||
This is your dehydrated device. It saves encryption keys for you while you're offline.
|
||||
{% else %}
|
||||
This device can access and control all features of your Matrix account.
|
||||
<br>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use askama::filters::urlencode_strict;
|
||||
use axum::{
|
||||
extract::FromRequestParts,
|
||||
http::request::Parts,
|
||||
@@ -63,8 +64,11 @@ pub(crate) fn target_path(&self) -> String {
|
||||
| Self::DeviceCode(code) =>
|
||||
format!("oauth2/grant/device_code?{}", serde_urlencoded::to_string(code).unwrap())
|
||||
.into(),
|
||||
| Self::DeviceInfo(path) => format!("account/device/{}/", path.device).into(),
|
||||
| Self::RemoveDevice(path) => format!("account/device/{}/remove", path.device).into(),
|
||||
| Self::DeviceInfo(path) =>
|
||||
format!("account/device/{}/", urlencode_strict(&path.device).unwrap()).into(),
|
||||
| Self::RemoveDevice(path) =>
|
||||
format!("account/device/{}/remove", urlencode_strict(&path.device).unwrap())
|
||||
.into(),
|
||||
};
|
||||
|
||||
format!("{ROUTE_PREFIX}/{path}")
|
||||
|
||||
Reference in New Issue
Block a user