mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-08-28 03:04:12 +00:00
data model: Add personal sessions with mpt_ prefix
This commit is contained in:
@@ -11,6 +11,7 @@ use thiserror::Error;
|
||||
pub mod clock;
|
||||
pub(crate) mod compat;
|
||||
pub mod oauth2;
|
||||
pub mod personal;
|
||||
pub(crate) mod policy_data;
|
||||
mod site_config;
|
||||
pub(crate) mod tokens;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
pub mod session;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use ulid::Ulid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PersonalAccessToken {
|
||||
pub id: Ulid,
|
||||
pub session_id: Ulid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
pub revoked_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl PersonalAccessToken {
|
||||
#[must_use]
|
||||
pub fn is_valid(&self, now: DateTime<Utc>) -> bool {
|
||||
if self.revoked_at.is_some() {
|
||||
return false;
|
||||
}
|
||||
if let Some(expires_at) = self.expires_at {
|
||||
expires_at > now
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use oauth2_types::scope::Scope;
|
||||
use serde::Serialize;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::InvalidTransitionError;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
|
||||
pub enum SessionState {
|
||||
#[default]
|
||||
Valid,
|
||||
Revoked {
|
||||
revoked_at: DateTime<Utc>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SessionState {
|
||||
/// Returns `true` if the session state is [`Valid`].
|
||||
///
|
||||
/// [`Valid`]: SessionState::Valid
|
||||
#[must_use]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
matches!(self, Self::Valid)
|
||||
}
|
||||
|
||||
/// Returns `true` if the session state is [`Revoked`].
|
||||
///
|
||||
/// [`Revoked`]: SessionState::Revoked
|
||||
#[must_use]
|
||||
pub fn is_revoked(&self) -> bool {
|
||||
matches!(self, Self::Revoked { .. })
|
||||
}
|
||||
|
||||
/// Transitions the session state to [`Revoked`].
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `revoked_at` - The time at which the session was revoked.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the session state is already [`Revoked`].
|
||||
///
|
||||
/// [`Revoked`]: SessionState::Revoked
|
||||
pub fn revoke(self, revoked_at: DateTime<Utc>) -> Result<Self, InvalidTransitionError> {
|
||||
match self {
|
||||
Self::Valid => Ok(Self::Revoked { revoked_at }),
|
||||
Self::Revoked { .. } => Err(InvalidTransitionError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the time the session was revoked, if any
|
||||
///
|
||||
/// Returns `None` if the session is still [`Valid`].
|
||||
///
|
||||
/// [`Valid`]: SessionState::Valid
|
||||
#[must_use]
|
||||
pub fn revoked_at(&self) -> Option<DateTime<Utc>> {
|
||||
match self {
|
||||
Self::Valid => None,
|
||||
Self::Revoked { revoked_at } => Some(*revoked_at),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct PersonalSession {
|
||||
pub id: Ulid,
|
||||
pub state: SessionState,
|
||||
pub owner_user_id: Ulid,
|
||||
pub actor_user_id: Ulid,
|
||||
pub human_name: String,
|
||||
/// The scope for the session, identical to OAuth2 sessions.
|
||||
/// May or may not include a device scope
|
||||
/// (personal sessions can be deviceless).
|
||||
pub scope: Scope,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_active_at: Option<DateTime<Utc>>,
|
||||
pub last_active_ip: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for PersonalSession {
|
||||
type Target = SessionState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonalSession {
|
||||
/// Marks the session as revoked.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `revoked_at` - The time at which the session was finished.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the session is already finished.
|
||||
pub fn finish(mut self, revoked_at: DateTime<Utc>) -> Result<Self, InvalidTransitionError> {
|
||||
self.state = self.state.revoke(revoked_at)?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
@@ -240,6 +240,9 @@ pub enum TokenType {
|
||||
|
||||
/// A legacy refresh token
|
||||
CompatRefreshToken,
|
||||
|
||||
/// A personal access token.
|
||||
PersonalAccessToken,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TokenType {
|
||||
@@ -249,6 +252,7 @@ impl std::fmt::Display for TokenType {
|
||||
TokenType::RefreshToken => write!(f, "refresh token"),
|
||||
TokenType::CompatAccessToken => write!(f, "compat access token"),
|
||||
TokenType::CompatRefreshToken => write!(f, "compat refresh token"),
|
||||
TokenType::PersonalAccessToken => write!(f, "personal access token"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +264,7 @@ impl TokenType {
|
||||
TokenType::RefreshToken => "mar",
|
||||
TokenType::CompatAccessToken => "mct",
|
||||
TokenType::CompatRefreshToken => "mcr",
|
||||
TokenType::PersonalAccessToken => "mpt",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +274,7 @@ impl TokenType {
|
||||
"mar" => Some(TokenType::RefreshToken),
|
||||
"mct" | "syt" => Some(TokenType::CompatAccessToken),
|
||||
"mcr" | "syr" => Some(TokenType::CompatRefreshToken),
|
||||
"mpt" => Some(TokenType::PersonalAccessToken),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -335,7 +341,9 @@ impl PartialEq<OAuthTokenTypeHint> for TokenType {
|
||||
matches!(
|
||||
(self, other),
|
||||
(
|
||||
TokenType::AccessToken | TokenType::CompatAccessToken,
|
||||
TokenType::AccessToken
|
||||
| TokenType::CompatAccessToken
|
||||
| TokenType::PersonalAccessToken,
|
||||
OAuthTokenTypeHint::AccessToken
|
||||
) | (
|
||||
TokenType::RefreshToken | TokenType::CompatRefreshToken,
|
||||
|
||||
@@ -625,6 +625,11 @@ pub(crate) async fn post(
|
||||
device_id: session.device.map(Device::into),
|
||||
}
|
||||
}
|
||||
|
||||
TokenType::PersonalAccessToken => {
|
||||
// TODO
|
||||
return Err(RouteError::UnknownToken(TokenType::PersonalAccessToken));
|
||||
}
|
||||
};
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
Reference in New Issue
Block a user