Add personal access token and session storage
This commit is contained in:
@@ -111,6 +111,7 @@ mod utils;
|
||||
pub mod app_session;
|
||||
pub mod compat;
|
||||
pub mod oauth2;
|
||||
pub mod personal;
|
||||
pub mod policy_data;
|
||||
pub mod queue;
|
||||
pub mod upstream_oauth2;
|
||||
|
||||
119
crates/storage/src/personal/access_token.rs
Normal file
119
crates/storage/src/personal/access_token.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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 async_trait::async_trait;
|
||||
use chrono::Duration;
|
||||
use mas_data_model::{
|
||||
Clock,
|
||||
personal::{PersonalAccessToken, session::PersonalSession},
|
||||
};
|
||||
use rand_core::RngCore;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::repository_impl;
|
||||
|
||||
/// An [`PersonalAccessTokenRepository`] helps interacting with
|
||||
/// [`PersonalAccessToken`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait PersonalAccessTokenRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an access token by its ID
|
||||
///
|
||||
/// Returns the access token if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the access token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalAccessToken>, Self::Error>;
|
||||
|
||||
/// Find an access token by its token
|
||||
///
|
||||
/// Returns the access token if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `access_token`: The token of the access token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
access_token: &str,
|
||||
) -> Result<Option<PersonalAccessToken>, Self::Error>;
|
||||
|
||||
/// Add a new access token to the database
|
||||
///
|
||||
/// Returns the newly created access token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: A random number generator
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `session`: The session the access token is associated with
|
||||
/// * `access_token`: The access token to add
|
||||
/// * `expires_after`: The duration after which the access token expires. If
|
||||
/// [`None`] the access token never expires
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
clock: &dyn Clock,
|
||||
session: &PersonalSession,
|
||||
access_token: String,
|
||||
expires_after: Option<Duration>,
|
||||
) -> Result<PersonalAccessToken, Self::Error>;
|
||||
|
||||
/// Revoke an access token
|
||||
///
|
||||
/// Returns the revoked access token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `access_token`: The access token to revoke
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn revoke(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
access_token: PersonalAccessToken,
|
||||
) -> Result<PersonalAccessToken, Self::Error>;
|
||||
}
|
||||
|
||||
repository_impl!(PersonalAccessTokenRepository:
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalAccessToken>, Self::Error>;
|
||||
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
access_token: &str,
|
||||
) -> Result<Option<PersonalAccessToken>, Self::Error>;
|
||||
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
clock: &dyn Clock,
|
||||
session: &PersonalSession,
|
||||
access_token: String,
|
||||
expires_after: Option<Duration>,
|
||||
) -> Result<PersonalAccessToken, Self::Error>;
|
||||
|
||||
async fn revoke(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
access_token: PersonalAccessToken,
|
||||
) -> Result<PersonalAccessToken, Self::Error>;
|
||||
);
|
||||
13
crates/storage/src/personal/mod.rs
Normal file
13
crates/storage/src/personal/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
|
||||
//! Repositories to deal with Personal Sessions and Personal Access Tokens
|
||||
//! (PATs), which are sessions/access tokens created manually by users for use
|
||||
//! in scripts, bots and similar applications.
|
||||
|
||||
mod access_token;
|
||||
mod session;
|
||||
|
||||
pub use self::{access_token::PersonalAccessTokenRepository, session::PersonalSessionRepository};
|
||||
101
crates/storage/src/personal/session.rs
Normal file
101
crates/storage/src/personal/session.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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 async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_data_model::{Clock, Device, User, personal::session::PersonalSession};
|
||||
use oauth2_types::scope::Scope;
|
||||
use rand_core::RngCore;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::repository_impl;
|
||||
|
||||
/// A [`PersonalSessionRepository`] helps interacting with
|
||||
/// [`PersonalSession`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait PersonalSessionRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a Personal session by its ID
|
||||
///
|
||||
/// Returns the Personal session if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the Personal session to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
|
||||
|
||||
/// Start a new Personal session
|
||||
///
|
||||
/// Returns the newly created Personal session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `owner_user`: The user that will own the personal session
|
||||
/// * `actor_user`: The user that will be represented by the personal
|
||||
/// session
|
||||
/// * `device`: The device ID of this session
|
||||
/// * `human_name`: The human-readable name of the session provided by the
|
||||
/// client or the user
|
||||
/// * `scope`: The [`Scope`] of the [`PersonalSession`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
clock: &dyn Clock,
|
||||
owner_user: &User,
|
||||
actor_user: &User,
|
||||
human_name: String,
|
||||
scope: Scope,
|
||||
) -> Result<PersonalSession, Self::Error>;
|
||||
|
||||
/// End a Personal session
|
||||
///
|
||||
/// Returns the ended Personal session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `Personal_session`: The Personal session to end
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn revoke(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
personal_session: PersonalSession,
|
||||
) -> Result<PersonalSession, Self::Error>;
|
||||
}
|
||||
|
||||
repository_impl!(PersonalSessionRepository:
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
|
||||
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
clock: &dyn Clock,
|
||||
owner_user: &User,
|
||||
actor_user: &User,
|
||||
human_name: String,
|
||||
scope: Scope,
|
||||
) -> Result<PersonalSession, Self::Error>;
|
||||
|
||||
async fn revoke(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
personal_session: PersonalSession,
|
||||
) -> Result<PersonalSession, Self::Error>;
|
||||
);
|
||||
@@ -18,6 +18,7 @@ use crate::{
|
||||
OAuth2AccessTokenRepository, OAuth2AuthorizationGrantRepository, OAuth2ClientRepository,
|
||||
OAuth2DeviceCodeGrantRepository, OAuth2RefreshTokenRepository, OAuth2SessionRepository,
|
||||
},
|
||||
personal::{PersonalAccessTokenRepository, PersonalSessionRepository},
|
||||
policy_data::PolicyDataRepository,
|
||||
queue::{QueueJobRepository, QueueScheduleRepository, QueueWorkerRepository},
|
||||
upstream_oauth2::{
|
||||
@@ -214,6 +215,16 @@ pub trait RepositoryAccess: Send {
|
||||
&'c mut self,
|
||||
) -> Box<dyn CompatRefreshTokenRepository<Error = Self::Error> + 'c>;
|
||||
|
||||
/// Get a [`PersonalAccessTokenRepository`]
|
||||
fn personal_access_token<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn PersonalAccessTokenRepository<Error = Self::Error> + 'c>;
|
||||
|
||||
/// Get a [`PersonalSessionRepository`]
|
||||
fn personal_session<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn PersonalSessionRepository<Error = Self::Error> + 'c>;
|
||||
|
||||
/// Get a [`QueueWorkerRepository`]
|
||||
fn queue_worker<'c>(&'c mut self) -> Box<dyn QueueWorkerRepository<Error = Self::Error> + 'c>;
|
||||
|
||||
@@ -247,6 +258,7 @@ mod impls {
|
||||
OAuth2ClientRepository, OAuth2DeviceCodeGrantRepository, OAuth2RefreshTokenRepository,
|
||||
OAuth2SessionRepository,
|
||||
},
|
||||
personal::{PersonalAccessTokenRepository, PersonalSessionRepository},
|
||||
policy_data::PolicyDataRepository,
|
||||
queue::{QueueJobRepository, QueueScheduleRepository, QueueWorkerRepository},
|
||||
upstream_oauth2::{
|
||||
@@ -458,6 +470,21 @@ mod impls {
|
||||
))
|
||||
}
|
||||
|
||||
fn personal_access_token<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn PersonalAccessTokenRepository<Error = Self::Error> + 'c> {
|
||||
Box::new(MapErr::new(
|
||||
self.inner.personal_access_token(),
|
||||
&mut self.mapper,
|
||||
))
|
||||
}
|
||||
|
||||
fn personal_session<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn PersonalSessionRepository<Error = Self::Error> + 'c> {
|
||||
Box::new(MapErr::new(self.inner.personal_session(), &mut self.mapper))
|
||||
}
|
||||
|
||||
fn queue_worker<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn QueueWorkerRepository<Error = Self::Error> + 'c> {
|
||||
@@ -610,6 +637,18 @@ mod impls {
|
||||
(**self).compat_refresh_token()
|
||||
}
|
||||
|
||||
fn personal_access_token<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn PersonalAccessTokenRepository<Error = Self::Error> + 'c> {
|
||||
(**self).personal_access_token()
|
||||
}
|
||||
|
||||
fn personal_session<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn PersonalSessionRepository<Error = Self::Error> + 'c> {
|
||||
(**self).personal_session()
|
||||
}
|
||||
|
||||
fn queue_worker<'c>(
|
||||
&'c mut self,
|
||||
) -> Box<dyn QueueWorkerRepository<Error = Self::Error> + 'c> {
|
||||
|
||||
Reference in New Issue
Block a user