1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Simple list of compat sessions

This commit is contained in:
Quentin Gliech
2022-11-14 17:44:03 +01:00
parent 02c47cab34
commit 2064c11d9b
19 changed files with 771 additions and 98 deletions

View File

@ -10,7 +10,9 @@ async-graphql = { version = "4.0.16", features = ["chrono", "url"] }
chrono = "0.4.23"
serde = { version = "1.0.147", features = ["derive"] }
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "postgres"] }
thiserror = "1.0.37"
tokio = { version = "1.21.2", features = ["time"] }
tracing = "0.1.37"
ulid = "1.0.0"
url = "2.3.1"

View File

@ -22,8 +22,9 @@
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
use async_graphql::{Context, Description, EmptyMutation, EmptySubscription};
use async_graphql::{Context, Description, EmptyMutation, EmptySubscription, ID};
use mas_axum_utils::SessionInfo;
use model::NodeType;
use sqlx::PgPool;
use self::model::{BrowserSession, Node, User};
@ -78,4 +79,34 @@ impl RootQuery {
Ok(session.map(User::from))
}
/// Fetches an object given its ID.
async fn node(&self, ctx: &Context<'_>, id: ID) -> Result<Option<Node>, async_graphql::Error> {
let (node_type, id) = NodeType::from_id(&id)?;
let database = ctx.data::<PgPool>()?;
let session_info = ctx.data::<SessionInfo>()?;
let mut conn = database.acquire().await?;
let session = session_info.load_session(&mut conn).await?;
let Some(session) = session else { return Ok(None) };
match node_type {
// TODO
NodeType::Authentication
| NodeType::BrowserSession
| NodeType::CompatSession
| NodeType::CompatSsoLogin
| NodeType::OAuth2Client
| NodeType::UserEmail
| NodeType::OAuth2Session => Ok(None),
NodeType::User => {
if session.user.data == id {
Ok(Some(Box::new(User(session.user)).into()))
} else {
Ok(None)
}
}
}
}
}

View File

@ -16,7 +16,7 @@ use async_graphql::{Description, Object, ID};
use chrono::{DateTime, Utc};
use mas_storage::PostgresqlBackend;
use super::User;
use super::{NodeType, User};
/// A browser session represents a logged in user in a browser.
#[derive(Description)]
@ -32,7 +32,7 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession
impl BrowserSession {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::BrowserSession.id(self.0.data)
}
/// The user logged in this session.
@ -60,7 +60,7 @@ pub struct Authentication(pub mas_data_model::Authentication<PostgresqlBackend>)
impl Authentication {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::Authentication.id(self.0.data)
}
/// When the object was created.

View File

@ -18,7 +18,7 @@ use mas_data_model::CompatSsoLoginState;
use mas_storage::PostgresqlBackend;
use url::Url;
use super::User;
use super::{NodeType, User};
/// A compat session represents a client session which used the legacy Matrix
/// login API.
@ -29,7 +29,7 @@ pub struct CompatSession(pub mas_data_model::CompatSession<PostgresqlBackend>);
impl CompatSession {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::CompatSession.id(self.0.data)
}
/// The user authorized for this session.
@ -62,7 +62,7 @@ pub struct CompatSsoLogin(pub mas_data_model::CompatSsoLogin<PostgresqlBackend>)
impl CompatSsoLogin {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::CompatSsoLogin.id(self.0.data)
}
/// When the object was created.

View File

@ -16,14 +16,7 @@ use async_graphql::connection::OpaqueCursor;
use serde::{Deserialize, Serialize};
use ulid::Ulid;
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy)]
#[serde(rename = "snake_case")]
pub enum NodeType {
UserEmail,
BrowserSession,
CompatSsoLogin,
OAuth2Session,
}
pub use super::NodeType;
#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub struct NodeCursor(pub NodeType, pub Ulid);

View File

@ -12,37 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_graphql::{Interface, ID};
use async_graphql::Interface;
use chrono::{DateTime, Utc};
mod browser_sessions;
mod compat_sessions;
mod cursor;
mod node;
mod oauth;
mod users;
pub use self::{
browser_sessions::{Authentication, BrowserSession},
compat_sessions::{CompatSession, CompatSsoLogin},
cursor::{Cursor, NodeCursor, NodeType},
cursor::{Cursor, NodeCursor},
node::{Node, NodeType},
oauth::{OAuth2Client, OAuth2Consent, OAuth2Session},
users::{User, UserEmail},
};
/// An object with an ID.
#[derive(Interface)]
#[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))]
pub enum Node {
Authentication(Box<Authentication>),
BrowserSession(Box<BrowserSession>),
CompatSession(Box<CompatSession>),
CompatSsoLogin(Box<CompatSsoLogin>),
OAuth2Client(Box<OAuth2Client>),
OAuth2Session(Box<OAuth2Session>),
User(Box<User>),
UserEmail(Box<UserEmail>),
}
#[derive(Interface)]
#[graphql(field(
name = "created_at",

View File

@ -0,0 +1,107 @@
// Copyright 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 async_graphql::{Interface, ID};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use ulid::Ulid;
use super::{
Authentication, BrowserSession, CompatSession, CompatSsoLogin, OAuth2Client, OAuth2Session,
User, UserEmail,
};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NodeType {
Authentication,
BrowserSession,
CompatSession,
CompatSsoLogin,
OAuth2Client,
OAuth2Session,
User,
UserEmail,
}
#[derive(Debug, Error)]
#[error("invalid id")]
pub enum InvalidID {
InvalidFormat,
InvalidUlid(#[from] ulid::DecodeError),
UnknownPrefix,
}
impl NodeType {
fn to_prefix(self) -> &'static str {
match self {
NodeType::Authentication => "authentication",
NodeType::BrowserSession => "browser_session",
NodeType::CompatSession => "compat_session",
NodeType::CompatSsoLogin => "compat_sso_login",
NodeType::OAuth2Client => "oauth2_client",
NodeType::OAuth2Session => "oauth2_session",
NodeType::User => "user",
NodeType::UserEmail => "user_email",
}
}
fn from_prefix(prefix: &str) -> Option<Self> {
match prefix {
"authentication" => Some(NodeType::Authentication),
"browser_session" => Some(NodeType::BrowserSession),
"compat_session" => Some(NodeType::CompatSession),
"compat_sso_login" => Some(NodeType::CompatSsoLogin),
"oauth2_client" => Some(NodeType::OAuth2Client),
"oauth2_session" => Some(NodeType::OAuth2Session),
"user" => Some(NodeType::User),
"user_email" => Some(NodeType::UserEmail),
_ => None,
}
}
pub fn serialize(self, id: impl Into<Ulid>) -> String {
let prefix = self.to_prefix();
let id = id.into();
format!("{prefix}:{id}")
}
pub fn id(self, id: impl Into<Ulid>) -> ID {
ID(self.serialize(id))
}
pub fn deserialize(serialized: &str) -> Result<(Self, Ulid), InvalidID> {
let (prefix, id) = serialized.split_once(':').ok_or(InvalidID::InvalidFormat)?;
let prefix = NodeType::from_prefix(prefix).ok_or(InvalidID::UnknownPrefix)?;
let id = id.parse()?;
Ok((prefix, id))
}
pub fn from_id(id: &ID) -> Result<(Self, Ulid), InvalidID> {
Self::deserialize(&id.0)
}
}
/// An object with an ID.
#[derive(Interface)]
#[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))]
pub enum Node {
Authentication(Box<Authentication>),
BrowserSession(Box<BrowserSession>),
CompatSession(Box<CompatSession>),
CompatSsoLogin(Box<CompatSsoLogin>),
OAuth2Client(Box<OAuth2Client>),
OAuth2Session(Box<OAuth2Session>),
User(Box<User>),
UserEmail(Box<UserEmail>),
}

View File

@ -19,7 +19,7 @@ use sqlx::PgPool;
use ulid::Ulid;
use url::Url;
use super::{BrowserSession, User};
use super::{BrowserSession, NodeType, User};
/// An OAuth 2.0 session represents a client session which used the OAuth APIs
/// to login.
@ -30,7 +30,7 @@ pub struct OAuth2Session(pub mas_data_model::Session<PostgresqlBackend>);
impl OAuth2Session {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::OAuth2Session.id(self.0.data)
}
/// OAuth 2.0 client used by this session.
@ -62,7 +62,7 @@ pub struct OAuth2Client(pub mas_data_model::Client<PostgresqlBackend>);
impl OAuth2Client {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::OAuth2Client.id(self.0.data)
}
/// OAuth 2.0 client ID

View File

@ -44,7 +44,7 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for User {
impl User {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::User.id(self.0.data)
}
/// Username chosen by the user.
@ -79,10 +79,10 @@ impl User {
|after, before, first, last| async move {
let mut conn = database.acquire().await?;
let after_id = after
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::UserEmail))
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::CompatSsoLogin))
.transpose()?;
let before_id = before
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::UserEmail))
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::CompatSsoLogin))
.transpose()?;
let (has_previous_page, has_next_page, edges) =
@ -262,7 +262,7 @@ pub struct UserEmail(mas_data_model::UserEmail<PostgresqlBackend>);
impl UserEmail {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
NodeType::UserEmail.id(self.0.data)
}
/// Email address