Skip to content
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

2 Questions #3

Closed
cloudcompute opened this issue May 25, 2021 · 12 comments
Closed

2 Questions #3

cloudcompute opened this issue May 25, 2021 · 12 comments

Comments

@cloudcompute
Copy link

How can I use the Sendgrid, in other words, what portion of the code do I need to modify to accomplish a third-party mailing service?

Is the permission functionality fully implemented? Great if you can give an example demonstrating how to decorate the resolvers with Role and/Or Permissions?

Is this code prduction-ready?

@cloudcompute
Copy link
Author

Probably we need to set the mailerURL env variable.

@jasonraimondi
Copy link
Owner

Hey, so this project is more or less my playground slash boilerplate project for if I wanted to start a production GraphQL API and Next.js client. I've used it to try out different ORM's, starting as a TypeORM project, and ultimately switching to PrismaJS.

I would definitely consider this project almost production ready, it was built with the intention that you'd be able to have different release versions (dev, staging, production) and has background job support using bulljs and email support using Nunjucks and MJML.

You are correct, if you wanted to change to use Sendgrid's SMTP server, you would use the environment variable MAILER_URL as exampled here.

The permission functionality is as implemented as it can be, without having any set rules for who can do what (what user has what role, with what permission). You would expand upon the configured permissions by creating custom roles and permissions, created for your application.

Feel free to fork this project and make it your own.

@cloudcompute
Copy link
Author

cloudcompute commented May 25, 2021

Thanks a lot for the reply. I am glad to know that the code can be used in production. Kindly give a real-world example that help explain expand upon the configured permissions (which source code files) by creating custom roles and permissions.

For example 3 roles: Admin, Author, Reader, Moderator

Permissions:
Author can write an article but cannot see the list of all users.
List of users are made available to admin only.
Readers can read/view the articles and create comments on them.
Moderate can edit the comments but cannot author the articles.

Could you pl. write the sample code for it?

Thanks

@cloudcompute
Copy link
Author

I had a look at the code and got to see the redis-based bull queue.

@jasonraimondi
Copy link
Owner

This would more or less be just rows in our database.

These pivot tables would be taken care of by the ORM, but it is worth noting this is what your database structure ultimately looks like. See here where I am seeding a user and role called "overlord" (ultimately just an admin).

Basically, the user can have many roles. When executing mutations (since this is mostly a graphql API, in rest, the equivalent would be POST), you would have guards in place that protected the routes. Here is how Nest.js handles guards in REST, and here is the relevant GraphQL sections.

Users table:

id username
1 admin-user
2 author-user
3 reader-user
4 moderator-user

Roles table:

id role
1 Admin
2 Author
3 Reader
4 Moderator

A permission table:

id permission
1 article.read
2 article.write
3 users.read
4 users.write
5 comments.read
6 comments.write

The role_permission pivot table

role_id permission_id
1 1
1 2
1 3
1 4
1 5
1 6
2 1
2 2
2 5
2 6
3 1
3 5
4 1
4 5
4 6

user_roles table

user_id role_id
1 1
2 2
3 3
4 4

@cloudcompute
Copy link
Author

cloudcompute commented May 26, 2021

@jasonraimondi

Thank you for the detailed explanation. Yesterday, I had a look at the seed-data. I almost got it but the word "overlord" made me wonder what it is referring to. Now I got to know you meant "admin"

As I have seen developers using jwt-decode at the client side. They fetch the current date/time from the client's machine. But the token was created using the server-side's date/time. What if the date/time of the client and server machine have a difference of let's say several hours or may be days. Am I wrong somewhere in perceiving this concept?

You are using RxJs at the client side. Is this library being used with an assumption that we should manage the entire state of the client app using RxJS.. instead of using standard libraries like Redux, MobX?

With Regards

@jasonraimondi
Copy link
Owner

The timestamps are stored as epoch timestamps which are independent of timezone. Epoch timestamps (new Date().getTIme() / 1000 or Date.now()) when decoded by the client browsers will automatically convert to the browser time.

const date = new Date();
const timestamp = date.getTime();
const epochTimestamp = date.getTime() / 1000; // divide by 1000 to get timestamp in seconds (typical epoch)

// then on the browser
const date = new Date(timestampMilliSeconds);

Rxjs is optional, you are free to use it if you'd like, otherwise it will be tree shaken out.

@cloudcompute
Copy link
Author

Wonderful!

I have started configuring the code. I may come up with few questions. Hope you'd reply.

Thanks.

@jasonraimondi
Copy link
Owner

Sounds good, will do what I can

@cloudcompute
Copy link
Author

cloudcompute commented May 26, 2021

This quality of this source code is excellent.

Reference:
https://stackoverflow.com/questions/42581421/where-does-javascript-get-a-new-date-from/42581497#comment72295334_42581488

If we look at the answers stated in the URL above, it tells us that the current date and time are retrieved from the client's local environment/machine. Actually, when I pointed out a concern regarding the difference between server and client date/time, I wanted to indicate that if someone gets hold of an expired access token, he can adjust the local clock and fools the system and make it believe that the token has not yet expired.

const isAuthenticated = (() => {
if (!state.accessToken || !decodedToken) return false;
const expiresAt = new Date(decodedToken.exp * 1000);
return new Date() < expiresAt; //here the browser is returning the local date/time
})();

..

In bcryptjs.hash() the value of salt rounds is only "1". Better if we keep this value somewhere around 8 or 10, probably more security. Do you agree to this?

..

CSRF functionality (fastify.register) is there at the API end. Then sending the csrfToken in a hidden field:

The csrfToken's value is not getting set/stored anywhere at the server end. Is this functionality fully implemented? Do we need to send this hidden field in all the React forms? Right now, you are using this hidden in the server-side templating system (Nunjucks).

..

I need to add support for error handling that can handle db and graphql errors in dev and prod modes. The main difference is that in dev mode, we should be able to see the Stack trace in the browser. Whereas in production mode, some error code is displayed asking the end user to notify this error code to the system admin. Something like that.. any hints how to implement it in nestjs.

Sincere regards

@jasonraimondi
Copy link
Owner

jasonraimondi commented May 26, 2021

if someone gets hold of an expired access token, he can adjust the local clock and fools the system

This is not a security concern for the API because the access tokens are signed JWT with a secret password set in the JwtStrategy. If anyone was to manipulate this data and attempt to extend the duration, the token would fail verification, and the API would return an Unauthorized 401 (if the endpoint/query/mutation requires auth).

This is not really a security concern for the client either, because as soon as the client attempts to do anything with that token (like retrieving secret data that only the token holder should see) the API will fail, and the frontend will receive 401s and basically enter error states.

In bcryptjs.hash() the value of salt rounds is only "1"

Where do you see this value set as 1? Our hash function is set to 10 salt rounds by default as seen here in utils/passwords.ts

CSRF & Right now, you are using this hidden in the server-side templating system (Nunjucks).

So this project has been through a few phases, and one of those phases was implementing my oauth2-server package. When that was the case, I was using nunjucks templating for the oauth2 login page as well as the scope acceptance pages ("Do you allow this project to edit your projects, read your projects etc"). With these server rendered pages, the CSRF token was necessary.

The CSRF token should also be set for the register and login endpoints (basically... wherever a token is not required to POST). Meaning, if you have a form open to the world (login, register, password reset), it should be protected by CSRF. This is not the case for this project currently. I have not implemented anything to send the CSRF token to the Next.js client yet.

It is worth noting that Nunjucks is still used for email templating for the server (along with MJML). You can see the email templates here

I need to add support for error handling that can handle db and graphql errors in dev and prod modes

The graphql configuration along with formatError configuration is located here in the config/graphql.ts. You could just add some magic in that function to check the ENV.isProduction value.

@cloudcompute
Copy link
Author

The 'wip' branch was 1 commit ahead than the 'master' branch. Therefore I cloned the 'wip' branch. Over there, it is 1 (and not 10): Here is the link:

I compared both the branches, Hope I am not losing any more changes while using the 'wip'.
https://github.com/jasonraimondi/scratchy/compare/wip

With these server rendered pages, the CSRF token was necessary.

I will use the SPA (create-react-app) instead of Next. Looks like, in the SPAs, CSRF protection is not necessary. I will read more about it and try to implement it of my own. Taking care of Security is among the most difficult.

Thank you once again for putting me on the track.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants