You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-07 22:41:18 +03:00
209 lines
7.1 KiB
Rust
209 lines
7.1 KiB
Rust
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use anyhow::Context;
|
|
use clap::Parser;
|
|
use mas_config::{DatabaseConfig, PasswordsConfig};
|
|
use mas_data_model::{Device, TokenType};
|
|
use mas_storage::{
|
|
compat::{CompatAccessTokenRepository, CompatSessionRepository},
|
|
job::{JobRepositoryExt, ProvisionUserJob},
|
|
user::{UserEmailRepository, UserPasswordRepository, UserRepository},
|
|
Repository, RepositoryAccess, SystemClock,
|
|
};
|
|
use mas_storage_pg::PgRepository;
|
|
use rand::SeedableRng;
|
|
use sqlx::types::Uuid;
|
|
use tracing::{info, info_span};
|
|
|
|
use crate::util::{database_from_config, password_manager_from_config};
|
|
|
|
#[derive(Parser, Debug)]
|
|
pub(super) struct Options {
|
|
#[command(subcommand)]
|
|
subcommand: Subcommand,
|
|
}
|
|
|
|
#[derive(Parser, Debug)]
|
|
enum Subcommand {
|
|
/// Mark email address as verified
|
|
VerifyEmail { username: String, email: String },
|
|
|
|
/// Set a user password
|
|
SetPassword { username: String, password: String },
|
|
|
|
/// Issue a compatibility token
|
|
IssueCompatibilityToken {
|
|
/// User for which to issue the token
|
|
username: String,
|
|
|
|
/// Device ID to set in the token. If not specified, a random device ID
|
|
/// will be generated.
|
|
device_id: Option<String>,
|
|
|
|
/// Whether that token should be admin
|
|
#[arg(long = "yes-i-want-to-grant-synapse-admin-privileges")]
|
|
admin: bool,
|
|
},
|
|
|
|
/// Trigger a provisioning job for all users
|
|
ProvisionAllUsers,
|
|
}
|
|
|
|
impl Options {
|
|
#[allow(clippy::too_many_lines)]
|
|
pub async fn run(self, root: &super::Options) -> anyhow::Result<()> {
|
|
use Subcommand as SC;
|
|
let clock = SystemClock::default();
|
|
// XXX: we should disallow SeedableRng::from_entropy
|
|
let mut rng = rand_chacha::ChaChaRng::from_entropy();
|
|
|
|
match self.subcommand {
|
|
SC::SetPassword { username, password } => {
|
|
let _span =
|
|
info_span!("cli.manage.set_password", user.username = %username).entered();
|
|
|
|
let database_config: DatabaseConfig = root.load_config()?;
|
|
let passwords_config: PasswordsConfig = root.load_config()?;
|
|
|
|
let pool = database_from_config(&database_config).await?;
|
|
let password_manager = password_manager_from_config(&passwords_config).await?;
|
|
|
|
let mut repo = PgRepository::from_pool(&pool).await?.boxed();
|
|
let user = repo
|
|
.user()
|
|
.find_by_username(&username)
|
|
.await?
|
|
.context("User not found")?;
|
|
|
|
let password = password.into_bytes().into();
|
|
|
|
let (version, hashed_password) = password_manager.hash(&mut rng, password).await?;
|
|
|
|
repo.user_password()
|
|
.add(&mut rng, &clock, &user, version, hashed_password, None)
|
|
.await?;
|
|
|
|
info!(%user.id, %user.username, "Password changed");
|
|
repo.save().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
SC::VerifyEmail { username, email } => {
|
|
let _span = info_span!(
|
|
"cli.manage.verify_email",
|
|
user.username = username,
|
|
user_email.email = email
|
|
)
|
|
.entered();
|
|
|
|
let config: DatabaseConfig = root.load_config()?;
|
|
let pool = database_from_config(&config).await?;
|
|
let mut repo = PgRepository::from_pool(&pool).await?.boxed();
|
|
|
|
let user = repo
|
|
.user()
|
|
.find_by_username(&username)
|
|
.await?
|
|
.context("User not found")?;
|
|
|
|
let email = repo
|
|
.user_email()
|
|
.find(&user, &email)
|
|
.await?
|
|
.context("Email not found")?;
|
|
let email = repo.user_email().mark_as_verified(&clock, email).await?;
|
|
|
|
repo.save().await?;
|
|
info!(?email, "Email marked as verified");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
SC::IssueCompatibilityToken {
|
|
username,
|
|
admin,
|
|
device_id,
|
|
} => {
|
|
let config: DatabaseConfig = root.load_config()?;
|
|
let pool = database_from_config(&config).await?;
|
|
let mut repo = PgRepository::from_pool(&pool).await?.boxed();
|
|
|
|
let user = repo
|
|
.user()
|
|
.find_by_username(&username)
|
|
.await?
|
|
.context("User not found")?;
|
|
|
|
let device = if let Some(device_id) = device_id {
|
|
device_id.try_into()?
|
|
} else {
|
|
Device::generate(&mut rng)
|
|
};
|
|
|
|
let compat_session = repo
|
|
.compat_session()
|
|
.add(&mut rng, &clock, &user, device, admin)
|
|
.await?;
|
|
|
|
let token = TokenType::CompatAccessToken.generate(&mut rng);
|
|
|
|
let compat_access_token = repo
|
|
.compat_access_token()
|
|
.add(&mut rng, &clock, &compat_session, token, None)
|
|
.await?;
|
|
|
|
repo.save().await?;
|
|
|
|
info!(
|
|
%compat_access_token.id,
|
|
%compat_session.id,
|
|
%compat_session.device,
|
|
%user.id,
|
|
%user.username,
|
|
"Compatibility token issued: {}", compat_access_token.token
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
SC::ProvisionAllUsers => {
|
|
let _span = info_span!("cli.manage.provision_all_users").entered();
|
|
let config: DatabaseConfig = root.load_config()?;
|
|
let pool = database_from_config(&config).await?;
|
|
let mut conn = pool.acquire().await?;
|
|
let mut repo = PgRepository::from_pool(&pool).await?.boxed();
|
|
|
|
// TODO: do some pagination here
|
|
let ids: Vec<Uuid> = sqlx::query_scalar("SELECT user_id FROM users")
|
|
.fetch_all(&mut conn)
|
|
.await?;
|
|
drop(conn);
|
|
|
|
for id in ids {
|
|
let id = id.into();
|
|
info!(user.id = %id, "Scheduling provisioning job");
|
|
let job = ProvisionUserJob::new_for_id(id);
|
|
repo.job().schedule_job(job).await?;
|
|
}
|
|
|
|
repo.save().await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|