From 2d238d6407d59e0ba45b9389b04fa21ec0bf2d70 Mon Sep 17 00:00:00 2001 From: tkt <37575408+tktcorporation@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:25:27 +0000 Subject: [PATCH] :sparkles: finish https://redwoodjs.com/docs/tutorial/chapter7/rbac#fixing-the-hasrole-function --- .../migration.sql | 2 + api/db/schema.prisma | 1 + api/src/lib/{auth.js => auth.ts} | 38 +++++++++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 api/db/migrations/20221010121520_add_roles_to_user/migration.sql rename api/src/lib/{auth.js => auth.ts} (70%) diff --git a/api/db/migrations/20221010121520_add_roles_to_user/migration.sql b/api/db/migrations/20221010121520_add_roles_to_user/migration.sql new file mode 100644 index 000000000..a2189ab35 --- /dev/null +++ b/api/db/migrations/20221010121520_add_roles_to_user/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "roles" TEXT NOT NULL DEFAULT 'moderator'; diff --git a/api/db/schema.prisma b/api/db/schema.prisma index ab28f23c6..9946a618a 100644 --- a/api/db/schema.prisma +++ b/api/db/schema.prisma @@ -32,6 +32,7 @@ model User { salt String resetToken String? resetTokenExpiresAt DateTime? + roles String @default("moderator") } model Comment { diff --git a/api/src/lib/auth.js b/api/src/lib/auth.ts similarity index 70% rename from api/src/lib/auth.js rename to api/src/lib/auth.ts index a2a6b6ed9..2a82891f3 100644 --- a/api/src/lib/auth.js +++ b/api/src/lib/auth.ts @@ -1,3 +1,4 @@ +import type { Decoded } from '@redwoodjs/api' import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' import { db } from './db' @@ -19,10 +20,14 @@ import { db } from './db' * fields to the `select` object below once you've decided they are safe to be * seen if someone were to open the Web Inspector in their browser. */ -export const getCurrentUser = async (session) => { +export const getCurrentUser = async (session: Decoded) => { + if (!session || typeof session.id !== 'number') { + throw new Error('Invalid session') + } + return await db.user.findUnique({ where: { id: session.id }, - select: { id: true, email: true }, + select: { id: true, email: true, roles: true }, }) } @@ -31,19 +36,25 @@ export const getCurrentUser = async (session) => { * * @returns {boolean} - If the currentUser is authenticated */ -export const isAuthenticated = () => { +export const isAuthenticated = (): boolean => { 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` + */ +type AllowedRoles = string | string[] | undefined + /** * 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 + * @param roles: {@link 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) => { +export const hasRole = (roles: AllowedRoles): boolean => { if (!isAuthenticated()) { return false } @@ -54,9 +65,6 @@ export const hasRole = (roles) => { if (typeof currentUserRoles === 'string') { // roles to check is a string, currentUser.roles is a string return currentUserRoles === roles - } else if (Array.isArray(currentUserRoles)) { - // roles to check is a string, currentUser.roles is an array - return currentUserRoles?.some((allowedRole) => roles === allowedRole) } } @@ -66,11 +74,9 @@ export const hasRole = (roles) => { return currentUserRoles?.some((allowedRole) => roles.includes(allowedRole) ) - } else if (typeof context.currentUser.roles === 'string') { + } else if (typeof currentUserRoles === 'string') { // roles to check is an array, currentUser.roles is a string - return roles.some( - (allowedRole) => context.currentUser?.roles === allowedRole - ) + return roles.some((allowedRole) => currentUserRoles === allowedRole) } } @@ -83,16 +89,16 @@ export const hasRole = (roles) => { * whether or not they are assigned a role, and optionally raise an * error if they're not. * - * @param roles: AllowedRoles - When checking role membership, these roles grant access. + * @param roles: {@link AllowedRoles} - When checking role membership, these roles grant access. * * @returns - If the currentUser is authenticated (and assigned one of the given roles) * - * @throws {AuthenticationError} - If the currentUser is not authenticated - * @throws {ForbiddenError} If the currentUser is not allowed due to role permissions + * @throws {@link AuthenticationError} - If the currentUser is not authenticated + * @throws {@link ForbiddenError} If the currentUser is not allowed due to role permissions * * @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples */ -export const requireAuth = ({ roles }) => { +export const requireAuth = ({ roles }: { roles?: AllowedRoles } = {}) => { if (!isAuthenticated()) { throw new AuthenticationError("You don't have permission to do that.") }