From 617b60adca7a98acecb80d4f6d2588086e8a21df Mon Sep 17 00:00:00 2001 From: Francesco Manzali Date: Tue, 19 Oct 2021 09:26:35 +0200 Subject: [PATCH 1/4] Fix parseJWT import --- api/src/lib/auth.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/api/src/lib/auth.js b/api/src/lib/auth.js index d0ba80d..4d05d90 100644 --- a/api/src/lib/auth.js +++ b/api/src/lib/auth.js @@ -1,8 +1,5 @@ -import { - AuthenticationError, - ForbiddenError, - parseJWT, -} from '@redwoodjs/graphql-server' +import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' +import { parseJWT } from '@redwoodjs/api' import { logger } from 'src/lib/logger' /** * getCurrentUser returns the user information together with From eaeb432fe6bcef0c84346045004b438c08c19f42 Mon Sep 17 00:00:00 2001 From: Francesco Manzali Date: Tue, 19 Oct 2021 10:43:42 +0200 Subject: [PATCH 2/4] Fix import on useForm + validate e-mail --- web/src/pages/ContactPage/ContactPage.js | 136 ++++++++++++----------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/web/src/pages/ContactPage/ContactPage.js b/web/src/pages/ContactPage/ContactPage.js index 65a066e..f91cded 100644 --- a/web/src/pages/ContactPage/ContactPage.js +++ b/web/src/pages/ContactPage/ContactPage.js @@ -6,10 +6,10 @@ import { FieldError, Label, FormError, + useForm, } from '@redwoodjs/forms' import { useMutation } from '@redwoodjs/web' import { toast, Toaster } from '@redwoodjs/web/toast' -import { useForm } from 'react-hook-form' const CREATE_CONTACT = gql` mutation CreateContactMutation($input: CreateContactInput!) { @@ -34,75 +34,81 @@ const ContactPage = () => { console.log(data) } - return <> - -
- + + - - - + + + + - - - + + + - - - + + + - - Save - - - ; + + Save + + + + ) } export default ContactPage From d3dc6a107952b7bf5fd005445349df137d489363 Mon Sep 17 00:00:00 2001 From: Francesco Manzali Date: Tue, 19 Oct 2021 10:55:29 +0200 Subject: [PATCH 3/4] Fix auth test --- .../requireAuth/requireAuth.test.js | 4 +- api/src/lib/auth.js | 72 +++++++------------ 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/api/src/directives/requireAuth/requireAuth.test.js b/api/src/directives/requireAuth/requireAuth.test.js index 0f01aa3..75453ef 100644 --- a/api/src/directives/requireAuth/requireAuth.test.js +++ b/api/src/directives/requireAuth/requireAuth.test.js @@ -11,7 +11,9 @@ describe('requireAuth directive', () => { it('requireAuth has stub implementation. Should not throw when current user', () => { // If you want to set values in context, pass it through e.g. // mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }}) - const mockExecution = mockRedwoodDirective(requireAuth, { context: {} }) + const mockExecution = mockRedwoodDirective(requireAuth, { + context: { currentUser: { id: 1, name: 'Bob McBobFace' } }, + }) expect(mockExecution).not.toThrowError() }) diff --git a/api/src/lib/auth.js b/api/src/lib/auth.js index 4d05d90..e2a3c19 100644 --- a/api/src/lib/auth.js +++ b/api/src/lib/auth.js @@ -32,66 +32,44 @@ export const getCurrentUser = async ( return { ...decoded } } -/** - * The user is authenticated if there is a currentUser in the context - * - * @returns {boolean} - If the currentUser is authenticated - */ -export const isAuthenticated = () => { - return !!context.currentUser -} - -/** - * Checks if the currentUser is authenticated (and assigned one of the given roles) - * - * @param {string= | string[]=} roles - A single role or list of roles to check if the user belongs to - * - * @returns {boolean} - Returns true if the currentUser is authenticated (and assigned one of the given roles) - */ -export const hasRole = ({ roles }) => { - if (!isAuthenticated()) { - return false - } - - if ( - typeof roles !== 'undefined' && - typeof roles === 'string' && - context.currentUser.roles?.includes(roles) - ) { - return true - } - - if ( - typeof roles !== 'undefined' && - Array.isArray(roles) && - context.currentUser.roles?.some((r) => roles.includes(r)) - ) { - return true - } - - return false -} - +//Taken from: https://redwoodjs.com/cookbook/role-based-access-control-rbac#how-to-code-examples /** * Use requireAuth in your services to check that a user is logged in, * whether or not they are assigned a role, and optionally raise an * error if they're not. * - * @param {string= | string[]=} roles - A single role or list of roles to check if the user belongs to + * @param {string=} roles - An optional role or list of roles + * @param {array=} roles - An optional list of roles + + * @example * - * @returns - If the currentUser is authenticated (and assigned one of the given roles) + * // checks if currentUser is authenticated + * requireAuth() * - * @throws {AuthenticationError} - If the currentUser is not authenticated - * @throws {ForbiddenError} If the currentUser is not allowed due to role permissions + * @example * - * @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples + * // checks if currentUser is authenticated and assigned one of the given roles + * requireAuth({ roles: 'editor' }) + * requireAuth({ roles: ['admin', 'author', 'publisher'] }) */ export const requireAuth = ({ roles } = {}) => { - if (!isAuthenticated) { + if (!context.currentUser) { throw new AuthenticationError("You don't have permission to do that.") } - if (!hasRole({ roles })) { + if ( + typeof roles !== 'undefined' && + typeof roles === 'string' && + !context.currentUser.roles?.includes(roles) + ) { + throw new ForbiddenError("You don't have access to do that.") + } + + if ( + typeof roles !== 'undefined' && + Array.isArray(roles) && + !context.currentUser.roles?.some((role) => roles.includes(role)) + ) { throw new ForbiddenError("You don't have access to do that.") } } From 6d99694b736c8d6d5908abfbeab6234a57b7d270 Mon Sep 17 00:00:00 2001 From: David Price Date: Tue, 19 Oct 2021 16:37:31 -0700 Subject: [PATCH 4/4] udpate auth.js to current template --- api/src/lib/auth.js | 82 +++++++++++++++++++++++++++++---------------- web/package.json | 4 +-- yarn.lock | 8 ++--- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/api/src/lib/auth.js b/api/src/lib/auth.js index e2a3c19..2b35523 100644 --- a/api/src/lib/auth.js +++ b/api/src/lib/auth.js @@ -1,12 +1,13 @@ -import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' import { parseJWT } from '@redwoodjs/api' +import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' import { logger } from 'src/lib/logger' + /** * getCurrentUser returns the user information together with * an optional collection of roles used by requireAuth() to check * if the user is authenticated or has role-based access * - * @param decoded - The decoded access token containing user info and JWT claims like `sub` + * @param decoded - The decoded access token containing user info and JWT claims like `sub`. Note could be null. * @param { token, SupportedAuthTypes type } - The access token itself as well as the auth provider type * @param { APIGatewayEvent event, Context context } - An object which contains information from the invoker * such as headers and cookies, and the context information about the invocation such as IP Address @@ -32,44 +33,69 @@ export const getCurrentUser = async ( return { ...decoded } } -//Taken from: https://redwoodjs.com/cookbook/role-based-access-control-rbac#how-to-code-examples +/** + * The user is authenticated if there is a currentUser in the context + * + * @returns {boolean} - If the currentUser is authenticated + */ +export const isAuthenticated = () => { + return !!context.currentUser +} + +/** + * When checking role membership, roles can be a single value, a list, or none. + * You can use Prisma enums too (if you're using them for roles), just import your enum type from `@prisma/client` + */ + +/** + * Checks if the currentUser is authenticated (and assigned one of the given roles) + * + * @param roles: AllowedRoles - Checks if the currentUser is assigned one of these roles + * + * @returns {boolean} - Returns true if the currentUser is logged in and assigned one of the given roles, + * or when no roles are provided to check against. Otherwise returns false. + */ +export const hasRole = ({ roles }) => { + if (!isAuthenticated()) { + return false + } + + if (roles) { + if (Array.isArray(roles)) { + return context.currentUser.roles?.some((r) => roles.includes(r)) + } + + if (typeof roles === 'string') { + return context.currentUser.roles?.includes(roles) + } + + // roles not found + return false + } + + return true +} + /** * Use requireAuth in your services to check that a user is logged in, * whether or not they are assigned a role, and optionally raise an * error if they're not. * - * @param {string=} roles - An optional role or list of roles - * @param {array=} roles - An optional list of roles - - * @example + * @param roles: AllowedRoles - When checking role membership, these roles grant access. * - * // checks if currentUser is authenticated - * requireAuth() + * @returns - If the currentUser is authenticated (and assigned one of the given roles) * - * @example + * @throws {AuthenticationError} - If the currentUser is not authenticated + * @throws {ForbiddenError} If the currentUser is not allowed due to role permissions * - * // checks if currentUser is authenticated and assigned one of the given roles - * requireAuth({ roles: 'editor' }) - * requireAuth({ roles: ['admin', 'author', 'publisher'] }) + * @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples */ -export const requireAuth = ({ roles } = {}) => { - if (!context.currentUser) { +export const requireAuth = ({ roles }) => { + if (!isAuthenticated()) { throw new AuthenticationError("You don't have permission to do that.") } - if ( - typeof roles !== 'undefined' && - typeof roles === 'string' && - !context.currentUser.roles?.includes(roles) - ) { - throw new ForbiddenError("You don't have access to do that.") - } - - if ( - typeof roles !== 'undefined' && - Array.isArray(roles) && - !context.currentUser.roles?.some((role) => roles.includes(role)) - ) { + if (!hasRole({ roles })) { throw new ForbiddenError("You don't have access to do that.") } } diff --git a/web/package.json b/web/package.json index 0dddd0f..ed1e9a4 100644 --- a/web/package.json +++ b/web/package.json @@ -17,7 +17,7 @@ "@redwoodjs/forms": "^0.37.4", "@redwoodjs/router": "^0.37.4", "@redwoodjs/web": "^0.37.4", - "netlify-identity-widget": "^1.9.1", + "netlify-identity-widget": "^1.9.2", "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2" @@ -28,4 +28,4 @@ "postcss-loader": "^6.1.1", "tailwindcss": "^2.2.7" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 5d31593..9af085f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13764,10 +13764,10 @@ nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== -netlify-identity-widget@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/netlify-identity-widget/-/netlify-identity-widget-1.9.1.tgz#9e716c4b92b9f0cc041074eb86fc962f35295b46" - integrity sha512-9oIWjwUSdRk3SkREcZNjZaVuDDx9T/wSIXZNQsQeY4qoXic/FiXVEGgu2RU3IuA4OI3L2652xY1o+PpS03Ugaw== +netlify-identity-widget@^1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/netlify-identity-widget/-/netlify-identity-widget-1.9.2.tgz#4339c9155fc4c2570ae3ddd61825d952b574b02e" + integrity sha512-IbS1JHhs7BflCCvp3C9f6tmNSZqbyBhZ4Gs5+Qxt4IlPybTOVv0PqJ4TAsA7uxh1R+oXOAmk0OOMAkEaPYeCtA== new-github-issue-url@0.2.1: version "0.2.1"