From 1b3ccd10d4adbf25ec0ac476113a21d7e1942aae Mon Sep 17 00:00:00 2001 From: Brecht De Rooms Date: Wed, 22 Jan 2020 16:28:59 +0100 Subject: [PATCH 1/4] Let users define their own client token. --- examples/with-graphql-faunadb/README.md | 15 ++- examples/with-graphql-faunadb/next.config.js | 2 +- examples/with-graphql-faunadb/package.json | 8 +- .../with-graphql-faunadb/scripts/setup.js | 95 +++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 examples/with-graphql-faunadb/scripts/setup.js diff --git a/examples/with-graphql-faunadb/README.md b/examples/with-graphql-faunadb/README.md index 68815c44ce86dad..f850a23ef176be4 100644 --- a/examples/with-graphql-faunadb/README.md +++ b/examples/with-graphql-faunadb/README.md @@ -16,7 +16,20 @@ By importing a `.gql` or `.graphql` schema into FaunaDB ([see our sample schema You can start with this template [using `create-next-app`](#using-create-next-app) or by [downloading the repository manually](#download-manually). -To use a live FaunaDB database, create one and import this example's `schema.gql` file using the FaunaDB console. Create a client secret, then paste it into `next.config.js`. +To use a live FaunaDB database, create a database at [dashboard.fauna.com](https://dashboard.fauna.com/) and generate a server token by going to the **Security** tab on the left and then click **New Key**. Give the new key a name and select the 'Server' Role. Copy the token since the setup script will ask for it. Do not use it in the frontend, it has superpowers which you don't want to give to your users. + +The database can then be set up with the delivered setup by running: +``` +yarn setup +``` +This script will ask for the server token and will then automatically: + +- **Import the GraphQL schema**, by importing a GraphQL schema in FaunaDB, FaunaDB automatically sets up collections and indexes to support your queries. +- **Create a role suitable for the Client**, FaunaDB has a security system that allows you to define which resources can be accessed for a specific token. That's how we limit our clients powers, feel free to look at the scripts/setup.js script to see how we make roles and tokens. +- **Create a token for that role** which is printed, this is the token to be used in the frontend. + +At the end, the newly generated client token will be printed and should be used to replace the '< GRAPHQL_SECRET >' placeholder in the next.config.js config. + ### Using `create-next-app` diff --git a/examples/with-graphql-faunadb/next.config.js b/examples/with-graphql-faunadb/next.config.js index 53ded0ee42327ba..b80a98e8de9b981 100644 --- a/examples/with-graphql-faunadb/next.config.js +++ b/examples/with-graphql-faunadb/next.config.js @@ -8,7 +8,7 @@ module.exports = { | https://docs.fauna.com/fauna/current/security/ |-------------------------------------------------- */ - faunaDbSecret: 'fnADcnWRUcACE_6uDSw05MspruDdWKk88ZSmsm2a', + faunaDbSecret: '< GRAPHQL_SECRET >', faunaDbGraphQlEndpoint: 'https://graphql.fauna.com/graphql', }, } diff --git a/examples/with-graphql-faunadb/package.json b/examples/with-graphql-faunadb/package.json index 021dea565629111..7e5b79e34468ec7 100644 --- a/examples/with-graphql-faunadb/package.json +++ b/examples/with-graphql-faunadb/package.json @@ -5,11 +5,17 @@ "scripts": { "dev": "next", "build": "next build", - "start": "next start" + "start": "next start", + "setup": "node ./scripts/setup.js" }, "dependencies": { "next": "latest", "react": "^16.10.2", "react-dom": "^16.10.2" + }, + "devDependencies": { + "faunadb": "2.11.1", + "request": "2.88.0", + "stream-to-promise": "2.2.0" } } diff --git a/examples/with-graphql-faunadb/scripts/setup.js b/examples/with-graphql-faunadb/scripts/setup.js new file mode 100644 index 000000000000000..21c59954fdd11a2 --- /dev/null +++ b/examples/with-graphql-faunadb/scripts/setup.js @@ -0,0 +1,95 @@ + +// This script sets up the database to be used for this example application. +// Look at the code to see what is behind the magic +const faunadb = require('faunadb') +const q = faunadb.query +const request = require('request') +const fs = require('fs') +const streamToPromise = require('stream-to-promise') + +const readline = require('readline').createInterface({ + input: process.stdin, + output: process.stdout +}) + +// In order to set up a database, we need a server key, so let's ask the user for a key. +readline.question(`Please provide the FaunaDB server key\n`, (serverKey) => { + // A graphql schema can be imported in override or merge mode: 'https://docs.fauna.com/fauna/current/api/graphql/endpoints#import' + const options = { model: 'merge', 'uri': 'https://graphql.fauna.com/import', headers: { 'Authorization':`Bearer ${serverKey}`}} + const stream = fs.createReadStream('./schema.gql') + .pipe(request.post(options)) + + streamToPromise(stream) + .then((res) => { + const readableResult = res.toString() + if(readableResult.startsWith('Invalid authorization header')){ + console.error('You need to provide a secret, closing. Try again') + return readline.close() + } + else if(readableResult.startsWith('Invalid database secret')){ + console.error('The secret you have provided is not valid, closing. Try again') + return readline.close() + } + else if(readableResult.includes('success')) { + console.log('1. Successfully imported schema') + return readline.close() + } + }) + .catch((err) => { + console.error(err) + console.error(`Could not import schema, closing`) + }) + .then((res) => { + // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. + // Then we create a token that can only read and write to that index and collection + var client = new faunadb.Client({ secret: serverKey }) + return client.query( + q.CreateRole({ + name: 'GuestbookRole', + privileges: [{ + resource: q.Collection('GuestbookEntry'), + actions: { read: true, write: true }, + }, + { + resource: q.Index('entries'), + actions: { read: true }, + }] + })) + .then((res) => { + console.log('2. Successfully created role to read and write guestbook entries') + }) + .catch((err) => { + if(err.toString().includes('instance already exists')){ + console.log('2. Role already exists.') + } + else { + throw err + } + }) + }) + .catch((err) => { + console.error(err) + console.error(`Failed to create role, closing`) + }) + .then((res) => { + // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. + // Then we create a token that can only read and write to that index and collection + var client = new faunadb.Client({ secret: serverKey }) + return client.query( + q.CreateKey({ + role: q.Role('GuestbookRole'), + })) + .then((res) => { + console.log('3. Created key to use in client') + console.log('Replace the < GRAPHQL_SECRET > placehold in next.config.js with:') + console.log(res.secret) + }) + }) + .catch((err) => { + console.error(err) + console.error(`Failed to create key, closing`) + }) + + }) + + \ No newline at end of file From a05a9698a2a3126d2b29b00418788a1201022193 Mon Sep 17 00:00:00 2001 From: Brecht De Rooms Date: Wed, 22 Jan 2020 16:31:45 +0100 Subject: [PATCH 2/4] Make sure users know that GraphQL schema can also be done via the UI --- examples/with-graphql-faunadb/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/with-graphql-faunadb/README.md b/examples/with-graphql-faunadb/README.md index f850a23ef176be4..81e6ae9941ce2a1 100644 --- a/examples/with-graphql-faunadb/README.md +++ b/examples/with-graphql-faunadb/README.md @@ -22,9 +22,9 @@ The database can then be set up with the delivered setup by running: ``` yarn setup ``` -This script will ask for the server token and will then automatically: +This script will ask for the server token. Once you provide it with a valid token, this is what the script automatically does for you: -- **Import the GraphQL schema**, by importing a GraphQL schema in FaunaDB, FaunaDB automatically sets up collections and indexes to support your queries. +- **Import the GraphQL schema**, by importing a GraphQL schema in FaunaDB, FaunaDB automatically sets up collections and indexes to support your queries. This is now done for you with this script but can also be done from the [dashboard.fauna.com](https://dashboard.fauna.com/) UI by going to the GraphQL tab - **Create a role suitable for the Client**, FaunaDB has a security system that allows you to define which resources can be accessed for a specific token. That's how we limit our clients powers, feel free to look at the scripts/setup.js script to see how we make roles and tokens. - **Create a token for that role** which is printed, this is the token to be used in the frontend. From a12a2d3f0d9582e88bdaa03cad642bd16b03d25e Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 22 Jan 2020 15:10:30 -0500 Subject: [PATCH 3/4] Update README.md --- examples/with-graphql-faunadb/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/with-graphql-faunadb/README.md b/examples/with-graphql-faunadb/README.md index 81e6ae9941ce2a1..9201dce913aafb9 100644 --- a/examples/with-graphql-faunadb/README.md +++ b/examples/with-graphql-faunadb/README.md @@ -18,19 +18,20 @@ You can start with this template [using `create-next-app`](#using-create-next-ap To use a live FaunaDB database, create a database at [dashboard.fauna.com](https://dashboard.fauna.com/) and generate a server token by going to the **Security** tab on the left and then click **New Key**. Give the new key a name and select the 'Server' Role. Copy the token since the setup script will ask for it. Do not use it in the frontend, it has superpowers which you don't want to give to your users. -The database can then be set up with the delivered setup by running: +The database can then be set up with the delivered setup by running: + ``` yarn setup ``` + This script will ask for the server token. Once you provide it with a valid token, this is what the script automatically does for you: - **Import the GraphQL schema**, by importing a GraphQL schema in FaunaDB, FaunaDB automatically sets up collections and indexes to support your queries. This is now done for you with this script but can also be done from the [dashboard.fauna.com](https://dashboard.fauna.com/) UI by going to the GraphQL tab -- **Create a role suitable for the Client**, FaunaDB has a security system that allows you to define which resources can be accessed for a specific token. That's how we limit our clients powers, feel free to look at the scripts/setup.js script to see how we make roles and tokens. +- **Create a role suitable for the Client**, FaunaDB has a security system that allows you to define which resources can be accessed for a specific token. That's how we limit our clients powers, feel free to look at the scripts/setup.js script to see how we make roles and tokens. - **Create a token for that role** which is printed, this is the token to be used in the frontend. At the end, the newly generated client token will be printed and should be used to replace the '< GRAPHQL_SECRET >' placeholder in the next.config.js config. - ### Using `create-next-app` Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: From 80101766309ddc92b5db01b032e941857b516203 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 22 Jan 2020 15:10:44 -0500 Subject: [PATCH 4/4] Update setup.js --- .../with-graphql-faunadb/scripts/setup.js | 168 +++++++++--------- 1 file changed, 88 insertions(+), 80 deletions(-) diff --git a/examples/with-graphql-faunadb/scripts/setup.js b/examples/with-graphql-faunadb/scripts/setup.js index 21c59954fdd11a2..f4410feda221fb2 100644 --- a/examples/with-graphql-faunadb/scripts/setup.js +++ b/examples/with-graphql-faunadb/scripts/setup.js @@ -1,6 +1,5 @@ - // This script sets up the database to be used for this example application. -// Look at the code to see what is behind the magic +// Look at the code to see what is behind the magic const faunadb = require('faunadb') const q = faunadb.query const request = require('request') @@ -8,88 +7,97 @@ const fs = require('fs') const streamToPromise = require('stream-to-promise') const readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout + input: process.stdin, + output: process.stdout, }) // In order to set up a database, we need a server key, so let's ask the user for a key. -readline.question(`Please provide the FaunaDB server key\n`, (serverKey) => { - // A graphql schema can be imported in override or merge mode: 'https://docs.fauna.com/fauna/current/api/graphql/endpoints#import' - const options = { model: 'merge', 'uri': 'https://graphql.fauna.com/import', headers: { 'Authorization':`Bearer ${serverKey}`}} - const stream = fs.createReadStream('./schema.gql') - .pipe(request.post(options)) +readline.question(`Please provide the FaunaDB server key\n`, serverKey => { + // A graphql schema can be imported in override or merge mode: 'https://docs.fauna.com/fauna/current/api/graphql/endpoints#import' + const options = { + model: 'merge', + uri: 'https://graphql.fauna.com/import', + headers: { Authorization: `Bearer ${serverKey}` }, + } + const stream = fs.createReadStream('./schema.gql').pipe(request.post(options)) - streamToPromise(stream) - .then((res) => { - const readableResult = res.toString() - if(readableResult.startsWith('Invalid authorization header')){ - console.error('You need to provide a secret, closing. Try again') - return readline.close() - } - else if(readableResult.startsWith('Invalid database secret')){ - console.error('The secret you have provided is not valid, closing. Try again') - return readline.close() - } - else if(readableResult.includes('success')) { - console.log('1. Successfully imported schema') - return readline.close() - } - }) - .catch((err) => { - console.error(err) - console.error(`Could not import schema, closing`) - }) - .then((res) => { - // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. - // Then we create a token that can only read and write to that index and collection - var client = new faunadb.Client({ secret: serverKey }) - return client.query( - q.CreateRole({ - name: 'GuestbookRole', - privileges: [{ - resource: q.Collection('GuestbookEntry'), - actions: { read: true, write: true }, - }, - { - resource: q.Index('entries'), - actions: { read: true }, - }] - })) - .then((res) => { - console.log('2. Successfully created role to read and write guestbook entries') - }) - .catch((err) => { - if(err.toString().includes('instance already exists')){ - console.log('2. Role already exists.') - } - else { - throw err - } - }) - }) - .catch((err) => { - console.error(err) - console.error(`Failed to create role, closing`) + streamToPromise(stream) + .then(res => { + const readableResult = res.toString() + if (readableResult.startsWith('Invalid authorization header')) { + console.error('You need to provide a secret, closing. Try again') + return readline.close() + } else if (readableResult.startsWith('Invalid database secret')) { + console.error( + 'The secret you have provided is not valid, closing. Try again' + ) + return readline.close() + } else if (readableResult.includes('success')) { + console.log('1. Successfully imported schema') + return readline.close() + } + }) + .catch(err => { + console.error(err) + console.error(`Could not import schema, closing`) + }) + .then(res => { + // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. + // Then we create a token that can only read and write to that index and collection + var client = new faunadb.Client({ secret: serverKey }) + return client + .query( + q.CreateRole({ + name: 'GuestbookRole', + privileges: [ + { + resource: q.Collection('GuestbookEntry'), + actions: { read: true, write: true }, + }, + { + resource: q.Index('entries'), + actions: { read: true }, + }, + ], + }) + ) + .then(res => { + console.log( + '2. Successfully created role to read and write guestbook entries' + ) }) - .then((res) => { - // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. - // Then we create a token that can only read and write to that index and collection - var client = new faunadb.Client({ secret: serverKey }) - return client.query( - q.CreateKey({ - role: q.Role('GuestbookRole'), - })) - .then((res) => { - console.log('3. Created key to use in client') - console.log('Replace the < GRAPHQL_SECRET > placehold in next.config.js with:') - console.log(res.secret) - }) + .catch(err => { + if (err.toString().includes('instance already exists')) { + console.log('2. Role already exists.') + } else { + throw err + } }) - .catch((err) => { - console.error(err) - console.error(`Failed to create key, closing`) + }) + .catch(err => { + console.error(err) + console.error(`Failed to create role, closing`) + }) + .then(res => { + // The GraphQL schema is important, this means that we now have a GuestbookEntry Colleciton and an entries index. + // Then we create a token that can only read and write to that index and collection + var client = new faunadb.Client({ secret: serverKey }) + return client + .query( + q.CreateKey({ + role: q.Role('GuestbookRole'), + }) + ) + .then(res => { + console.log('3. Created key to use in client') + console.log( + 'Replace the < GRAPHQL_SECRET > placehold in next.config.js with:' + ) + console.log(res.secret) }) - - }) - - \ No newline at end of file + }) + .catch(err => { + console.error(err) + console.error(`Failed to create key, closing`) + }) +})