1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-20 12:02:22 +03:00

GraphQL schema documentation

This commit is contained in:
Quentin Gliech
2022-11-09 13:39:25 +01:00
parent 4918440c3a
commit 4f01c123c3
8 changed files with 349 additions and 57 deletions

View File

@@ -22,36 +22,40 @@
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
use async_graphql::{Context, EmptyMutation, EmptySubscription};
use async_graphql::{Context, Description, EmptyMutation, EmptySubscription};
use mas_axum_utils::SessionInfo;
use model::CreationEvent;
use sqlx::PgPool;
use self::model::{BrowserSession, User};
use self::model::{BrowserSession, Node, User};
mod model;
pub type Schema = async_graphql::Schema<Query, EmptyMutation, EmptySubscription>;
pub type SchemaBuilder = async_graphql::SchemaBuilder<Query, EmptyMutation, EmptySubscription>;
pub type Schema = async_graphql::Schema<RootQuery, EmptyMutation, EmptySubscription>;
pub type SchemaBuilder = async_graphql::SchemaBuilder<RootQuery, EmptyMutation, EmptySubscription>;
#[must_use]
pub fn schema_builder() -> SchemaBuilder {
async_graphql::Schema::build(Query::new(), EmptyMutation, EmptySubscription)
async_graphql::Schema::build(RootQuery::new(), EmptyMutation, EmptySubscription)
.register_output_type::<Node>()
.register_output_type::<CreationEvent>()
}
#[derive(Default)]
pub struct Query {
/// The query root of the GraphQL interface.
#[derive(Default, Description)]
pub struct RootQuery {
_private: (),
}
impl Query {
impl RootQuery {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
#[async_graphql::Object]
impl Query {
#[async_graphql::Object(use_type_description)]
impl RootQuery {
/// Get the current logged in browser session
async fn current_browser_session(
&self,

View File

@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_graphql::{Object, ID};
use async_graphql::{Description, Object, ID};
use chrono::{DateTime, Utc};
use mas_storage::PostgresqlBackend;
use super::User;
/// A browser session represents a logged in user in a browser.
#[derive(Description)]
pub struct BrowserSession(pub mas_data_model::BrowserSession<PostgresqlBackend>);
impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession {
@@ -26,34 +28,43 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession
}
}
#[Object]
#[Object(use_type_description)]
impl BrowserSession {
async fn id(&self) -> ID {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
/// The user logged in this session.
async fn user(&self) -> User {
User(self.0.user.clone())
}
/// The most recent authentication of this session.
async fn last_authentication(&self) -> Option<Authentication> {
self.0.last_authentication.clone().map(Authentication)
}
async fn created_at(&self) -> DateTime<Utc> {
/// When the object was created.
pub async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
}
/// An authentication records when a user enter their credential in a browser
/// session.
#[derive(Description)]
pub struct Authentication(pub mas_data_model::Authentication<PostgresqlBackend>);
#[Object]
#[Object(use_type_description)]
impl Authentication {
async fn id(&self) -> ID {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
async fn created_at(&self) -> DateTime<Utc> {
/// When the object was created.
pub async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_graphql::{Object, ID};
use async_graphql::{Description, Object, ID};
use chrono::{DateTime, Utc};
use mas_data_model::CompatSsoLoginState;
use mas_storage::PostgresqlBackend;
@@ -20,47 +20,63 @@ use url::Url;
use super::User;
/// A compat session represents a client session which used the legacy Matrix
/// login API.
#[derive(Description)]
pub struct CompatSession(pub mas_data_model::CompatSession<PostgresqlBackend>);
#[Object]
#[Object(use_type_description)]
impl CompatSession {
async fn id(&self) -> ID {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
/// The user authorized for this session.
async fn user(&self) -> User {
User(self.0.user.clone())
}
/// The Matrix Device ID of this session.
async fn device_id(&self) -> &str {
self.0.device.as_str()
}
async fn created_at(&self) -> DateTime<Utc> {
/// When the object was created.
pub async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
async fn finished_at(&self) -> Option<DateTime<Utc>> {
/// When the session ended.
pub async fn finished_at(&self) -> Option<DateTime<Utc>> {
self.0.finished_at
}
}
/// A compat SSO login represents a login done through the legacy Matrix login
/// API, via the `m.login.sso` login method.
#[derive(Description)]
pub struct CompatSsoLogin(pub mas_data_model::CompatSsoLogin<PostgresqlBackend>);
#[Object]
#[Object(use_type_description)]
impl CompatSsoLogin {
async fn id(&self) -> ID {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
async fn created_at(&self) -> DateTime<Utc> {
/// When the object was created.
pub async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
/// The redirect URI used during the login.
async fn redirect_uri(&self) -> &Url {
&self.0.redirect_uri
}
/// When the login was fulfilled, and the user was redirected back to the
/// client.
async fn fulfilled_at(&self) -> Option<DateTime<Utc>> {
match &self.0.state {
CompatSsoLoginState::Pending => None,
@@ -69,6 +85,7 @@ impl CompatSsoLogin {
}
}
/// When the client exchanged the login token sent during the redirection.
async fn exchanged_at(&self) -> Option<DateTime<Utc>> {
match &self.0.state {
CompatSsoLoginState::Pending | CompatSsoLoginState::Fulfilled { .. } => None,
@@ -76,6 +93,7 @@ impl CompatSsoLogin {
}
}
/// The compat session which was started by this login.
async fn session(&self) -> Option<CompatSession> {
match &self.0.state {
CompatSsoLoginState::Pending => None,

View File

@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_graphql::{Interface, ID};
use chrono::{DateTime, Utc};
mod browser_sessions;
mod compat_sessions;
mod cursor;
@@ -20,7 +23,34 @@ mod users;
pub use self::{
browser_sessions::{Authentication, BrowserSession},
compat_sessions::{CompatSession, CompatSsoLogin},
cursor::{Cursor, NodeCursor, NodeType},
oauth::{OAuth2Client, OAuth2Consent, OAuth2Session},
users::{User, UserEmail},
};
#[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",
desc = "When the object was created.",
type = "DateTime<Utc>"
))]
pub enum CreationEvent {
Authentication(Box<Authentication>),
CompatSession(Box<CompatSession>),
BrowserSession(Box<BrowserSession>),
UserEmail(Box<UserEmail>),
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use async_graphql::{Context, Object, ID};
use async_graphql::{Context, Description, Object, ID};
use mas_storage::{oauth2::client::lookup_client, PostgresqlBackend};
use oauth2_types::scope::Scope;
use sqlx::PgPool;
@@ -21,75 +21,97 @@ use url::Url;
use super::{BrowserSession, User};
/// An OAuth 2.0 session represents a client session which used the OAuth APIs
/// to login.
#[derive(Description)]
pub struct OAuth2Session(pub mas_data_model::Session<PostgresqlBackend>);
#[Object]
#[Object(use_type_description)]
impl OAuth2Session {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
/// OAuth 2.0 client used by this session.
pub async fn client(&self) -> OAuth2Client {
OAuth2Client(self.0.client.clone())
}
/// Scope granted for this session.
pub async fn scope(&self) -> String {
self.0.scope.to_string()
}
/// The browser session which started this OAuth 2.0 session.
pub async fn browser_session(&self) -> BrowserSession {
BrowserSession(self.0.browser_session.clone())
}
/// User authorized for this session.
pub async fn user(&self) -> User {
User(self.0.browser_session.user.clone())
}
}
/// An OAuth 2.0 client
#[derive(Description)]
pub struct OAuth2Client(pub mas_data_model::Client<PostgresqlBackend>);
#[Object]
#[Object(use_type_description)]
impl OAuth2Client {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
/// OAuth 2.0 client ID
pub async fn client_id(&self) -> &str {
&self.0.client_id
}
/// Client name advertised by the client.
pub async fn client_name(&self) -> Option<&str> {
self.0.client_name.as_deref()
}
/// Client URI advertised by the client.
pub async fn client_uri(&self) -> Option<&Url> {
self.0.client_uri.as_ref()
}
/// Terms of services URI advertised by the client.
pub async fn tos_uri(&self) -> Option<&Url> {
self.0.tos_uri.as_ref()
}
/// Privacy policy URI advertised by the client.
pub async fn policy_uri(&self) -> Option<&Url> {
self.0.policy_uri.as_ref()
}
/// List of redirect URIs used for authorization grants by the client.
pub async fn redirect_uris(&self) -> &[Url] {
&self.0.redirect_uris
}
}
/// An OAuth 2.0 consent represents the scope a user consented to grant to a
/// client.
#[derive(Description)]
pub struct OAuth2Consent {
scope: Scope,
client_id: Ulid,
}
#[Object]
#[Object(use_type_description)]
impl OAuth2Consent {
/// Scope consented by the user for this client.
pub async fn scope(&self) -> String {
self.scope.to_string()
}
/// OAuth 2.0 client for which the user granted access.
pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
let mut conn = ctx.data::<PgPool>()?.acquire().await?;
let client = lookup_client(&mut conn, self.client_id).await?;

View File

@@ -14,7 +14,7 @@
use async_graphql::{
connection::{query, Connection, Edge, OpaqueCursor},
Context, Object, ID,
Context, Description, Object, ID,
};
use chrono::{DateTime, Utc};
use mas_storage::PostgresqlBackend;
@@ -24,6 +24,8 @@ use super::{
compat_sessions::CompatSsoLogin, BrowserSession, Cursor, NodeCursor, NodeType, OAuth2Session,
};
#[derive(Description)]
/// A user is an individual's account.
pub struct User(pub mas_data_model::User<PostgresqlBackend>);
impl From<mas_data_model::User<PostgresqlBackend>> for User {
@@ -38,27 +40,34 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for User {
}
}
#[Object]
#[Object(use_type_description)]
impl User {
async fn id(&self) -> ID {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
/// Username chosen by the user.
async fn username(&self) -> &str {
&self.0.username
}
/// Primary email address of the user.
async fn primary_email(&self) -> Option<UserEmail> {
self.0.primary_email.clone().map(UserEmail)
}
/// Get the list of compatibility SSO logins, chronologically sorted
async fn compat_sso_logins(
&self,
ctx: &Context<'_>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
) -> Result<Connection<Cursor, CompatSsoLogin>, async_graphql::Error> {
let database = ctx.data::<PgPool>()?;
@@ -96,13 +105,17 @@ impl User {
.await
}
/// Get the list of active browser sessions, chronologically sorted
async fn browser_sessions(
&self,
ctx: &Context<'_>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
) -> Result<Connection<Cursor, BrowserSession>, async_graphql::Error> {
let database = ctx.data::<PgPool>()?;
@@ -140,13 +153,17 @@ impl User {
.await
}
/// Get the list of emails, chronologically sorted
async fn emails(
&self,
ctx: &Context<'_>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
) -> Result<Connection<Cursor, UserEmail, UserEmailsPagination>, async_graphql::Error> {
let database = ctx.data::<PgPool>()?;
@@ -188,13 +205,17 @@ impl User {
.await
}
/// Get the list of OAuth 2.0 sessions, chronologically sorted
async fn oauth2_sessions(
&self,
ctx: &Context<'_>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
) -> Result<Connection<Cursor, OAuth2Session>, async_graphql::Error> {
let database = ctx.data::<PgPool>()?;
@@ -233,22 +254,29 @@ impl User {
}
}
/// A user email address
#[derive(Description)]
pub struct UserEmail(mas_data_model::UserEmail<PostgresqlBackend>);
#[Object]
#[Object(use_type_description)]
impl UserEmail {
async fn id(&self) -> ID {
/// ID of the object.
pub async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
/// Email address
async fn email(&self) -> &str {
&self.0.email
}
async fn created_at(&self) -> DateTime<Utc> {
/// When the object was created.
pub async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
/// When the email address was confirmed. Is `null` if the email was never
/// verified by the user.
async fn confirmed_at(&self) -> Option<DateTime<Utc>> {
self.0.confirmed_at
}
@@ -258,6 +286,7 @@ pub struct UserEmailsPagination(mas_data_model::User<PostgresqlBackend>);
#[Object]
impl UserEmailsPagination {
/// Identifies the total count of items in the connection.
async fn total_count(&self, ctx: &Context<'_>) -> Result<i64, async_graphql::Error> {
let mut conn = ctx.data::<PgPool>()?.acquire().await?;
let count = mas_storage::user::count_user_emails(&mut conn, &self.0).await?;