43
loading...
This website collects cookies to deliver better user experience
This article was published on 2018-11-16 by Arda Tanrikulu @ The Guild Blog
If you haven't checked out our blog post about authentication and authorization in GraphQL Modules, please read that before!
yarn add mongodb @accounts/server @accounts/password @accounts/database-manager @accounts/mongo @accounts/graphql-api @graphql-modules/core apollo-server graphql-import-node
import 'graphql-import-node';
import { ApolloServer } from 'apollo-server';
import { MongoClient, ObjectId } from 'mongodb';
import { AccountsServer } from '@accounts/server';
import { AccountsPassword } from '@accounts/password';
import { DatabaseManager } from '@accounts/database-manager';
import MongoDBInterface from '@accounts/mongo';
import { AccountsModule } from '@accounts/graphql-api';
import * as typeDefs from './typeDefs.graphql';
import { resolvers } from './resolvers';
const PORT = process.env['MONGO_URI'] || 4000;
const MONGO_URI = process.env['MONGO_URI'] || 'mongodb://localhost:27017/myDb';
const TOKEN_SECRET = process.env['TOKEN_SECRET'] || 'myTokenSecret';
async function main() {
const mongoClient = await MongoClient.connect(MONGO_URI, {
useNewUrlParser: true,
native_parser: true
});
const db = mongoClient.db();
const userStorage = new MongoDBInterface(db, {
convertUserIdToMongoObjectId: false
});
// Create database manager (create user, find users, sessions etc) for accounts-js
const accountsDb = new DatabaseManager({
sessionStorage: userStorage,
userStorage,
});
// Create accounts server that holds a lower level of all accounts operations
const accountsServer = new AccountsServer(
{
db: accountsDb,
tokenSecret: TOKEN_SECRET
},
{
password: new AccountsPassword(),
}
);
const { schema } = new GraphQLModule({
typeDefs,
resolvers,
imports: [
AccountsModule.forRoot({
accountsServer
});
],
providers: [
{
provide: Db,
useValue: db // Use MongoDB instance inside DI
}
]
});
const apolloServer = new ApolloServer({
schema,
context: session => session,
introspection: true
});
const { url } = await apolloServer.listen(PORT);
console.log(`Server listening: ${url}`);
}
main();
type Query {
allPosts: [Post]
}
type Mutation {
addPost(title: String, content: String): ID @auth
}
type User {
posts: [Post]
}
type Post {
id: ID
title: String
content: String
author: User
}
export const resolvers = {
User: {
posts: ({ _id }, args, { injector }) => {
const db = injector.get(Db);
const Posts = db.collection('posts');
return Posts.find({ userId: _id }).toArray();
}
},
Post: {
id: ({ _id }) => _id,
author: ({ userId }, args, { injector }) => {
const accountsServer = injector.get(AccountsServer);
return accountsServer.findUserById(userId);
}
},
Query: {
allPosts: (root, args, { injector }) => {
const db = injector.get(Db);
const Posts = db.collection('posts');
return Posts.find().toArray();
}
},
Mutation: {
addPost: (root, { title, content }, { injector, userId }: ModuleContext<AccountsContext>) => {
const db = injector.get(Db);
const Posts = db.collection('posts');
const { insertedId } = Posts.insertOne({ title, content, userId });
return insertedId;
}
}
type TwoFactorSecretKey {
ascii: String
base32: String
hex: String
qr_code_ascii: String
qr_code_hex: String
qr_code_base32: String
google_auth_qr: String
otpauth_url: String
}
input TwoFactorSecretKeyInput {
ascii: String
base32: String
hex: String
qr_code_ascii: String
qr_code_hex: String
qr_code_base32: String
google_auth_qr: String
otpauth_url: String
}
input CreateUserInput {
username: String
email: String
password: String
}
type Query {
twoFactorSecret: TwoFactorSecretKey
getUser: User
allPosts: [Post]
}
type Mutation {
createUser(user: CreateUserInput!): ID
verifyEmail(token: String!): Boolean
resetPassword(token: String!, newPassword: String!): Boolean
sendVerificationEmail(email: String!): Boolean
sendResetPasswordEmail(email: String!): Boolean
changePassword(oldPassword: String!, newPassword: String!): Boolean
twoFactorSet(secret: TwoFactorSecretKeyInput!, code: String!): Boolean
twoFactorUnset(code: String!): Boolean
impersonate(accessToken: String!, username: String!): ImpersonateReturn
refreshTokens(accessToken: String!, refreshToken: String!): LoginResult
logout: Boolean
authenticate(serviceName: String!, params: AuthenticateParamsInput!): LoginResult
addPost(title: String, content: String): Post
}
type Tokens {
refreshToken: String
accessToken: String
}
type LoginResult {
sessionId: String
tokens: Tokens
}
type ImpersonateReturn {
authorized: Boolean
tokens: Tokens
user: User
}
type EmailRecord {
address: String
verified: Boolean
}
type User {
id: ID!
emails: [EmailRecord!]
username: String
posts: [Post]
}
input UserInput {
id: ID
email: String
username: String
}
input AuthenticateParamsInput {
access_token: String
access_token_secret: String
provider: String
password: String
user: UserInput
code: String
}
type Post {
id: ID
title: String
content: String
author: User
}
import React, { Component } from 'react'
import { AccountsClient } from '@accounts/client'
import { AccountsClientPassword } from '@accounts/client-password'
import GraphQLClient from '@accounts/graphql-client'
import ApolloClient from 'apollo-boost'
import { Query, Mutation, ApolloProvider } from 'react-apollo'
import gql from 'graphql-tag'
import ReactDOM from 'react-dom'
const apolloClient = new ApolloClient({
request: async (operation) => {
const tokens = await accountsClient.getTokens()
if (tokens) {
operation.setContext({
headers: {
'accounts-access-token': tokens.accessToken
}
})
}
},
uri: 'http://localhost:4000/graphql'
})
const accountsGraphQL = new GraphQLClient({ graphQLClient: apolloClient })
const accountsClient = new AccountsClient({}, accountsGraphQL)
const accountsPassword = new AccountsClientPassword(accountsClient)
const ALL_POSTS_QUERY = gql`
query AllPosts {
allPosts {
id
title
content
author {
username
}
}
}
`
const ADD_POST_MUTATION = gql`
mutation AddPost($title: String, $content: String) {
addPost(title: $title, content: $content)
}
`
class App extends Component {
state = {
credentials: {
username: '',
password: ''
},
newPost: {
title: '',
content: ''
},
user: null
}
componentDidMount() {
return this.updateUserState()
}
async updateUserState() {
const tokens = await accountsClient.refreshSession()
if (tokens) {
const user = await accountsGraphQL.getUser()
await this.setState({ user })
}
}
renderAllPosts() {
return (
<Query query={ALL_POSTS_QUERY}>
{({ data, loading, error }) => {
if (loading) {
return <p>Loading...</p>
}
if (error) {
return <p>Error: {error}</p>
}
return data.allPosts.map((post: any) => (
<li>
<p>{post.title}</p>
<p>{post.content}</p>
<p>Author: {post.author.username}</p>
</li>
))
}}
</Query>
)
}
renderLoginRegister() {
return (
<fieldset>
<legend>Login - Register</legend>
<form>
<p>
<label>
Username:
<input
value={this.state.credentials.username}
onChange={(e) =>
this.setState({
credentials: {
...this.state.credentials,
username: e.target.value
}
})
}
/>
</label>
</p>
<p>
<label>
Password:
<input
value={this.state.credentials.password}
onChange={(e) =>
this.setState({
credentials: {
...this.state.credentials,
password: e.target.value
}
})
}
/>
</label>
</p>
<p>
<button
onClick={(e) => {
e.preventDefault()
accountsPassword
.login({
password: this.state.credentials.password,
user: {
username: this.state.credentials.username
}
})
.then(() => this.updateUserState())
}}
>
Login
</button>
</p>
<p>
<button
onClick={(e) => {
e.preventDefault()
accountsPassword
.createUser({
password: this.state.credentials.password,
username: this.state.credentials.username
})
.then(() => {
alert('Please login with your new credentials')
this.setState({
credentials: {
username: '',
password: ''
}
})
})
}}
>
Register
</button>
</p>
</form>
</fieldset>
)
}
renderAddPost() {
return (
<Mutation mutation={ADD_POST_MUTATION}>
{(addPost) => {
return (
<fieldset>
<legend>Add Post</legend>
<form>
<p>
<label>
Title:
<input
value={this.state.newPost.title}
onChange={(e) =>
this.setState({
newPost: {
...this.state.newPost,
title: e.target.value
}
})
}
/>
</label>
</p>
<p>
<label>
Content:
<input
value={this.state.newPost.content}
onChange={(e) =>
this.setState({
newPost: {
...this.state.newPost,
content: e.target.value
}
})
}
/>
</label>
</p>
<p>
<input
type="submit"
onClick={(e) => {
e.preventDefault()
addPost({
variables: {
title: this.state.newPost.title,
content: this.state.newPost.content
}
})
}}
/>
</p>
</form>
</fieldset>
)
}}
</Mutation>
)
}
render() {
return (
<div>
<h2>All Posts</h2>
{this.renderAllPosts()}
{!this.state.user && this.renderLoginRegister()}
{this.state.user && this.renderAddPost()}
</div>
)
}
}
ReactDOM.render(
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>,
document.getElementById('root')
)