summaryrefslogtreecommitdiffstats
path: root/stepped-solutions/38
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-08-15 14:47:25 -0400
committerWes Bos <wesbos@gmail.com>2018-08-15 14:47:25 -0400
commit124f227a8235e08bddf8376e0b3d01d33c4414f3 (patch)
tree1ea23e539e42c35ca0b609e2615c043aa6fc4219 /stepped-solutions/38
parentee09df7dee16a7b44711159c60c54cc9365be35e (diff)
Permissions is doneeee
Diffstat (limited to 'stepped-solutions/38')
-rwxr-xr-xstepped-solutions/38/backend/src/resolvers/Mutation.js215
-rwxr-xr-xstepped-solutions/38/frontend/components/DeleteItem.js50
2 files changed, 265 insertions, 0 deletions
diff --git a/stepped-solutions/38/backend/src/resolvers/Mutation.js b/stepped-solutions/38/backend/src/resolvers/Mutation.js
new file mode 100755
index 0000000..b48be39
--- /dev/null
+++ b/stepped-solutions/38/backend/src/resolvers/Mutation.js
@@ -0,0 +1,215 @@
+const bcrypt = require('bcryptjs');
+const jwt = require('jsonwebtoken');
+const { randomBytes } = require('crypto');
+const { promisify } = require('util');
+const { transport, makeANiceEmail } = require('../mail');
+const { hasPermission } = require('../utils');
+
+const Mutations = {
+ async createItem(parent, args, ctx, info) {
+ if (!ctx.request.userId) {
+ throw new Error('You must be logged in to do that!');
+ }
+
+ const item = await ctx.db.mutation.createItem(
+ {
+ data: {
+ // This is how to create a relationship between the Item and the User
+ user: {
+ connect: {
+ id: ctx.request.userId,
+ },
+ },
+ ...args,
+ },
+ },
+ info
+ );
+
+ console.log(item);
+
+ return item;
+ },
+ updateItem(parent, args, ctx, info) {
+ // first take a copy of the updates
+ const updates = { ...args };
+ // remove the ID from the updates
+ delete updates.id;
+ // run the update method
+ return ctx.db.mutation.updateItem(
+ {
+ data: updates,
+ where: {
+ id: args.id,
+ },
+ },
+ info
+ );
+ },
+ async deleteItem(parent, args, ctx, info) {
+ const where = { id: args.id };
+ // 1. find the item
+ const item = await ctx.db.query.item({ where }, `{ id title user { id }}`);
+ // 2. Check if they own that item, or have the permissions
+ const ownsItem = item.user.id === ctx.request.userId;
+ const hasPermissions = ctx.request.user.permissions.some(permission =>
+ ['ADMIN', 'ITEMDELETE'].includes(permission)
+ );
+
+ if (!ownsItem && hasPermissions) {
+ throw new Error("You don't have permission to do that!");
+ }
+
+ // 3. Delete it!
+ return ctx.db.mutation.deleteItem({ where }, info);
+ },
+ async signup(parent, args, ctx, info) {
+ // lowercase their email
+ args.email = args.email.toLowerCase();
+ // hash their password
+ const password = await bcrypt.hash(args.password, 10);
+ // create the user in the database
+ const user = await ctx.db.mutation.createUser(
+ {
+ data: {
+ ...args,
+ password,
+ permissions: { set: ['USER'] },
+ },
+ },
+ info
+ );
+ // create the JWT token for them
+ const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
+ // We set the jwt as a cookie on the response
+ ctx.response.cookie('token', token, {
+ httpOnly: true,
+ maxAge: 1000 * 60 * 60 * 24 * 365, // 1 year cookie
+ });
+ // Finalllllly we return the user to the browser
+ return user;
+ },
+ async signin(parent, { email, password }, ctx, info) {
+ // 1. check if there is a user with that email
+ const user = await ctx.db.query.user({ where: { email } });
+ if (!user) {
+ throw new Error(`No such user found for email ${email}`);
+ }
+ // 2. Check if their password is correct
+ const valid = await bcrypt.compare(password, user.password);
+ if (!valid) {
+ throw new Error('Invalid Password!');
+ }
+ // 3. generate the JWT Token
+ const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
+ // 4. Set the cookie with the token
+ ctx.response.cookie('token', token, {
+ httpOnly: true,
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ });
+ // 5. Return the user
+ return user;
+ },
+ signout(parent, args, ctx, info) {
+ ctx.response.clearCookie('token');
+ return { message: 'Goodbye!' };
+ },
+ async requestReset(parent, args, ctx, info) {
+ // 1. Check if this is a real user
+ const user = await ctx.db.query.user({ where: { email: args.email } });
+ if (!user) {
+ throw new Error(`No such user found for email ${args.email}`);
+ }
+ // 2. Set a reset token and expiry on that user
+ const randomBytesPromiseified = promisify(randomBytes);
+ const resetToken = (await randomBytesPromiseified(20)).toString('hex');
+ const resetTokenExpiry = Date.now() + 3600000; // 1 hour from now
+ const res = await ctx.db.mutation.updateUser({
+ where: { email: args.email },
+ data: { resetToken, resetTokenExpiry },
+ });
+ // 3. Email them that reset token
+ const mailRes = await transport.sendMail({
+ from: 'wes@wesbos.com',
+ to: user.email,
+ subject: 'Your Password Reset Token',
+ html: makeANiceEmail(`Your Password Reset Token is here!
+ \n\n
+ <a href="${process.env
+ .FRONTEND_URL}/reset?resetToken=${resetToken}">Click Here to Reset</a>`),
+ });
+
+ // 4. Return the message
+ return { message: 'Thanks!' };
+ },
+ async resetPassword(parent, args, ctx, info) {
+ // 1. check if the passwords match
+ if (args.password !== args.confirmPassword) {
+ throw new Error("Yo Passwords don't match!");
+ }
+ // 2. check if its a legit reset token
+ // 3. Check if its expired
+ const [user] = await ctx.db.query.users({
+ where: {
+ resetToken: args.resetToken,
+ resetTokenExpiry_gte: Date.now() - 3600000,
+ },
+ });
+ if (!user) {
+ throw new Error('This token is either invalid or expired!');
+ }
+ // 4. Hash their new password
+ const password = await bcrypt.hash(args.password, 10);
+ // 5. Save the new password to the user and remove old resetToken fields
+ const updatedUser = await ctx.db.mutation.updateUser({
+ where: { email: user.email },
+ data: {
+ password,
+ resetToken: null,
+ resetTokenExpiry: null,
+ },
+ });
+ // 6. Generate JWT
+ const token = jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET);
+ // 7. Set the JWT cookie
+ ctx.response.cookie('token', token, {
+ httpOnly: true,
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ });
+ // 8. return the new user
+ return updatedUser;
+ },
+ async updatePermissions(parent, args, ctx, info) {
+ // 1. Check if they are logged in
+ if (!ctx.request.userId) {
+ throw new Error('You must be logged in!');
+ }
+ // 2. Query the current user
+ const currentUser = await ctx.db.query.user(
+ {
+ where: {
+ id: ctx.request.userId,
+ },
+ },
+ info
+ );
+ // 3. Check if they have permissions to do this
+ hasPermission(currentUser, ['ADMIN', 'PERMISSIONUPDATE']);
+ // 4. Update the permissions
+ return ctx.db.mutation.updateUser(
+ {
+ data: {
+ permissions: {
+ set: args.permissions,
+ },
+ },
+ where: {
+ id: args.userId,
+ },
+ },
+ info
+ );
+ },
+};
+
+module.exports = Mutations;
diff --git a/stepped-solutions/38/frontend/components/DeleteItem.js b/stepped-solutions/38/frontend/components/DeleteItem.js
new file mode 100755
index 0000000..e5e4752
--- /dev/null
+++ b/stepped-solutions/38/frontend/components/DeleteItem.js
@@ -0,0 +1,50 @@
+import React, { Component } from 'react';
+import { Mutation } from 'react-apollo';
+import gql from 'graphql-tag';
+import { ALL_ITEMS_QUERY } from './Items';
+
+const DELETE_ITEM_MUTATION = gql`
+ mutation DELETE_ITEM_MUTATION($id: ID!) {
+ deleteItem(id: $id) {
+ id
+ }
+ }
+`;
+
+class DeleteItem extends Component {
+ update = (cache, payload) => {
+ // manually update the cache on the client, so it matches the server
+ // 1. Read the cache for the items we want
+ const data = cache.readQuery({ query: ALL_ITEMS_QUERY });
+ console.log(data, payload);
+ // 2. Filter the deleted itemout of the page
+ data.items = data.items.filter(item => item.id !== payload.data.deleteItem.id);
+ // 3. Put the items back!
+ cache.writeQuery({ query: ALL_ITEMS_QUERY, data });
+ };
+ render() {
+ return (
+ <Mutation
+ mutation={DELETE_ITEM_MUTATION}
+ variables={{ id: this.props.id }}
+ update={this.update}
+ >
+ {(deleteItem, { error }) => (
+ <button
+ onClick={() => {
+ if (confirm('Are you sure you want to delete this item?')) {
+ deleteItem().catch(err => {
+ alert(err.message);
+ });
+ }
+ }}
+ >
+ {this.props.children}
+ </button>
+ )}
+ </Mutation>
+ );
+ }
+}
+
+export default DeleteItem;