-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exercise 4 #11
Exercise 4 #11
Conversation
… with uuid and userid in token conformant to jwt spec, fixed some api definitions
@Systems-Development-and-Frameworks/countryroads, there is a bug in our PR and we are not able to add you as a Code Reviewer. I have to manually go to our PR and make a code review. |
Hi... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review on 12.06.2020 at commit "tests fixed(1f1eb90)"
The idea of using graph-shield to check the login (passwordIsValid) and to check the user has voted or not (isPostUpvoted) is quite good 👍 . And you can create 2 classes Post and User to give the code a more cool look. 😉
✔️ ⭐ For an implemented signup feature.
❌ ⭐ For a software-tested signup feature.
✔️ ⭐ For password validation.
✔️ ⭐ For unique email address validation.
✔️ ⭐ For not having security issues according to instructions.
✔️ ⭐ For an implemented login feature using JWT.
❌ ⭐ For a software-tested login feature.
✔️ ⭐ For assigning the authenticated user to a post in write and upvote mutations.
✔️ ⭐ For requesting a review and reviewing another team's PR according to the instructions.
❌ ⭐ For a successful rebase on homework/main.
const userById = await dataSources.usersApi.getUserById(parent.author); | ||
return { | ||
name: parent.author | ||
name: userById.name | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you return a name like this then it's not possible to nest your queries 'indefinitely':
{
posts {
title
votes
author {
name
posts {
title
}
}
}
}
as result, the author's posts will be an empty list.
Here you should return to a User instead of the author's name.
const userById = await dataSources.usersApi.getUserById(parent.author); | |
return { | |
name: parent.author | |
name: userById.name | |
}; | |
return await dataSources.usersApi.getUserById(parent.author); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's 100% correct!
backend/src/tests/tests.test.js
Outdated
it("creates new post - not authenticated", async () => { | ||
const { | ||
data: { write } | ||
} = await mutate({ mutation: WRITE_POST, variables: { post: { title: "Title1", author: { name: "Max" } } } }); | ||
errors:[ error ] | ||
} = await mutate({ mutation: WRITE_POST, variables: { post: { title: "Title1"}}}); | ||
|
||
expect(write).toEqual({title: "Title1", author: {name: "Max"}}); | ||
expect(error.message).toEqual('Not Authorised!'); | ||
}); | ||
|
||
it("throws error if author creates post with already existent title", async () => { | ||
it("creates new post - authenticated", async () => { | ||
reqMock.headers = {authorization: 'Bearer ' + atanas_auth_token}; | ||
|
||
const { | ||
data: {write} | ||
} = await mutate({ mutation: WRITE_POST, variables: { post: { title: "Title1"} } }); | ||
|
||
expect(write).toEqual({title: "Title1", author: {name: "Atanas"}}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1/2⭐ For assigning the authenticated user to a post in write mutations.
it("upvotes a post - not authorized", async () => { | ||
const { | ||
errors:[ error ] | ||
} = await mutate({ mutation: UPVOTE_POST, variables: { title: "Atanas's Post 1" } }); | ||
|
||
expect(error.message).toEqual('Not Authorised!'); | ||
}); | ||
|
||
it("upvotes a post - authorized", async () => { | ||
reqMock.headers = {authorization: 'Bearer ' + atanas_auth_token}; | ||
|
||
const { | ||
data: {upvote} | ||
} = await mutate({ mutation: UPVOTE_POST, variables: { title: "The Nothing", voter: { name: "Max" } } }); | ||
expect(upvote).toMatchObject({ title: "The Nothing", votes: 1 }); | ||
data:{upvote}, | ||
} = await mutate({ mutation: UPVOTE_POST, variables: { title: "Atanas's Post 1" } }); | ||
|
||
expect(upvote).toMatchObject({ title: "Atanas's Post 1", votes: 1 }); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1/2⭐ For assigning the authenticated user to a post in upvote mutations.
async login(parent, args, { dataSources }) { | ||
const user = await dataSources.usersApi.getUserByEmail(args.email); | ||
const token = await dataSources.authApi.createToken(user.id); | ||
return token; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⭐ For an implemented login feature using JWT.
async signup(parent, args, { dataSources }) { | ||
const createdUser = await dataSources.usersApi.createUser(args.name, args.email, args.password); | ||
const token = await dataSources.authApi.createToken(createdUser.id); | ||
return token; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⭐ For an implemented signup feature.
Mutation: { | ||
"*": deny, | ||
signup: and( | ||
not(emailIsTaken, new UserInputError("A user with this email already exists.")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⭐ For unique email address validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, didn't know that not
has a second error
argument.
"*": deny, | ||
signup: and( | ||
not(emailIsTaken, new UserInputError("A user with this email already exists.")), | ||
not(passwordIsTooShort, new UserInputError("The password must be at least 8 characters long.")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⭐ For password validation.
const context = require('../context'); | ||
const contextPipeline = () => context({ req: reqMock, res: resMock }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could easily use:
const contextPipeline = () => {}
when you want to authenticate a user, just use:
server.context = () => ({token: {uId: user_id}});
instead of creating these variables:
reqMock = { headers: {} };
resMock = {};
let userIds = [uuidv4(), uuidv4()];
mike_auth_token = await authApi.createToken(userIds[0]);
atanas_auth_token = await authApi.createToken(userIds[1]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, mocking the context
is fine and it's how I implemented the tests in my reference solution. However, this is copied from my example code for people who really want to test the requests headers for whatever reason.
const GET_POSTS = gql` | ||
query { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try testing the following query and you will see that the author's posts list will be empty.
const GET_POSTS = gql`
query {
posts {
title
author {
name
posts {
title
author {
name
}
}
}
}
}`;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree with @yiimnta here. You achieved 7/10 🌠 well done!
⭐ For an implemented signup
feature.
❌ For a software-tested signup
feature.
⭐ For password validation.
⭐ For unique email address validation.
⭐ For not having security issues according to instructions.
❌ For an implemented login
feature using JWT.
⭐ For a software-tested login
feature.
⭐ For assigning the authenticated user to a post in write
and upvote
mutations.
⭐ For requesting a review and reviewing another team's PR according to the instructions.
❌ For a successful rebase on homework/main
.
What got me the most is that you have put so much logic (also data-validations) into the permissions layer.
Regarding the rebase look here:
See my suggestions below.
let token = req.headers.authorization || ''; | ||
token = token.replace('Bearer ', ''); | ||
try { | ||
const decodedToken = jwt.verify(token, process.env.JWT_SECRET); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest to import a file here wich require('dotenv')
and exports a constant JWT_SECRET
. You would avoid a process.env.JWT_SECRET
being undefined here, regardless of the order how files are loaded.
} | ||
|
||
async createToken(userId) { | ||
return jwt.sign({ uId: userId }, process.env.JWT_SECRET, { algorithm: 'HS256', expiresIn: '1h' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expiresIn
👍
async getUser(name) { | ||
return this.users.find(user => user.name === name); | ||
async getUserById(id) { | ||
return this.users.find(user => user.id == id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return this.users.find(user => user.id == id); | |
return this.users.find(user => user.id === id); |
const context = require('../context'); | ||
const contextPipeline = () => context({ req: reqMock, res: resMock }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, mocking the context
is fine and it's how I implemented the tests in my reference solution. However, this is copied from my example code for people who really want to test the requests headers for whatever reason.
Co-authored-by: Robert Schäfer <git@roschaefer.de>
Co-authored-by: Robert Schäfer <git@roschaefer.de>
We have reviewed this PR