feat: Add admin command to issue access tokens

This commit is contained in:
Ginger authored and Ellis Git committed 2026-07-27 14:21:38 +00:00
1 parent 4cf743883a
commit 9a3496ae70
12 files changed
+79 -58

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ use futures::StreamExt;
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
+1 -1
View File
@@ -10,7 +10,7 @@ use crate::admin_command_dispatch;
pub enum TokenCommand {
/// Issue a new registration token
#[clap(name = "issue")]
IssueToken {
IssueRegistrationToken {
/// When this token will expire.
#[command(flatten)]
expires: TokenExpires,
+24 -1
View File
@@ -20,7 +20,7 @@ use ruma::{
tag::{TagEvent, TagEventContent, TagInfo},
},
};
use service::users::{AccountStatus, HashedPassword};
use service::users::{AccountStatus, DeviceToken, HashedPassword};
use crate::{
get_room_info,
@@ -66,6 +66,29 @@ impl crate::Context<'_> {
.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)?;
+9
View File
@@ -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
-1
View File
@@ -25,7 +25,6 @@ use ruma::{
};
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
use super::DEVICE_ID_LENGTH;
use crate::{Ruma, router::ClientIdentity};
pub(crate) mod register;
+4 -13
View File
@@ -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 @@ use service::{
users::{DeviceToken, HashedPassword},
};
use super::DEVICE_ID_LENGTH;
use crate::{Ruma, client_ip::ClientIp};
/// # `POST /_matrix/client/v3/register`
@@ -114,26 +110,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
+1 -1
View File
@@ -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()),
-3
View File
@@ -90,8 +90,5 @@ pub(super) use user_directory::*;
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
View File
@@ -3,7 +3,7 @@ use std::time::Duration;
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 ruma::{
};
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
View File
@@ -547,7 +547,7 @@ impl Service {
.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 @@ impl Service {
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 @@ impl Service {
));
}
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 @@ impl Service {
// 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 @@ impl Service {
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,
+1 -1
View File
@@ -40,7 +40,7 @@ impl super::Service {
self.create_device(
user_id,
&request.device_id,
Some(request.device_id.clone()),
None,
request.initial_device_display_name.clone(),
None,
+13 -6
View File
@@ -42,32 +42,39 @@ impl DeviceToken {
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.