mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Implement a web-based account management dashboard
This commit is contained in:
@@ -5,35 +5,36 @@ use serde::{Deserialize, Deserializer, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct ClientMetadata {
|
||||
#[serde(default)]
|
||||
application_type: ApplicationType,
|
||||
pub application_type: ApplicationType,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
client_name: Option<String>,
|
||||
pub client_name: Option<String>,
|
||||
|
||||
client_uri: Url,
|
||||
pub client_uri: Url,
|
||||
|
||||
#[serde(default, deserialize_with = "btreeset_skip_err")]
|
||||
grant_types: BTreeSet<GrantType>,
|
||||
pub grant_types: BTreeSet<GrantType>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
logo_uri: Option<Url>,
|
||||
pub logo_uri: Option<Url>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
policy_uri: Option<Url>,
|
||||
pub policy_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
redirect_uris: Vec<Url>,
|
||||
pub redirect_uris: Vec<Url>,
|
||||
|
||||
#[serde(default, deserialize_with = "btreeset_skip_err")]
|
||||
response_types: BTreeSet<ResponseType>,
|
||||
pub response_types: BTreeSet<ResponseType>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
token_endpoint_auth_method: Option<String>,
|
||||
pub token_endpoint_auth_method: Option<String>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tos_uri: Option<Url>,
|
||||
pub tos_uri: Option<Url>,
|
||||
}
|
||||
|
||||
impl ClientMetadata {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use base64::Engine;
|
||||
use conduwuit::{Result, utils::hash::sha256};
|
||||
use database::{Deserialized, Json, Map};
|
||||
use ruma::{DeviceId, OwnedUserId, UserId};
|
||||
|
||||
use crate::{Dep, config, oauth::client_metadata::ClientMetadata};
|
||||
|
||||
@@ -11,6 +16,7 @@ pub mod client_metadata;
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
db: Data,
|
||||
tickets: Mutex<HashMap<String, HashMap<OAuthTicket, SystemTime>>>,
|
||||
}
|
||||
|
||||
struct Data {
|
||||
@@ -21,6 +27,22 @@ struct Services {
|
||||
config: Dep<config::Service>,
|
||||
}
|
||||
|
||||
/// A time-limited grant for a client to perform some sensitive action.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum OAuthTicket {
|
||||
CrossSigningReset,
|
||||
}
|
||||
|
||||
impl OAuthTicket {
|
||||
const MAX_AGE: Duration = Duration::from_mins(10);
|
||||
|
||||
pub fn ticket_issue_path(&self) -> &'static str {
|
||||
match self {
|
||||
| Self::CrossSigningReset => "/account/cross_signing_reset",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
@@ -30,6 +52,7 @@ impl crate::Service for Service {
|
||||
db: Data {
|
||||
clientid_clientmetadata: args.db["clientid_clientmetadata"].clone(),
|
||||
},
|
||||
tickets: Mutex::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -61,7 +84,7 @@ impl Service {
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
async fn get_client_registration(&self, client_id: &str) -> Option<ClientMetadata> {
|
||||
pub async fn get_client_registration(&self, client_id: &str) -> Option<ClientMetadata> {
|
||||
self.db
|
||||
.clientid_clientmetadata
|
||||
.get(client_id)
|
||||
@@ -69,4 +92,33 @@ impl Service {
|
||||
.deserialized()
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn get_client_id_for_device(&self, _device_id: &DeviceId) -> Option<String> {
|
||||
None // TODO
|
||||
}
|
||||
|
||||
/// Issue a ticket for `localpart` to perform some action.
|
||||
pub fn issue_ticket(&self, localpart: String, ticket: OAuthTicket) {
|
||||
self.tickets
|
||||
.lock()
|
||||
.expect("should be able to lock tickets")
|
||||
.entry(localpart)
|
||||
.or_default()
|
||||
.insert(ticket, SystemTime::now());
|
||||
}
|
||||
|
||||
/// Try to consume an unexpired ticket for `localpart`.
|
||||
pub fn try_consume_ticket(&self, localpart: &str, ticket: OAuthTicket) -> bool {
|
||||
let now = SystemTime::now();
|
||||
|
||||
self.tickets
|
||||
.lock()
|
||||
.expect("should be able to lock tickets")
|
||||
.get_mut(localpart)
|
||||
.and_then(|tickets| tickets.remove(&ticket))
|
||||
.is_some_and(|issued| {
|
||||
now.duration_since(issued)
|
||||
.is_ok_and(|duration| duration < OAuthTicket::MAX_AGE)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user