diff --git a/.circleci/template.yml b/.circleci/template.yml index f72f7978a5..31bbf32e3c 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -435,9 +435,15 @@ jobs: docs_build_deploy: parallelism: 1 docker: - - image: cimg/python:3.9.0 + - image: cimg/python:3.9.0-node steps: - checkout + - run: + name: Build GraphQL static docs + command: | + npm install --prefix=$HOME/.local --global spectaql + npx spectaql -t doc/graphql-api -f admin-graphql-doc.html doc/graphql-api/Admin-GraphQL_spectaql.yml + npx spectaql -C -J -t doc/graphql-api -f user-graphql-doc.html doc/graphql-api/User-GraphQL_spectaql.yml - run: name: Test that docs build command: | diff --git a/doc/configuration/listen.md b/doc/configuration/listen.md index 67fc0ce2ec..95b87dc1bf 100644 --- a/doc/configuration/listen.md +++ b/doc/configuration/listen.md @@ -410,6 +410,7 @@ There are the following options for each of the HTTP listeners: * `mod_bosh` - for [BOSH](https://xmpp.org/extensions/xep-0124.html) connections, * `mod_websockets` - for [WebSocket](https://tools.ietf.org/html/rfc6455) connections, + * `mongoose_graphql_cowboy_handler` - for GraphQL API, * `mongoose_api_admin`, `mongoose_api_client`(obsolete), `mongoose_client_api`, `mongoose_domain_handler`, `mongoose_api` - for REST API. These types are described below in more detail. @@ -492,6 +493,36 @@ Maximum allowed incoming stanza size. This subsection enables external component connections over WebSockets. See the [service](#xmpp-components-listenservice) listener section for details. +### Handler types: GraphQL API - `mongoose_graphql_cowboy_handler` + +For more information about the API, see the [Admin interface](../graphql-api/Admin-GraphQL.md) and [User interface](../graphql-api/User-GraphQL.md) documentation. +The following options are supported for this handler: + +#### `listen.http.handlers.mongoose_graphql_cowboy_handler.schema_endpoint` +* **Syntax:** string, one of `"admin"`, `"domain_admin"`, `"user"` +* **Default:** no default, this option is mandatory +* **Example:** `schema_endpoint = "admin"` + +Specifies the schema endpoint: + +* `admin` - Endpoint with the admin commands. A global admin has permission to execute all commands. See the recommended configuration - [Example 5](#example-5-admin-graphql-api). +* `domain_admin` - Endpoint with the admin commands. A domain admin has permission to execute only commands with the owned domain. See the recommended configuration - [Example 6](#example-6-domain-admin-graphql-api). +* `user` - Endpoint with the user commands. Used to manage the authorized user. See the recommended configuration - [Example 7](#example-7-user-graphql-api). + +#### `listen.http.handlers.mongoose_graphql_cowboy_handler.username` - only for `admin` +* **Syntax:** string +* **Default:** not set +* **Example:** `username = "admin"` + +When set, enables authentication for the admin API, otherwise it is disabled. Requires setting `password`. + +#### `listen.http.handlers.mongoose_graphql_cowboy_handler.password` - only for `admin` +* **Syntax:** string +* **Default:** not set +* **Example:** `password = "secret"` + +Required to enable authentication for the admin API. + ### Handler types: REST API - Admin - `mongoose_api_admin` The recommended configuration is shown in [Example 2](#example-2-admin-api) below. @@ -679,3 +710,56 @@ REST API for domain management. username = "admin" password = "secret" ``` + +#### Example 5. Admin GraphQL API + +GraphQL API for administration, the listener is bound to 127.0.0.1 for increased security. The number of acceptors and connections is specified (reduced). + +```toml +[[listen.http]] + ip_address = "127.0.0.1" + port = 8088 + transport.num_acceptors = 5 + transport.max_connections = 10 + + [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + host = "localhost" + path = "/api/graphql" + schema_endpoint = "admin" + username = "admin" + password = "secret" +``` + +#### Example 6. Domain Admin GraphQL API + +GraphQL API for the domain admin. + +```toml +[[listen.http]] + ip_address = "0.0.0.0" + port = 5041 + transport.num_acceptors = 10 + transport.max_connections = 1024 + + [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + host = "_" + path = "/api/graphql" + schema_endpoint = "domain_admin" +``` + +#### Example 7. User GraphQL API + +GraphQL API for the user. + +```toml +[[listen.http]] + ip_address = "0.0.0.0" + port = 5061 + transport.num_acceptors = 10 + transport.max_connections = 1024 + + [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + host = "_" + path = "/api/graphql" + schema_endpoint = "user" +``` diff --git a/doc/graphql-api/Admin-GraphQL.md b/doc/graphql-api/Admin-GraphQL.md new file mode 100644 index 0000000000..73856f0f8d --- /dev/null +++ b/doc/graphql-api/Admin-GraphQL.md @@ -0,0 +1,65 @@ +# MongooseIM's GraphQL API for the administrator + +The new GraphQL admin API contains all the commands available through the REST API, and the vast majority of the CLI (`mongooseimctl`) commands. Only commands that wouldn't have worked well with GraphQL style have been omitted. + +We can distinguish two levels of the administration. A global admin (has access to all commands), and the admin per domain (has access only to the own domain). Each of them is handled by a different endpoint. Please see the configuration [Listen](../../configuration/listen/#handler-types-graphql-api-mongoose_graphql_cowboy_handler) section for more details. + +There is only one schema for both admin types. Admin per domain simply has no permissions to execute global commands or commands with not owned domain. The API documentation clearly says which commands are global. + +## Domain admin configuration + +Out of the box, domains are created with a disabled admin account. Admin per domain can be enabled only by the global admin with the command +mutation.domains.setDomainPassword. Afterward, the domain admin can change the password with the same command. + +The admin per domain can be disabled by the global admin with the command mutation.domains.removeDomainPassword. + +## Authentication + +MongooseIM uses *Basic Authentication* as an authentication method for the GraphQL API. + +*Basic authentication* is a simple authentication scheme built into the HTTP protocol. +Each HTTP request to the GraphQL API has to contain the Authorization header +with the word `Basic` followed by a space and a base64-encoded string. + +### Global admin endpoint + +The authentication for global admin is optional because this endpoint shouldn't be exposed outside. The credentials set in the handler section in the config enables the authentication. Please see the [GraphQL handler](../configuration/listen.md#handler-types-graphql-api-mongoose_graphql_cowboy_handler) section for more details. + +The base64-encoded string should have the form +`LOGIN:PASSWORD`, where: + +- `LOGIN` is the login set in the config, +- `PASSWORD` is the password set in the config. + +### Domain admin endpoint + +The authorization as a domain admin the base64-encoded string should have the form +`admin@DOMAIN:PASSWORD`, where: + +- `DOMAIN` is the domain to authorize, +- `PASSWORD` is the password for the given domain. + +## GraphiQL + +GraphiQL is the GraphQL integrated development environment (IDE). It allows to experiment with API and run queries with ease. The GraphiQL page is automatically served with each GraphQL endpoint. For example: + +`http://localhost:5551/api/graphql` + +Open the above address in your browser and try to use it. + +### Authorization + +Executing some of the queries requires authorization. Just add the following JSON into the header tab. Remember to update the credentials. + +```json +{ + "Authorization": "Basic YWxpY2VAbG9jYWxob3N0OnNlY3JldA==" +} +``` + +## Static documentation + +Open GraphQL documentation as a full page + + diff --git a/priv/graphql/spectaql_config.yml b/doc/graphql-api/Admin-GraphQL_spectaql.yml similarity index 78% rename from priv/graphql/spectaql_config.yml rename to doc/graphql-api/Admin-GraphQL_spectaql.yml index 8889c68ef7..f6fff344f6 100644 --- a/priv/graphql/spectaql_config.yml +++ b/doc/graphql-api/Admin-GraphQL_spectaql.yml @@ -1,11 +1,16 @@ # Config file needed to generate static documentation from GraphQL using SpectaQL +spectaql: + themeDir: default + introspection: - url: http://localhost:5551/api/graphql + schemaFile: + - priv/graphql/schemas/admin/**/*.gql + - priv/graphql/schemas/global/**/*.gql info: title: MongooseIM GraphQL API Reference - description: A static documentation of the MongooseIM GraphQL API + description: A static documentation of the MongooseIM GraphQL Admin API contact: name: API Support url: https://github.com/esl/MongooseIM diff --git a/doc/graphql-api/User-GraphQL.md b/doc/graphql-api/User-GraphQL.md new file mode 100644 index 0000000000..3d3ebe0ff7 --- /dev/null +++ b/doc/graphql-api/User-GraphQL.md @@ -0,0 +1,47 @@ +# MongooseIM's GraphQL API for the user + +The new GraphQL user API contains all commands from the client REST API and provides plenty of new ones. Multiple commands previously available only for the admin have their counterparts for the user. + +## Authentication + +MongooseIM uses *Basic Authentication* as the authentication method for the GraphQL API. + +*Basic authentication* is a simple authentication scheme built into the HTTP protocol. +Each HTTP request to the client REST API has to contain the Authorization header +with the word `Basic` followed by a space and a base64-encoded string +`username@host:password`, where: + +- `username@host` is the user's *bare JID*, +- `password` is the password used to register the user's account. + +For example, to authorize as `alice@localhost` with the password `secret`, the +client would send a header: + +``` +Authorization: Basic YWxpY2VAbG9jYWxob3N0OnNlY3JldA== +``` + +## GraphiQL + +GraphiQL is the GraphQL integrated development environment (IDE). It allows to experiment with API and run queries with ease. The GraphiQL page is automatically served with each GraphQL endpoint. For example: + +`http://localhost:5561/api/graphql` + +Open the above address in your browser and try to use it. + +### Authorization + +Executing some of the queries requires authorization. Just add the following JSON into the header tab. Remember to update the credentials. + +```json +{ + "Authorization": "Basic YWxpY2VAbG9jYWxob3N0OnNlY3JldA==" +} +``` + +## Static documentation + +Open GraphQL documentation as a full page + + diff --git a/doc/graphql-api/User-GraphQL_spectaql.yml b/doc/graphql-api/User-GraphQL_spectaql.yml new file mode 100644 index 0000000000..d9b63865a3 --- /dev/null +++ b/doc/graphql-api/User-GraphQL_spectaql.yml @@ -0,0 +1,25 @@ +# Config file needed to generate static documentation from GraphQL using SpectaQL + +spectaql: + themeDir: default + +introspection: + schemaFile: + - priv/graphql/schemas/user/**/*.gql + - priv/graphql/schemas/global/**/*.gql + +info: + title: MongooseIM GraphQL API Reference + description: A static documentation of the MongooseIM GraphQL User API + contact: + name: API Support + url: https://github.com/esl/MongooseIM + email: mongoose-im@erlang-solutions.com + license: + name: GPL-2.0 + url: https://github.com/esl/MongooseIM/blob/master/COPYING + +servers: + - url: http://localhost:5561/api/graphql + description: Dev mim1 + production: false diff --git a/mkdocs.yml b/mkdocs.yml index 18dfbf90fd..73beca05ad 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -144,6 +144,9 @@ nav: - 'Metrics backend': 'rest-api/Metrics-backend.md' - 'Administration backend': 'rest-api/Administration-backend.md' - 'Dynamic domains': 'rest-api/Dynamic-domains.md' + - 'GraphQL API': + - 'User': 'graphql-api/User-GraphQL.md' + - 'Admin': 'graphql-api/Admin-GraphQL.md' - 'Operation and Maintenance': - 'GDPR considerations': 'operation-and-maintenance/gdpr-considerations.md' - 'Cluster management considerations': 'operation-and-maintenance/Cluster-management-considerations.md' diff --git a/priv/graphql/schemas/admin/account.gql b/priv/graphql/schemas/admin/account.gql index 77956b402e..6856001d00 100644 --- a/priv/graphql/schemas/admin/account.gql +++ b/priv/graphql/schemas/admin/account.gql @@ -48,7 +48,7 @@ type UserPayload{ "Check password correctness payload" type CheckPasswordPayload{ "Status of the password correctness" - correct: Bool! + correct: Boolean! "Result message" message: String! } @@ -56,7 +56,7 @@ type CheckPasswordPayload{ "Check user existence payload" type CheckUserPayload{ "Status of the user existence" - exist: Bool! + exist: Boolean! "Result message" message: String! } diff --git a/priv/graphql/schemas/admin/domain.gql b/priv/graphql/schemas/admin/domain.gql index 5f6080269b..cc3cd8748b 100644 --- a/priv/graphql/schemas/admin/domain.gql +++ b/priv/graphql/schemas/admin/domain.gql @@ -1,5 +1,5 @@ type DomainAdminQuery @protected{ - "Get all enabled domains by hostType" + "Get all enabled domains by hostType. Only for global admin" domainsByHostType(hostType: String!): [String!] @protected(type: GLOBAL) "Get information about the domain" @@ -8,22 +8,22 @@ type DomainAdminQuery @protected{ } type DomainAdminMutation @protected{ - "Add new domain" + "Add new domain. Only for global admin" addDomain(domain: String!, hostType: String!): Domain @protected(type: GLOBAL) - "Remove domain" + "Remove domain. Only for global admin" removeDomain(domain: String!, hostType: String!): RemoveDomainPayload @protected(type: GLOBAL) - "Enable domain" + "Enable domain. Only for global admin" enableDomain(domain: String!): Domain @protected(type: GLOBAL) - "Disable domain" + "Disable domain. Only for global admin" disableDomain(domain: String!): Domain @protected(type: GLOBAL) "Create or update domain admin password" setDomainPassword(domain: String!, password: String!): String @protected(type: DOMAIN, args: ["domain"]) - "Delete domain admin password" + "Delete domain admin password. Only for global admin" deleteDomainPassword(domain: String!): String @protected(type: GLOBAL) } diff --git a/priv/graphql/schemas/admin/inbox.gql b/priv/graphql/schemas/admin/inbox.gql index fdc6c4aad5..fd184f3246 100644 --- a/priv/graphql/schemas/admin/inbox.gql +++ b/priv/graphql/schemas/admin/inbox.gql @@ -22,7 +22,7 @@ type InboxAdminMutation @protected{ flushGlobalBin( "Required to identify the DB backend" hostType: String!, - "Remove older than given days or all if null" + "Remove older than given days or all if null. Only for global admin" days: PosInt ): Int @protected(type: GLOBAL) } diff --git a/priv/graphql/schemas/admin/stanza.gql b/priv/graphql/schemas/admin/stanza.gql index 93015e0e68..9a562ed375 100644 --- a/priv/graphql/schemas/admin/stanza.gql +++ b/priv/graphql/schemas/admin/stanza.gql @@ -11,7 +11,7 @@ type StanzaAdminMutation @protected{ "Send a headline message to a local or remote bare or full JID" sendMessageHeadLine(from: JID!, to: JID!, subject: String, body: String): SendStanzaPayload @protected(type: DOMAIN, args: ["from"]) - "Send an arbitrary stanza" + "Send an arbitrary stanza. Only for global admin" sendStanza(stanza: Stanza): SendStanzaPayload @protected(type: GLOBAL) } diff --git a/priv/graphql/schemas/global/muc.gql b/priv/graphql/schemas/global/muc.gql index 724db72c06..16de7c3e35 100644 --- a/priv/graphql/schemas/global/muc.gql +++ b/priv/graphql/schemas/global/muc.gql @@ -33,49 +33,49 @@ type MUCRoomDesc{ type MUCRoomConfig{ title: String!, description: String!, - allowChangeSubject: Bool!, - allowQueryUsers: Bool!, - allowPrivateMessages: Bool!, - allowVisitorStatus: Bool!, - allowVisitorNickchange: Bool!, - public: Bool!, - publicList: Bool!, - persistent: Bool!, - moderated: Bool!, - membersByDefault: Bool!, - membersOnly: Bool!, - allowUserInvites: Bool!, - allowMultipleSession: Bool!, - passwordProtected: Bool!, + allowChangeSubject: Boolean!, + allowQueryUsers: Boolean!, + allowPrivateMessages: Boolean!, + allowVisitorStatus: Boolean!, + allowVisitorNickchange: Boolean!, + public: Boolean!, + publicList: Boolean!, + persistent: Boolean!, + moderated: Boolean!, + membersByDefault: Boolean!, + membersOnly: Boolean!, + allowUserInvites: Boolean!, + allowMultipleSession: Boolean!, + passwordProtected: Boolean!, password: String!, - anonymous: Bool!, + anonymous: Boolean!, mayGetMemberList: [String!]! maxUsers: Int, - logging: Bool!, + logging: Boolean!, } input MUCRoomConfigInput{ title: String, description: String, - allowChangeSubject: Bool, - allowQueryUsers: Bool, - allowPrivateMessages: Bool, - allowVisitorStatus: Bool, - allowVisitorNickchange: Bool, - public: Bool, - publicList: Bool, - persistent: Bool, - moderated: Bool, - membersByDefault: Bool, - membersOnly: Bool, - allowUserInvites: Bool, - allowMultipleSession: Bool, - passwordProtected: Bool, + allowChangeSubject: Boolean, + allowQueryUsers: Boolean, + allowPrivateMessages: Boolean, + allowVisitorStatus: Boolean, + allowVisitorNickchange: Boolean, + public: Boolean, + publicList: Boolean, + persistent: Boolean, + moderated: Boolean, + membersByDefault: Boolean, + membersOnly: Boolean, + allowUserInvites: Boolean, + allowMultipleSession: Boolean, + passwordProtected: Boolean, password: String, - anonymous: Bool, + anonymous: Boolean, mayGetMemberList: [String!], maxUsers: Int - logging: Bool, + logging: Boolean, } type MUCRoomsPayload{ diff --git a/priv/graphql/schemas/user/account.gql b/priv/graphql/schemas/user/account.gql index 462686ca9e..c694ae46ba 100644 --- a/priv/graphql/schemas/user/account.gql +++ b/priv/graphql/schemas/user/account.gql @@ -2,7 +2,7 @@ Allow user to get information about account. """ type AccountUserQuery @protected{ - field: Bool + field: Boolean } """