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

Lint GraphQL schema and operations with eslint

This commit is contained in:
Quentin Gliech
2022-11-10 14:22:23 +01:00
parent 815afb4535
commit 47c9b54c06
12 changed files with 3166 additions and 1028 deletions

View File

@ -52,9 +52,35 @@ jobs:
files: crates/policy/policies/coverage.json
flags: policies
frontend-lint:
name: Check frontend style
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install Node dependencies
working-directory: ./frontend
run: npm ci
- name: Lint
working-directory: ./frontend
run: npm run lint
rustfmt:
name: Check style
name: Check Rust style
runs-on: ubuntu-latest
permissions:
@ -85,7 +111,7 @@ jobs:
- name: Checkout the code
uses: actions/checkout@v3
- name: Install toolchain
- name: Install Rust toolchain
run: |
rustup toolchain install stable
rustup default stable
@ -93,8 +119,19 @@ jobs:
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install Node dependencies
working-directory: ./frontend
run: npm ci
- name: Update the schemas
run: sh ./misc/update-schemas.sh
run: sh ./misc/update.sh
- name: Check that the workspace is clean
run: |
@ -396,6 +433,7 @@ jobs:
if: ${{ always() }}
needs:
- opa-lint
- frontend-lint
- rustfmt
- clippy
- check-schema

View File

@ -1,398 +0,0 @@
"""
An authentication records when a user enter their credential in a browser
session.
"""
type Authentication implements Node {
"""
ID of the object.
"""
id: ID!
"""
When the object was created.
"""
createdAt: DateTime!
}
"""
A browser session represents a logged in user in a browser.
"""
type BrowserSession implements Node {
"""
ID of the object.
"""
id: ID!
"""
The user logged in this session.
"""
user: User!
"""
The most recent authentication of this session.
"""
lastAuthentication: Authentication
"""
When the object was created.
"""
createdAt: DateTime!
}
type BrowserSessionConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [BrowserSessionEdge!]!
"""
A list of nodes.
"""
nodes: [BrowserSession!]!
}
"""
An edge in a connection.
"""
type BrowserSessionEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: BrowserSession!
}
"""
A compat session represents a client session which used the legacy Matrix
login API.
"""
type CompatSession implements Node {
"""
ID of the object.
"""
id: ID!
"""
The user authorized for this session.
"""
user: User!
"""
The Matrix Device ID of this session.
"""
deviceId: String!
"""
When the object was created.
"""
createdAt: DateTime!
"""
When the session ended.
"""
finishedAt: DateTime
}
"""
A compat SSO login represents a login done through the legacy Matrix login
API, via the `m.login.sso` login method.
"""
type CompatSsoLogin implements Node {
"""
ID of the object.
"""
id: ID!
"""
When the object was created.
"""
createdAt: DateTime!
"""
The redirect URI used during the login.
"""
redirectUri: Url!
"""
When the login was fulfilled, and the user was redirected back to the
client.
"""
fulfilledAt: DateTime
"""
When the client exchanged the login token sent during the redirection.
"""
exchangedAt: DateTime
"""
The compat session which was started by this login.
"""
session: CompatSession
}
type CompatSsoLoginConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [CompatSsoLoginEdge!]!
"""
A list of nodes.
"""
nodes: [CompatSsoLogin!]!
}
"""
An edge in a connection.
"""
type CompatSsoLoginEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: CompatSsoLogin!
}
"""
Implement the DateTime<Utc> scalar
The input/output is a string in RFC3339 format.
"""
scalar DateTime
interface Node {
"""
ID of the object.
"""
id: ID!
}
"""
An OAuth 2.0 client
"""
type Oauth2Client implements Node {
"""
ID of the object.
"""
id: ID!
"""
OAuth 2.0 client ID
"""
clientId: String!
"""
Client name advertised by the client.
"""
clientName: String
"""
Client URI advertised by the client.
"""
clientUri: Url
"""
Terms of services URI advertised by the client.
"""
tosUri: Url
"""
Privacy policy URI advertised by the client.
"""
policyUri: Url
"""
List of redirect URIs used for authorization grants by the client.
"""
redirectUris: [Url!]!
}
"""
An OAuth 2.0 session represents a client session which used the OAuth APIs
to login.
"""
type Oauth2Session implements Node {
"""
ID of the object.
"""
id: ID!
"""
OAuth 2.0 client used by this session.
"""
client: Oauth2Client!
"""
Scope granted for this session.
"""
scope: String!
"""
The browser session which started this OAuth 2.0 session.
"""
browserSession: BrowserSession!
"""
User authorized for this session.
"""
user: User!
}
type Oauth2SessionConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [Oauth2SessionEdge!]!
"""
A list of nodes.
"""
nodes: [Oauth2Session!]!
}
"""
An edge in a connection.
"""
type Oauth2SessionEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: Oauth2Session!
}
"""
Information about pagination in a connection
"""
type PageInfo {
"""
When paginating backwards, are there more items?
"""
hasPreviousPage: Boolean!
"""
When paginating forwards, are there more items?
"""
hasNextPage: Boolean!
"""
When paginating backwards, the cursor to continue.
"""
startCursor: String
"""
When paginating forwards, the cursor to continue.
"""
endCursor: String
}
"""
The query root of the GraphQL interface.
"""
type RootQuery {
"""
Get the current logged in browser session
"""
currentBrowserSession: BrowserSession
"""
Get the current logged in user
"""
currentUser: User
}
"""
URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/)
"""
scalar Url
"""
A user is an individual's account.
"""
type User implements Node {
"""
ID of the object.
"""
id: ID!
"""
Username chosen by the user.
"""
username: String!
"""
Primary email address of the user.
"""
primaryEmail: UserEmail
"""
Get the list of compatibility SSO logins, chronologically sorted
"""
compatSsoLogins(after: String, before: String, first: Int, last: Int): CompatSsoLoginConnection!
"""
Get the list of active browser sessions, chronologically sorted
"""
browserSessions(after: String, before: String, first: Int, last: Int): BrowserSessionConnection!
"""
Get the list of emails, chronologically sorted
"""
emails(after: String, before: String, first: Int, last: Int): UserEmailConnection!
"""
Get the list of OAuth 2.0 sessions, chronologically sorted
"""
oauth2Sessions(after: String, before: String, first: Int, last: Int): Oauth2SessionConnection!
}
"""
A user email address
"""
type UserEmail implements Node {
"""
ID of the object.
"""
id: ID!
"""
Email address
"""
email: String!
"""
When the object was created.
"""
createdAt: DateTime!
"""
When the email address was confirmed. Is `null` if the email was never
verified by the user.
"""
confirmedAt: DateTime
}
type UserEmailConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [UserEmailEdge!]!
"""
A list of nodes.
"""
nodes: [UserEmail!]!
"""
Identifies the total count of items in the connection.
"""
totalCount: Int!
}
"""
An edge in a connection.
"""
type UserEmailEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: UserEmail!
}
schema {
query: RootQuery
}

View File

@ -29,6 +29,7 @@ pub use self::{
users::{User, UserEmail},
};
/// An object with an ID.
#[derive(Interface)]
#[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))]
pub enum Node {

View File

@ -15,6 +15,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
ignorePatterns: ["**/dist/**", "**/__generated__/**"],
overrides: [
// General rules for JS/TS files
{
extends: [
"react-app",
"react-app/jest",
@ -23,4 +27,85 @@ module.exports = {
"plugin:jsx-a11y/recommended",
],
plugins: ["jsx-a11y"],
files: ["*.ts", "*.tsx", "*.cjs"],
},
// Processor to extract GraphQL operations embedded in TS files
{
files: ["*.tsx", "*.ts"],
processor: "@graphql-eslint/graphql",
},
// Validate the GraphQL schema
{
files: "./schema.graphql",
extends: [
"plugin:@graphql-eslint/schema-recommended",
"plugin:@graphql-eslint/relay",
"prettier",
"plugin:prettier/recommended",
],
rules: {
"@graphql-eslint/relay-edge-types": [
"error",
{
// We do have *some* fields without connections,
// and async-graphql's connections 'nodes' field break this anyway
listTypeCanWrapOnlyEdgeType: false,
},
],
"@graphql-eslint/strict-id-in-types": [
"error",
{
exceptions: {
// The '*Connection', '*Edge' and 'PageInfo' types don't have IDs
types: ["PageInfo"],
suffixes: ["Connection", "Edge"],
},
},
],
// We need to disable this rule because of the 'username' field in the 'User' node
"@graphql-eslint/no-typename-prefix": "off",
// We need to disable this rule for object types,
// because the '*Connection' types lack descriptions
"@graphql-eslint/require-description": [
"error",
{
types: true,
ObjectTypeDefinition: false,
},
],
},
},
// Validate the GraphQL operations
{
files: "./src/**/*.graphql",
extends: ["plugin:@graphql-eslint/operations-recommended"],
rules: {
// This rule is copied from the 'operations-recommended' config,
// but without the 'Query' forbidden suffix on operations,
// since it directly clashes with the relay operation naming convention
"@graphql-eslint/naming-convention": [
"error",
{
VariableDefinition: "camelCase",
OperationDefinition: {
style: "PascalCase",
forbiddenPrefixes: ["Query", "Mutation", "Subscription", "Get"],
forbiddenSuffixes: [/* "Query", */ "Mutation", "Subscription"],
},
FragmentDefinition: {
style: "PascalCase",
forbiddenPrefixes: ["Fragment"],
forbiddenSuffixes: ["Fragment"],
},
},
],
},
},
],
};

View File

@ -0,0 +1,4 @@
{
"schema": "./schema.graphql",
"documents": "./src/**/*"
}

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,10 @@
"type": "module",
"scripts": {
"dev": "vite",
"lint": "eslint src/ .eslintrc.cjs tailwind.config.cjs vite.config.ts",
"generate": "relay-compiler && eslint --fix .",
"lint": "relay-compiler --validate && eslint . && tsc",
"relay": "relay-compiler",
"build": "tsc && vite build",
"build": "npm run lint && vite build",
"preview": "vite preview"
},
"dependencies": {
@ -17,6 +18,7 @@
"relay-runtime": "^14.1.0"
},
"devDependencies": {
"@graphql-eslint/eslint-plugin": "^3.13.1",
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8",

View File

@ -1,6 +1,6 @@
{
"src": "./src",
"schema": "../crates/graphql/schema.graphql",
"schema": "./schema.graphql",
"language": "typescript",
"eagerEsModules": true,
"exclude": [

415
frontend/schema.graphql Normal file
View File

@ -0,0 +1,415 @@
"""
An authentication records when a user enter their credential in a browser
session.
"""
type Authentication implements Node {
"""
ID of the object.
"""
id: ID!
"""
When the object was created.
"""
createdAt: DateTime!
}
"""
A browser session represents a logged in user in a browser.
"""
type BrowserSession implements Node {
"""
ID of the object.
"""
id: ID!
"""
The user logged in this session.
"""
user: User!
"""
The most recent authentication of this session.
"""
lastAuthentication: Authentication
"""
When the object was created.
"""
createdAt: DateTime!
}
type BrowserSessionConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [BrowserSessionEdge!]!
"""
A list of nodes.
"""
nodes: [BrowserSession!]!
}
"""
An edge in a connection.
"""
type BrowserSessionEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: BrowserSession!
}
"""
A compat session represents a client session which used the legacy Matrix
login API.
"""
type CompatSession implements Node {
"""
ID of the object.
"""
id: ID!
"""
The user authorized for this session.
"""
user: User!
"""
The Matrix Device ID of this session.
"""
deviceId: String!
"""
When the object was created.
"""
createdAt: DateTime!
"""
When the session ended.
"""
finishedAt: DateTime
}
"""
A compat SSO login represents a login done through the legacy Matrix login
API, via the `m.login.sso` login method.
"""
type CompatSsoLogin implements Node {
"""
ID of the object.
"""
id: ID!
"""
When the object was created.
"""
createdAt: DateTime!
"""
The redirect URI used during the login.
"""
redirectUri: Url!
"""
When the login was fulfilled, and the user was redirected back to the
client.
"""
fulfilledAt: DateTime
"""
When the client exchanged the login token sent during the redirection.
"""
exchangedAt: DateTime
"""
The compat session which was started by this login.
"""
session: CompatSession
}
type CompatSsoLoginConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [CompatSsoLoginEdge!]!
"""
A list of nodes.
"""
nodes: [CompatSsoLogin!]!
}
"""
An edge in a connection.
"""
type CompatSsoLoginEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: CompatSsoLogin!
}
"""
Implement the DateTime<Utc> scalar
The input/output is a string in RFC3339 format.
"""
scalar DateTime
"""
An object with an ID.
"""
interface Node {
"""
ID of the object.
"""
id: ID!
}
"""
An OAuth 2.0 client
"""
type Oauth2Client implements Node {
"""
ID of the object.
"""
id: ID!
"""
OAuth 2.0 client ID
"""
clientId: String!
"""
Client name advertised by the client.
"""
clientName: String
"""
Client URI advertised by the client.
"""
clientUri: Url
"""
Terms of services URI advertised by the client.
"""
tosUri: Url
"""
Privacy policy URI advertised by the client.
"""
policyUri: Url
"""
List of redirect URIs used for authorization grants by the client.
"""
redirectUris: [Url!]!
}
"""
An OAuth 2.0 session represents a client session which used the OAuth APIs
to login.
"""
type Oauth2Session implements Node {
"""
ID of the object.
"""
id: ID!
"""
OAuth 2.0 client used by this session.
"""
client: Oauth2Client!
"""
Scope granted for this session.
"""
scope: String!
"""
The browser session which started this OAuth 2.0 session.
"""
browserSession: BrowserSession!
"""
User authorized for this session.
"""
user: User!
}
type Oauth2SessionConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [Oauth2SessionEdge!]!
"""
A list of nodes.
"""
nodes: [Oauth2Session!]!
}
"""
An edge in a connection.
"""
type Oauth2SessionEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: Oauth2Session!
}
"""
Information about pagination in a connection
"""
type PageInfo {
"""
When paginating backwards, are there more items?
"""
hasPreviousPage: Boolean!
"""
When paginating forwards, are there more items?
"""
hasNextPage: Boolean!
"""
When paginating backwards, the cursor to continue.
"""
startCursor: String
"""
When paginating forwards, the cursor to continue.
"""
endCursor: String
}
"""
The query root of the GraphQL interface.
"""
type RootQuery {
"""
Get the current logged in browser session
"""
currentBrowserSession: BrowserSession
"""
Get the current logged in user
"""
currentUser: User
}
"""
URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/)
"""
scalar Url
"""
A user is an individual's account.
"""
type User implements Node {
"""
ID of the object.
"""
id: ID!
"""
Username chosen by the user.
"""
username: String!
"""
Primary email address of the user.
"""
primaryEmail: UserEmail
"""
Get the list of compatibility SSO logins, chronologically sorted
"""
compatSsoLogins(
after: String
before: String
first: Int
last: Int
): CompatSsoLoginConnection!
"""
Get the list of active browser sessions, chronologically sorted
"""
browserSessions(
after: String
before: String
first: Int
last: Int
): BrowserSessionConnection!
"""
Get the list of emails, chronologically sorted
"""
emails(
after: String
before: String
first: Int
last: Int
): UserEmailConnection!
"""
Get the list of OAuth 2.0 sessions, chronologically sorted
"""
oauth2Sessions(
after: String
before: String
first: Int
last: Int
): Oauth2SessionConnection!
}
"""
A user email address
"""
type UserEmail implements Node {
"""
ID of the object.
"""
id: ID!
"""
Email address
"""
email: String!
"""
When the object was created.
"""
createdAt: DateTime!
"""
When the email address was confirmed. Is `null` if the email was never
verified by the user.
"""
confirmedAt: DateTime
}
type UserEmailConnection {
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
"""
A list of edges.
"""
edges: [UserEmailEdge!]!
"""
A list of nodes.
"""
nodes: [UserEmail!]!
"""
Identifies the total count of items in the connection.
"""
totalCount: Int!
}
"""
An edge in a connection.
"""
type UserEmailEdge {
"""
A cursor for use in pagination
"""
cursor: String!
"""
The item at the end of the edge
"""
node: UserEmail!
}
schema {
query: RootQuery
}

View File

@ -22,6 +22,7 @@ export const App: React.FC = () => {
graphql`
query AppQuery {
currentUser {
id
username
}
}

View File

@ -1,5 +1,5 @@
/**
* @generated SignedSource<<85f26fc928e25f5adee1aa4bdb35943f>>
* @generated SignedSource<<1d2a0ace12c4e6d2a684909c137cbc73>>
* @lightSyntaxTransform
* @nogrep
*/
@ -12,6 +12,7 @@ import { ConcreteRequest, Query } from 'relay-runtime';
export type AppQuery$variables = {};
export type AppQuery$data = {
readonly currentUser: {
readonly id: string;
readonly username: string;
} | null;
};
@ -21,20 +22,7 @@ export type AppQuery = {
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "AppQuery",
"selections": [
var v0 = [
{
"alias": null,
"args": null,
@ -43,11 +31,31 @@ return {
"name": "currentUser",
"plural": false,
"selections": [
(v0/*: any*/)
],
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "AppQuery",
"selections": (v0/*: any*/),
"type": "RootQuery",
"abstractKey": null
},
@ -56,39 +64,19 @@ return {
"argumentDefinitions": [],
"kind": "Operation",
"name": "AppQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "currentUser",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
]
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "84b2b5650a0c71cce7c40242b378e52f",
"cacheID": "34c6b7534570705caab3d20018e06721",
"id": null,
"metadata": {},
"name": "AppQuery",
"operationKind": "query",
"text": "query AppQuery {\n currentUser {\n username\n id\n }\n}\n"
"text": "query AppQuery {\n currentUser {\n id\n username\n }\n}\n"
}
};
})();
(node as any).hash = "fef45d7542db63ccb8b3d59ca5dc6afb";
(node as any).hash = "25e4287bcbdf0f3f35028d4be484dda9";
export default node;

View File

@ -5,9 +5,12 @@ set -eu
export SQLX_OFFLINE=1
BASE_DIR="$(dirname "$0")/.."
CONFIG_SCHEMA="${BASE_DIR}/docs/config.schema.json"
GRAPHQL_SCHEMA="${BASE_DIR}/crates/graphql/schema.graphql"
GRAPHQL_SCHEMA="${BASE_DIR}/frontend/schema.graphql"
set -x
# XXX: we shouldn't have to specify this feature
cargo run -p mas-config --features webpki-roots > "${CONFIG_SCHEMA}"
cargo run -p mas-graphql --features webpki-roots > "${GRAPHQL_SCHEMA}"
cd "${BASE_DIR}/frontend"
npm run generate