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

Basic current session/user query + user emails connection

This commit is contained in:
Quentin Gliech
2022-11-07 18:34:11 +01:00
parent ea1ecd5fc6
commit ac40367c5f
7 changed files with 453 additions and 10 deletions

View File

@@ -29,6 +29,10 @@ use mas_axum_utils::SessionInfo;
use sqlx::PgPool;
use tokio_stream::{Stream, StreamExt};
use self::model::{BrowserSession, User};
mod model;
pub type Schema = async_graphql::Schema<Query, Mutation, Subscription>;
pub type SchemaBuilder = async_graphql::SchemaBuilder<Query, Mutation, Subscription>;
@@ -51,14 +55,25 @@ impl Query {
#[async_graphql::Object]
impl Query {
/// A simple property which uses the DB pool and the current session
async fn username(&self, ctx: &Context<'_>) -> Result<Option<String>, async_graphql::Error> {
async fn current_session(
&self,
ctx: &Context<'_>,
) -> Result<Option<BrowserSession>, async_graphql::Error> {
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?;
Ok(session.map(|s| s.user.username))
Ok(session.map(BrowserSession::from))
}
async fn current_user(&self, ctx: &Context<'_>) -> Result<Option<User>, async_graphql::Error> {
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?;
Ok(session.map(User::from))
}
}

195
crates/graphql/src/model.rs Normal file
View File

@@ -0,0 +1,195 @@
// 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::{
connection::{query, Connection, Edge, OpaqueCursor},
Context, Object, ID,
};
use chrono::{DateTime, Utc};
use mas_storage::PostgresqlBackend;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use ulid::Ulid;
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy)]
#[serde(rename = "snake_case")]
enum NodeType {
User,
UserEmail,
BrowserSession,
}
#[derive(Serialize, Deserialize, PartialEq, Eq)]
struct NodeCursor(NodeType, Ulid);
impl NodeCursor {
fn extract_for_type(&self, node_type: NodeType) -> Result<Ulid, async_graphql::Error> {
if self.0 == node_type {
Ok(self.1)
} else {
Err(async_graphql::Error::new("invalid cursor"))
}
}
}
type Cursor = OpaqueCursor<NodeCursor>;
pub struct BrowserSession(mas_data_model::BrowserSession<PostgresqlBackend>);
impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession {
fn from(v: mas_data_model::BrowserSession<PostgresqlBackend>) -> Self {
Self(v)
}
}
#[Object]
impl BrowserSession {
async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
async fn user(&self) -> User {
User(self.0.user.clone())
}
async fn last_authentication(&self) -> Option<Authentication> {
self.0.last_authentication.clone().map(Authentication)
}
async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
}
pub struct User(mas_data_model::User<PostgresqlBackend>);
impl From<mas_data_model::User<PostgresqlBackend>> for User {
fn from(v: mas_data_model::User<PostgresqlBackend>) -> Self {
Self(v)
}
}
impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for User {
fn from(v: mas_data_model::BrowserSession<PostgresqlBackend>) -> Self {
Self(v.user)
}
}
#[Object]
impl User {
async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
async fn username(&self) -> &str {
&self.0.username
}
async fn primary_email(&self) -> Option<UserEmail> {
self.0.primary_email.clone().map(UserEmail)
}
async fn emails(
&self,
ctx: &Context<'_>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
) -> Result<Connection<Cursor, UserEmail, UserEmailsPagination>, async_graphql::Error> {
let database = ctx.data::<PgPool>()?;
query(
after,
before,
first,
last,
|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))
.transpose()?;
let before_id = before
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::UserEmail))
.transpose()?;
let (has_previous_page, has_next_page, edges) =
mas_storage::user::get_paginated_user_emails(
&mut conn, &self.0, before_id, after_id, first, last,
)
.await?;
let mut connection = Connection::with_additional_fields(
has_previous_page,
has_next_page,
UserEmailsPagination(self.0.clone()),
);
connection.edges.extend(edges.into_iter().map(|u| {
Edge::new(
OpaqueCursor(NodeCursor(NodeType::UserEmail, u.data)),
UserEmail(u),
)
}));
Ok::<_, async_graphql::Error>(connection)
},
)
.await
}
}
pub struct Authentication(mas_data_model::Authentication<PostgresqlBackend>);
#[Object]
impl Authentication {
async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
}
pub struct UserEmail(mas_data_model::UserEmail<PostgresqlBackend>);
#[Object]
impl UserEmail {
async fn id(&self) -> ID {
ID(self.0.data.to_string())
}
async fn email(&self) -> &str {
&self.0.email
}
async fn created_at(&self) -> DateTime<Utc> {
self.0.created_at
}
async fn confirmed_at(&self) -> Option<DateTime<Utc>> {
self.0.confirmed_at
}
}
pub struct UserEmailsPagination(mas_data_model::User<PostgresqlBackend>);
#[Object]
impl UserEmailsPagination {
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?;
Ok(count)
}
}