From 5057357c5f24417cc3f1d2da50848d0c3b6b7157 Mon Sep 17 00:00:00 2001 From: "Tage A. L. K" Date: Fri, 1 Nov 2019 04:38:36 +0100 Subject: [PATCH 1/3] Remove usage of graph.cool > Remove usage of graph.cool > Setup apollo-micro-server for /api/graphql > Setup Photon with Postgres backend for simple database > Changes to files to make them work with the new changes --- examples/with-apollo-auth/README.md | 17 ++- .../components/RegisterBox.js | 12 +- .../with-apollo-auth/components/SigninBox.js | 7 +- examples/with-apollo-auth/lib/apollo.js | 2 +- .../with-apollo-auth/lib/checkLoggedIn.js | 10 +- examples/with-apollo-auth/package.json | 9 +- .../with-apollo-auth/pages/api/graphql.js | 103 ++++++++++++++++++ .../with-apollo-auth/pages/create-account.js | 7 +- examples/with-apollo-auth/pages/index.js | 8 +- examples/with-apollo-auth/pages/signin.js | 7 +- .../with-apollo-auth/prisma/schema.prisma | 15 +++ examples/with-apollo-auth/project.graphcool | 22 ---- 12 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 examples/with-apollo-auth/pages/api/graphql.js create mode 100644 examples/with-apollo-auth/prisma/schema.prisma delete mode 100644 examples/with-apollo-auth/project.graphcool diff --git a/examples/with-apollo-auth/README.md b/examples/with-apollo-auth/README.md index 800958a40243..5a2cec81a7de 100644 --- a/examples/with-apollo-auth/README.md +++ b/examples/with-apollo-auth/README.md @@ -47,23 +47,20 @@ This is an extention of the _[with Apollo](https://github.com/zeit/next.js/tree/ > > On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/features/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized. > -> This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend. +> This example uses [Photon](https://photonjs.prisma.io/) as ORM connected to Postgres > > _Note: If you're interested in integrating the client with your existing Redux store check out the [`with-apollo-and-redux`](https://github.com/zeit/next.js/tree/master/examples/with-apollo-and-redux) example._ -[graph.cool](https://www.graph.cool) can be setup with many different -[authentication providers](https://www.graph.cool/docs/reference/integrations/overview-seimeish6e/#authentication-providers), the most basic of which is [email-password authentication](https://www.graph.cool/docs/reference/simple-api/user-authentication-eixu9osueb/#email-and-password). Once email-password authentication is enabled for your graph.cool project, you are provided with 2 useful mutations: `createUser` and `signinUser`. +On loading each route, we perform a `me` query to see if the current visitor is logged in (based on a cookie, more on that in a moment). Depending on the query result, and the route, the user may be [redirected](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/lib/redirect.js) to a different page. -On loading each route, we perform a `user` query to see if the current visitor is logged in (based on a cookie, more on that in a moment). Depending on the query result, and the route, the user may be [redirected](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/lib/redirect.js) to a different page. +When creating an account `register` mutation are executed in `/api/graphql`, which returns a token that can be used to authenticate the user. The token is stored in a cookie for easy access (_note: This may have security implications. Please understand XSS and JWT before deploying this to production_). -When creating an account, both the `createUser` and `signinUser` mutations are executed on graph.cool, which returns a token that can be used to [authenticate the user for future requests](https://www.graph.cool/docs/reference/auth/authentication-tokens-eip7ahqu5o/). The token is stored in a cookie for easy access (_note: This may have security implications. Please understand XSS and JWT before deploying this to production_). +A similar process is followed when using `login` mutation. -A similar process is followed when signing in, except `signinUser` is the only mutation executed. +It is important to note the use of Apollo's `client.cache.reset()` method after signing in and signing out to ensure that no user data is kept in the browser's memory. -It is important to note the use of Apollo's `resetStore()` method after signing in and signing out to ensure that no user data is kept in the browser's memory. - -To get this example running locally, you will need to create a graph.cool -account, and provide [the `project.graphcool` schema](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/project.graphcool). +To get this example running locally, you will need to create a Postgres database, and database url in [the `schema.prisma` file](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/prisma/schema.prisma). +Photon supports other databases if you don't wish to use Postgres see [Photon](https://photonjs.prisma.io/) for more information. ### Note: diff --git a/examples/with-apollo-auth/components/RegisterBox.js b/examples/with-apollo-auth/components/RegisterBox.js index 7caa08817d63..30016d50f12a 100644 --- a/examples/with-apollo-auth/components/RegisterBox.js +++ b/examples/with-apollo-auth/components/RegisterBox.js @@ -5,14 +5,8 @@ import cookie from 'cookie' import redirect from '../lib/redirect' const CREATE_USER = gql` - mutation Create($name: String!, $email: String!, $password: String!) { - createUser( - name: $name - authProvider: { email: { email: $email, password: $password } } - ) { - id - } - signinUser(email: { email: $email, password: $password }) { + mutation register($email: String, $name: String, $password: String) { + register(email: $email, name: $name, password: $password) { token } } @@ -23,7 +17,7 @@ const RegisterBox = () => { const onCompleted = data => { // Store the token in cookie - document.cookie = cookie.serialize('token', data.signinUser.token, { + document.cookie = cookie.serialize('token', data.register.token, { maxAge: 30 * 24 * 60 * 60, // 30 days path: '/' // make cookie available for all routes underneath "/" }) diff --git a/examples/with-apollo-auth/components/SigninBox.js b/examples/with-apollo-auth/components/SigninBox.js index e19c286f9258..5d247d573aea 100644 --- a/examples/with-apollo-auth/components/SigninBox.js +++ b/examples/with-apollo-auth/components/SigninBox.js @@ -5,8 +5,8 @@ import cookie from 'cookie' import redirect from '../lib/redirect' const SIGN_IN = gql` - mutation Signin($email: String!, $password: String!) { - signinUser(email: { email: $email, password: $password }) { + mutation login($email: String, $password: String) { + login(email: $email, password: $password) { token } } @@ -18,8 +18,7 @@ const SigninBox = () => { const onCompleted = data => { // Store the token in cookie - document.cookie = cookie.serialize('token', data.signinUser.token, { - sameSite: true, + document.cookie = cookie.serialize('token', data.login.token, { path: '/', maxAge: 30 * 24 * 60 * 60 // 30 days }) diff --git a/examples/with-apollo-auth/lib/apollo.js b/examples/with-apollo-auth/lib/apollo.js index 71f81a8ee8d4..ed84a49cc747 100644 --- a/examples/with-apollo-auth/lib/apollo.js +++ b/examples/with-apollo-auth/lib/apollo.js @@ -152,7 +152,7 @@ function createApolloClient (initialState = {}, { getToken }) { } const httpLink = new HttpLink({ - uri: 'https://api.graph.cool/simple/v1/cj5geu3slxl7t0127y8sity9r', // Server URL (must be absolute) + uri: 'http://localhost:3000/api/graphql', // Server URL (must be absolute) credentials: 'same-origin', fetch, fetchOptions diff --git a/examples/with-apollo-auth/lib/checkLoggedIn.js b/examples/with-apollo-auth/lib/checkLoggedIn.js index 7842dc11c7f8..0e9c475f9e73 100644 --- a/examples/with-apollo-auth/lib/checkLoggedIn.js +++ b/examples/with-apollo-auth/lib/checkLoggedIn.js @@ -4,8 +4,8 @@ export default apolloClient => apolloClient .query({ query: gql` - query getUser { - user { + query me { + me { id name } @@ -13,9 +13,9 @@ export default apolloClient => ` }) .then(({ data }) => { - return { loggedInUser: data } + return { ...data } }) - .catch(() => { + .catch(e => { // Fail gracefully - return { loggedInUser: {} } + return {} }) diff --git a/examples/with-apollo-auth/package.json b/examples/with-apollo-auth/package.json index e60b5fb62cb5..8beda0249bfb 100644 --- a/examples/with-apollo-auth/package.json +++ b/examples/with-apollo-auth/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "next", "build": "next build", - "start": "next start" + "start": "next start", + "postinstall": "prisma2 generate" }, "dependencies": { "@apollo/react-hooks": "^3.0.1", @@ -15,14 +16,20 @@ "apollo-client": "2.6.4", "apollo-link-context": "^1.0.8", "apollo-link-http": "1.5.15", + "apollo-server-micro": "^2.9.7", + "bcryptjs": "^2.4.3", "cookie": "^0.3.1", "graphql": "14.0.2", "graphql-tag": "2.10.1", "https-proxy-agent": "^2.2.1", "isomorphic-unfetch": "^2.0.0", + "jsonwebtoken": "^8.5.1", "next": "latest", "prop-types": "^15.6.1", "react": "^16.7.0", "react-dom": "^16.7.0" + }, + "devDependencies": { + "prisma2": "^2.0.0-preview015" } } diff --git a/examples/with-apollo-auth/pages/api/graphql.js b/examples/with-apollo-auth/pages/api/graphql.js new file mode 100644 index 000000000000..e5b0d2770198 --- /dev/null +++ b/examples/with-apollo-auth/pages/api/graphql.js @@ -0,0 +1,103 @@ +import { ApolloServer, gql } from 'apollo-server-micro' +import bcrypt from 'bcryptjs' +import jwt from 'jsonwebtoken' +import { Photon } from '@generated/photon' + +const photon = new Photon() + +const JWT_SECRET = 'PleaseUseBetterStorageForThisSecret' + +const getUserId = (req) => { + const Authorization = req.headers && req.headers.authorization || '' + if (Authorization) { + const token = Authorization.replace('Bearer ', '') + const verifiedToken = jwt.verify(token, JWT_SECRET) + return verifiedToken.userId + } +} + +const typeDefs = gql` + type Query { + me: User! + } + type Mutation { + register(email: String, name: String, password: String): AuthPayload! + login(email: String, password: String): AuthPayload! + } + type AuthPayload { + token: String + } + type User { + id: String + name: String + email: String + } +` + +const resolvers = { + Query: { + async me (parent, args, context) { + const id = context.user + const user = await context.photon.users.findOne({ where: { id } }).then(user => user) + + if (!user) throw new Error('No such user found.') + + return { ...user } + } + }, + Mutation: { + async register (parent, { email, name, password }, context) { + const hashedPassword = await bcrypt.hash(password, 10) + + const user = await context.photon.users.create({ + data: { + email, + name, + password: hashedPassword + } + }).then(user => user) + + if (!user) throw new Error('No such user found.') + + const token = jwt.sign({ + userId: user.id + }, JWT_SECRET) + + return { token } + }, + async login (parent, { email, password }, context) { + const user = await context.photon.users.findOne({ where: { email } }).then(user => user) + + if (!user) throw new Error('No such user found.') + + const valid = await bcrypt.compare(password, user.password) + + if (valid) { + const token = jwt.sign({ + userId: user.id + }, JWT_SECRET) + + return { token } + } else { + throw new Error('Invalid password.') + } + } + } +} + +const apolloServer = new ApolloServer({ + typeDefs, + resolvers, + context: ({ req }) => { + const user = getUserId(req) + return { req, user, photon } + } +}) + +export const config = { + api: { + bodyParser: false + } +} + +export default apolloServer.createHandler({ path: '/api/graphql' }) diff --git a/examples/with-apollo-auth/pages/create-account.js b/examples/with-apollo-auth/pages/create-account.js index 2907606fde01..612c8be89b66 100644 --- a/examples/with-apollo-auth/pages/create-account.js +++ b/examples/with-apollo-auth/pages/create-account.js @@ -18,11 +18,10 @@ const CreateAccountPage = () => ( ) CreateAccountPage.getInitialProps = async context => { - const { loggedInUser } = await checkLoggedIn(context.apolloClient) + const data = await checkLoggedIn(context.apolloClient) - if (loggedInUser.user) { - // Already signed in? No need to continue. - // Throw them back to the main page + if (data.me) { + // If not signed in, send them somewhere more useful redirect(context, '/') } diff --git a/examples/with-apollo-auth/pages/index.js b/examples/with-apollo-auth/pages/index.js index 4efda52942b8..3354fe0ac1b1 100644 --- a/examples/with-apollo-auth/pages/index.js +++ b/examples/with-apollo-auth/pages/index.js @@ -23,21 +23,21 @@ const IndexPage = ({ loggedInUser }) => { return (
- Hello {loggedInUser.user.name}!
+ Hello {loggedInUser.name}!
) } IndexPage.getInitialProps = async context => { - const { loggedInUser } = await checkLoggedIn(context.apolloClient) + const data = await checkLoggedIn(context.apolloClient) - if (!loggedInUser.user) { + if (!data.me) { // If not signed in, send them somewhere more useful redirect(context, '/signin') } - return { loggedInUser } + return { loggedInUser: data.me } } export default withApollo(IndexPage) diff --git a/examples/with-apollo-auth/pages/signin.js b/examples/with-apollo-auth/pages/signin.js index ceefae7e5064..8a440eae4dcb 100644 --- a/examples/with-apollo-auth/pages/signin.js +++ b/examples/with-apollo-auth/pages/signin.js @@ -18,11 +18,10 @@ const SigninPage = () => ( ) SigninPage.getInitialProps = async context => { - const { loggedInUser } = await checkLoggedIn(context.apolloClient) + const data = await checkLoggedIn(context.apolloClient) - if (loggedInUser.user) { - // Already signed in? No need to continue. - // Throw them back to the main page + if (data.me) { + // If not signed in, send them somewhere more useful redirect(context, '/') } diff --git a/examples/with-apollo-auth/prisma/schema.prisma b/examples/with-apollo-auth/prisma/schema.prisma new file mode 100644 index 000000000000..0a6439d38978 --- /dev/null +++ b/examples/with-apollo-auth/prisma/schema.prisma @@ -0,0 +1,15 @@ +generator photon { + provider = "photonjs" +} + +datasource db { + provider = "postgresql" + url = "postgresql://yevnnxduqfivsa:d91c5ef154ae01e7ef8e7add436065c15491dd2245e6a83149c01f0e4a85b1b0@ec2-54-235-163-246.compute-1.amazonaws.com/dd90ll8j0inem8?schema=next&sslmode=prefer" +} + +model User { + id String @default(cuid()) @id + email String @unique + name String? + password String +} \ No newline at end of file diff --git a/examples/with-apollo-auth/project.graphcool b/examples/with-apollo-auth/project.graphcool deleted file mode 100644 index 277cce8603b9..000000000000 --- a/examples/with-apollo-auth/project.graphcool +++ /dev/null @@ -1,22 +0,0 @@ -# projectId: cj3h80ffbllm20162alevpcby -# version: 3 - -type File implements Node { - contentType: String! - createdAt: DateTime! - id: ID! @isUnique - name: String! - secret: String! @isUnique - size: Int! - updatedAt: DateTime! - url: String! @isUnique -} - -type User implements Node { - createdAt: DateTime! - email: String @isUnique - id: ID! @isUnique - name: String! - password: String - updatedAt: DateTime! -} From 85402138d4b8574909bfac5d53d249318fd7d905 Mon Sep 17 00:00:00 2001 From: "Tage A. L. K" Date: Fri, 1 Nov 2019 04:52:39 +0100 Subject: [PATCH 2/3] Update dependencies > Update dependencies to latest --- examples/with-apollo-auth/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/with-apollo-auth/package.json b/examples/with-apollo-auth/package.json index 8beda0249bfb..79f7e6ce754e 100644 --- a/examples/with-apollo-auth/package.json +++ b/examples/with-apollo-auth/package.json @@ -10,24 +10,24 @@ "postinstall": "prisma2 generate" }, "dependencies": { - "@apollo/react-hooks": "^3.0.1", - "@apollo/react-ssr": "^3.0.1", + "@apollo/react-hooks": "^3.1.3", + "@apollo/react-ssr": "^3.1.3", "apollo-cache-inmemory": "1.6.3", "apollo-client": "2.6.4", - "apollo-link-context": "^1.0.8", - "apollo-link-http": "1.5.15", + "apollo-link-context": "^1.0.19", + "apollo-link-http": "^1.5.16", "apollo-server-micro": "^2.9.7", "bcryptjs": "^2.4.3", - "cookie": "^0.3.1", - "graphql": "14.0.2", + "cookie": "^0.4.0", + "graphql": "^14.5.8", "graphql-tag": "2.10.1", - "https-proxy-agent": "^2.2.1", - "isomorphic-unfetch": "^2.0.0", + "https-proxy-agent": "^3.0.1", + "isomorphic-unfetch": "^3.0.0", "jsonwebtoken": "^8.5.1", "next": "latest", - "prop-types": "^15.6.1", - "react": "^16.7.0", - "react-dom": "^16.7.0" + "prop-types": "^15.7.2", + "react": "^16.11.0", + "react-dom": "^16.11.0" }, "devDependencies": { "prisma2": "^2.0.0-preview015" From 725c7bddc02b6bc8af09de8949f9b2e306a7379b Mon Sep 17 00:00:00 2001 From: "Tage A. L. K" Date: Sun, 10 Nov 2019 00:02:44 +0100 Subject: [PATCH 3/3] Fix for CircleCI linter Change how a line is written --- examples/with-apollo-auth/pages/api/graphql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-apollo-auth/pages/api/graphql.js b/examples/with-apollo-auth/pages/api/graphql.js index e5b0d2770198..9a37f5cfd35c 100644 --- a/examples/with-apollo-auth/pages/api/graphql.js +++ b/examples/with-apollo-auth/pages/api/graphql.js @@ -8,7 +8,7 @@ const photon = new Photon() const JWT_SECRET = 'PleaseUseBetterStorageForThisSecret' const getUserId = (req) => { - const Authorization = req.headers && req.headers.authorization || '' + const Authorization = req.headers && req.headers.authorization ? req.headers.authorization : '' if (Authorization) { const token = Authorization.replace('Bearer ', '') const verifiedToken = jwt.verify(token, JWT_SECRET)