1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-06 06:02:40 +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

2
Cargo.lock generated
View File

@@ -2605,7 +2605,9 @@ dependencies = [
"oauth2-types", "oauth2-types",
"serde", "serde",
"sqlx", "sqlx",
"thiserror",
"tokio", "tokio",
"tracing",
"ulid", "ulid",
"url", "url",
] ]

View File

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

View File

@@ -22,8 +22,9 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)] #![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 mas_axum_utils::SessionInfo;
use model::NodeType;
use sqlx::PgPool; use sqlx::PgPool;
use self::model::{BrowserSession, Node, User}; use self::model::{BrowserSession, Node, User};
@@ -78,4 +79,34 @@ impl RootQuery {
Ok(session.map(User::from)) 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 chrono::{DateTime, Utc};
use mas_storage::PostgresqlBackend; use mas_storage::PostgresqlBackend;
use super::User; use super::{NodeType, User};
/// A browser session represents a logged in user in a browser. /// A browser session represents a logged in user in a browser.
#[derive(Description)] #[derive(Description)]
@@ -32,7 +32,7 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession
impl BrowserSession { impl BrowserSession {
/// ID of the object. /// ID of the object.
pub async fn id(&self) -> ID { pub async fn id(&self) -> ID {
ID(self.0.data.to_string()) NodeType::BrowserSession.id(self.0.data)
} }
/// The user logged in this session. /// The user logged in this session.
@@ -60,7 +60,7 @@ pub struct Authentication(pub mas_data_model::Authentication<PostgresqlBackend>)
impl Authentication { impl Authentication {
/// ID of the object. /// ID of the object.
pub async fn id(&self) -> ID { pub async fn id(&self) -> ID {
ID(self.0.data.to_string()) NodeType::Authentication.id(self.0.data)
} }
/// When the object was created. /// When the object was created.

View File

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

View File

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

View File

@@ -12,37 +12,25 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use async_graphql::{Interface, ID}; use async_graphql::Interface;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
mod browser_sessions; mod browser_sessions;
mod compat_sessions; mod compat_sessions;
mod cursor; mod cursor;
mod node;
mod oauth; mod oauth;
mod users; mod users;
pub use self::{ pub use self::{
browser_sessions::{Authentication, BrowserSession}, browser_sessions::{Authentication, BrowserSession},
compat_sessions::{CompatSession, CompatSsoLogin}, compat_sessions::{CompatSession, CompatSsoLogin},
cursor::{Cursor, NodeCursor, NodeType}, cursor::{Cursor, NodeCursor},
node::{Node, NodeType},
oauth::{OAuth2Client, OAuth2Consent, OAuth2Session}, oauth::{OAuth2Client, OAuth2Consent, OAuth2Session},
users::{User, UserEmail}, 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)] #[derive(Interface)]
#[graphql(field( #[graphql(field(
name = "created_at", 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 ulid::Ulid;
use url::Url; 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 /// An OAuth 2.0 session represents a client session which used the OAuth APIs
/// to login. /// to login.
@@ -30,7 +30,7 @@ pub struct OAuth2Session(pub mas_data_model::Session<PostgresqlBackend>);
impl OAuth2Session { impl OAuth2Session {
/// ID of the object. /// ID of the object.
pub async fn id(&self) -> ID { 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. /// OAuth 2.0 client used by this session.
@@ -62,7 +62,7 @@ pub struct OAuth2Client(pub mas_data_model::Client<PostgresqlBackend>);
impl OAuth2Client { impl OAuth2Client {
/// ID of the object. /// ID of the object.
pub async fn id(&self) -> ID { pub async fn id(&self) -> ID {
ID(self.0.data.to_string()) NodeType::OAuth2Client.id(self.0.data)
} }
/// OAuth 2.0 client ID /// OAuth 2.0 client ID

View File

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

View File

@@ -88,10 +88,8 @@ module.exports = {
rules: { rules: {
"@graphql-eslint/known-fragment-names": "off", "@graphql-eslint/known-fragment-names": "off",
"@graphql-eslint/no-unused-fragments": "off", "@graphql-eslint/no-unused-fragments": "off",
"@graphql-eslint/known-directives": [ "@graphql-eslint/unused-arguments": "off",
"error", "@graphql-eslint/known-directives": "off",
{ ignoreClientDirectives: ["connection", "refetchable"] },
],
// This rule is copied from the 'operations-recommended' config, // This rule is copied from the 'operations-recommended' config,
// but without the 'Query' forbidden suffix on operations, // but without the 'Query' forbidden suffix on operations,
// since it directly clashes with the relay operation naming convention // since it directly clashes with the relay operation naming convention

View File

@@ -293,6 +293,10 @@ type RootQuery {
Get the current logged in user Get the current logged in user
""" """
currentUser: User currentUser: User
"""
Fetches an object given its ID.
"""
node(id: ID!): Node
} }
""" """

View File

@@ -25,12 +25,53 @@ const CompatSsoLogin: React.FC<Props> = ({ login }) => {
fragment CompatSsoLogin_login on CompatSsoLogin { fragment CompatSsoLogin_login on CompatSsoLogin {
id id
redirectUri redirectUri
createdAt
session {
id
createdAt
deviceId
finishedAt
}
} }
`, `,
login login
); );
return <>{data.redirectUri}</>; let info = null;
if (data.session) {
info = (
<>
<div>
Started:{" "}
<span className="font-mono text-sm">{data.session.createdAt}</span>
</div>
{data.session.finishedAt ? (
<div className="text-alert">
Finished:{" "}
<span className="font-mono text-sm">{data.session.createdAt}</span>
</div>
) : null}
<div>
Device ID:{" "}
<span className="font-mono text-sm font-semibold">
{data.session.deviceId}
</span>
</div>
</>
);
}
return (
<div className="p-2 my-1 bg-grey-50 dark:bg-grey-450 rounded">
<div>
Requested: <span className="font-mono text-sm">{data.createdAt}</span>
</div>
{info}
<div>
Redirect URI: <span className="font-semibold">{data.redirectUri}</span>
</div>
</div>
);
}; };
export default CompatSsoLogin; export default CompatSsoLogin;

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, useFragment } from "react-relay"; import { graphql, usePaginationFragment } from "react-relay";
import CompatSsoLogin from "./CompatSsoLogin"; import CompatSsoLogin from "./CompatSsoLogin";
import { CompatSsoLoginList_user$key } from "./__generated__/CompatSsoLoginList_user.graphql"; import { CompatSsoLoginList_user$key } from "./__generated__/CompatSsoLoginList_user.graphql";
@@ -21,12 +21,15 @@ type Props = {
}; };
const CompatSsoLoginList: React.FC<Props> = ({ user }) => { const CompatSsoLoginList: React.FC<Props> = ({ user }) => {
const data = useFragment( const { data, loadNext, hasNext } = usePaginationFragment(
graphql` graphql`
fragment CompatSsoLoginList_user on User { fragment CompatSsoLoginList_user on User
compatSsoLogins(first: 10) { @refetchable(queryName: "CompatSsoLoginListQuery") {
compatSsoLogins(first: $count, after: $cursor)
@connection(key: "CompatSsoLoginList_user_compatSsoLogins") {
edges { edges {
node { node {
id
...CompatSsoLogin_login ...CompatSsoLogin_login
} }
} }
@@ -37,10 +40,16 @@ const CompatSsoLoginList: React.FC<Props> = ({ user }) => {
); );
return ( return (
<div> <div className="w-96">
<h2 className="text-lg">List of compatibility sessions:</h2>
{data.compatSsoLogins.edges.map((n) => ( {data.compatSsoLogins.edges.map((n) => (
<CompatSsoLogin login={n.node} /> <CompatSsoLogin login={n.node} key={n.node.id} />
))} ))}
{hasNext ? (
<button className="bg-accent p-2 rounded" onClick={() => loadNext(2)}>
Load more
</button>
) : null}
</div> </div>
); );
}; };

View File

@@ -0,0 +1,265 @@
/**
* @generated SignedSource<<3e111db0c30af3251a88326deea5562c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompatSsoLoginListQuery$variables = {
count?: number | null;
cursor?: string | null;
id: string;
};
export type CompatSsoLoginListQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">;
} | null;
};
export type CompatSsoLoginListQuery = {
response: CompatSsoLoginListQuery$data;
variables: CompatSsoLoginListQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompatSsoLoginListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "CompatSsoLoginList_user"
}
],
"storageKey": null
}
],
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompatSsoLoginListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": "CompatSsoLoginConnection",
"kind": "LinkedField",
"name": "compatSsoLogins",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLoginEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLogin",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUri",
"storageKey": null
},
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "CompatSession",
"kind": "LinkedField",
"name": "session",
"plural": false,
"selections": [
(v3/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deviceId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "finishedAt",
"storageKey": null
}
],
"storageKey": null
},
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": (v4/*: any*/),
"filters": null,
"handle": "connection",
"key": "CompatSsoLoginList_user_compatSsoLogins",
"kind": "LinkedHandle",
"name": "compatSsoLogins"
}
],
"type": "User",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "45c6342d3a53a3e3b2b18617796cf654",
"id": null,
"metadata": {},
"name": "CompatSsoLoginListQuery",
"operationKind": "query",
"text": "query CompatSsoLoginListQuery(\n $count: Int\n $cursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompatSsoLoginList_user\n id\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n}\n"
}
};
})();
(node as any).hash = "cafc795d1bf9643ac6155c017e66c858";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<d3404a632e1928901a9a8ec12357528d>> * @generated SignedSource<<4ace8ea8668e3dc638df21400c690bd8>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -8,16 +8,18 @@
/* eslint-disable */ /* eslint-disable */
// @ts-nocheck // @ts-nocheck
import { Fragment, ReaderFragment } from 'relay-runtime'; import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type CompatSsoLoginList_user$data = { export type CompatSsoLoginList_user$data = {
readonly compatSsoLogins: { readonly compatSsoLogins: {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">; readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">;
}; };
}>; }>;
}; };
readonly id: string;
readonly " $fragmentType": "CompatSsoLoginList_user"; readonly " $fragmentType": "CompatSsoLoginList_user";
}; };
export type CompatSsoLoginList_user$key = { export type CompatSsoLoginList_user$key = {
@@ -25,24 +27,64 @@ export type CompatSsoLoginList_user$key = {
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">; readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">;
}; };
const node: ReaderFragment = { import CompatSsoLoginListQuery_graphql from './CompatSsoLoginListQuery.graphql';
"argumentDefinitions": [],
const node: ReaderFragment = (function(){
var v0 = [
"compatSsoLogins"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "cursor"
}
],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": {
"connection": [
{
"count": "count",
"cursor": "cursor",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "cursor"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": CompatSsoLoginListQuery_graphql,
"identifierField": "id"
}
},
"name": "CompatSsoLoginList_user", "name": "CompatSsoLoginList_user",
"selections": [ "selections": [
{ {
"alias": null, "alias": "compatSsoLogins",
"args": [ "args": null,
{
"kind": "Literal",
"name": "first",
"value": 10
}
],
"concreteType": "CompatSsoLoginConnection", "concreteType": "CompatSsoLoginConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "compatSsoLogins", "name": "__CompatSsoLoginList_user_compatSsoLogins_connection",
"plural": false, "plural": false,
"selections": [ "selections": [
{ {
@@ -61,25 +103,67 @@ const node: ReaderFragment = {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v1/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
"name": "CompatSsoLogin_login" "name": "CompatSsoLogin_login"
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
} }
], ],
"storageKey": "compatSsoLogins(first:10)" "storageKey": null
} },
(v1/*: any*/)
], ],
"type": "User", "type": "User",
"abstractKey": null "abstractKey": null
}; };
})();
(node as any).hash = "b70f4b63784afe8f1f69c78198194cc9"; (node as any).hash = "cafc795d1bf9643ac6155c017e66c858";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<bafb31541b97839f32ff9790773ff904>> * @generated SignedSource<<20e2b233e5154ea60632046abc2aa29a>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -11,8 +11,15 @@
import { Fragment, ReaderFragment } from 'relay-runtime'; import { Fragment, ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type CompatSsoLogin_login$data = { export type CompatSsoLogin_login$data = {
readonly createdAt: any;
readonly id: string; readonly id: string;
readonly redirectUri: any; readonly redirectUri: any;
readonly session: {
readonly createdAt: any;
readonly deviceId: string;
readonly finishedAt: any | null;
readonly id: string;
} | null;
readonly " $fragmentType": "CompatSsoLogin_login"; readonly " $fragmentType": "CompatSsoLogin_login";
}; };
export type CompatSsoLogin_login$key = { export type CompatSsoLogin_login$key = {
@@ -20,31 +27,69 @@ export type CompatSsoLogin_login$key = {
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">; readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">;
}; };
const node: ReaderFragment = { const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"argumentDefinitions": [], "argumentDefinitions": [],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "CompatSsoLogin_login", "name": "CompatSsoLogin_login",
"selections": [ "selections": [
{ (v0/*: any*/),
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "redirectUri", "name": "redirectUri",
"storageKey": null "storageKey": null
},
(v1/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "CompatSession",
"kind": "LinkedField",
"name": "session",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deviceId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "finishedAt",
"storageKey": null
}
],
"storageKey": null
} }
], ],
"type": "CompatSsoLogin", "type": "CompatSsoLogin",
"abstractKey": null "abstractKey": null
}; };
})();
(node as any).hash = "e1151a93f1ba4a56332b8aa6129f7bfe"; (node as any).hash = "7be3b416b1023cea0de7a87b9738e5d5";
export default node; export default node;

View File

@@ -20,7 +20,7 @@ import type { HomeQuery } from "./__generated__/HomeQuery.graphql";
const Home: React.FC = () => { const Home: React.FC = () => {
const data = useLazyLoadQuery<HomeQuery>( const data = useLazyLoadQuery<HomeQuery>(
graphql` graphql`
query HomeQuery { query HomeQuery($count: Int!, $cursor: String) {
currentUser { currentUser {
id id
username username
@@ -29,7 +29,7 @@ const Home: React.FC = () => {
} }
} }
`, `,
{} { count: 2 }
); );
if (data.currentUser) { if (data.currentUser) {

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<94462aca9c48eae79c22a11003d865c3>> * @generated SignedSource<<874ab84c1095ab907f12b91b55f4bf2c>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -10,7 +10,10 @@
import { ConcreteRequest, Query } from 'relay-runtime'; import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type HomeQuery$variables = {}; export type HomeQuery$variables = {
count: number;
cursor?: string | null;
};
export type HomeQuery$data = { export type HomeQuery$data = {
readonly currentUser: { readonly currentUser: {
readonly id: string; readonly id: string;
@@ -24,23 +27,54 @@ export type HomeQuery = {
}; };
const node: ConcreteRequest = (function(){ const node: ConcreteRequest = (function(){
var v0 = { var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
}
],
v1 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v1 = { v2 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "username", "name": "username",
"storageKey": null "storageKey": null
},
v3 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
}; };
return { return {
"fragment": { "fragment": {
"argumentDefinitions": [], "argumentDefinitions": (v0/*: any*/),
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "HomeQuery", "name": "HomeQuery",
@@ -53,8 +87,8 @@ return {
"name": "currentUser", "name": "currentUser",
"plural": false, "plural": false,
"selections": [ "selections": [
(v0/*: any*/),
(v1/*: any*/), (v1/*: any*/),
(v2/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -69,7 +103,7 @@ return {
}, },
"kind": "Request", "kind": "Request",
"operation": { "operation": {
"argumentDefinitions": [], "argumentDefinitions": (v0/*: any*/),
"kind": "Operation", "kind": "Operation",
"name": "HomeQuery", "name": "HomeQuery",
"selections": [ "selections": [
@@ -81,17 +115,11 @@ return {
"name": "currentUser", "name": "currentUser",
"plural": false, "plural": false,
"selections": [ "selections": [
(v0/*: any*/),
(v1/*: any*/), (v1/*: any*/),
(v2/*: any*/),
{ {
"alias": null, "alias": null,
"args": [ "args": (v3/*: any*/),
{
"kind": "Literal",
"name": "first",
"value": 10
}
],
"concreteType": "CompatSsoLoginConnection", "concreteType": "CompatSsoLoginConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "compatSsoLogins", "name": "compatSsoLogins",
@@ -113,22 +141,98 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v0/*: any*/), (v1/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "redirectUri", "name": "redirectUri",
"storageKey": null "storageKey": null
},
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "CompatSession",
"kind": "LinkedField",
"name": "session",
"plural": false,
"selections": [
(v1/*: any*/),
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deviceId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "finishedAt",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
} }
], ],
"storageKey": "compatSsoLogins(first:10)" "storageKey": null
},
{
"alias": null,
"args": (v3/*: any*/),
"filters": null,
"handle": "connection",
"key": "CompatSsoLoginList_user_compatSsoLogins",
"kind": "LinkedHandle",
"name": "compatSsoLogins"
} }
], ],
"storageKey": null "storageKey": null
@@ -136,16 +240,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "1b4d3469bb6ccc19b8944b1f7fedd9c5", "cacheID": "3543c7ada63831383f66dbeed4b1648e",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "HomeQuery", "name": "HomeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query HomeQuery {\n currentUser {\n id\n username\n ...CompatSsoLoginList_user\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: 10) {\n edges {\n node {\n ...CompatSsoLogin_login\n id\n }\n }\n }\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n}\n" "text": "query HomeQuery(\n $count: Int!\n $cursor: String\n) {\n currentUser {\n id\n username\n ...CompatSsoLoginList_user\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "a5defd2dc81b62abebfe7a393573d5a8"; (node as any).hash = "1cd5ce8ae5a912b3d7ecf0c6e3cd8469";
export default node; export default node;