Hello! yes I have built it all custom roles / permissions / team access
model User {
id Int @id @default(autoincrement())
currentTeam Team? @relation(fields: [currentTeamId], references: [id])
currentTeamId Int?
currentRole TeamMemberRole? @relation(fields: [currentRoleId], references: [id])
currentRoleId Int?
email String @unique
issuer String @unique
teamMembers TeamMember[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TeamMember {
id Int @id @default(autoincrement())
team Team? @relation(fields: [teamId], references: [id])
teamId Int?
teamMemberRole TeamMemberRole? @relation(fields: [teamMemberRoleId], references: [id])
teamMemberRoleId Int?
}
model Team {
id Int @id @default(autoincrement())
name String
teamMemberRoles TeamMemberRole[]
teamMembers TeamMember[]
}
model TeamMemberRole {
id Int @id @default(autoincrement())
name String @unique
permissions TeamMemberPermission[]
teamMembers TeamMember[]
teams Team[]
users User[]
teamInvitations TeamInvitation[]
uneditable Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TeamMemberPermission {
id Int @id @default(autoincrement())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamRoles TeamMemberRole[]
}
This is my authfile
import { TeamMemberLogActionTypes } from '@prisma/client'
import { AuthenticationError, ForbiddenError } from '@redwoodjs/api'
import { context } from '@redwoodjs/api/dist/globalContext'
import { db } from './db'
export const getCurrentUser = async (authToken, b) => {
const mAdmin = new Magic(process.env.MAGICLINK_SECRET)
const {
email,
publicAddress,
issuer,
} = await mAdmin.users
.getMetadataByToken(authToken)
.catch((e) => console.error(e))
if (!email || !publicAddress || !issuer) {
throw new AuthenticationError('Uh, oh')
}
const account = await db.user.findOne({
where: { issuer },
include: {
currentRole: true,
currentTeam: true,
teamMembers: true,
onboarding: true,
},
})
if (!account) {
return await db.user.create({
data: {
email,
publicAddress,
issuer,
},
include: {
currentRole: true,
currentTeam: true,
teamMembers: true,
},
})
}
const currentUser = {
auth: {
issuer: account.issuer,
publicAddress: account.publicAddress,
email: account.email,
},
user: {
userId: account.id,l,
firstName: account.firstName,
secondName: account.secondName,
updatedAt: account.updatedAt,
createdAt: account.createdAt,
},
currentRole: account.currentRole,
currentTeam: account.currentTeam,
roles: null,
teamMembers: account.teamMembers,
}
if (currentUser.currentRole?.id) {
const findTeamRole = await db.teamMemberRole.findOne({
where: { id: currentUser.currentRole?.id },
include: {
permissions: true,
},
})
const currentPermissions = findTeamRole.permissions.map((o) => o.name)
return { ...currentUser, roles: currentPermissions }
} else {
return currentUser
}
}
// Use this function in your services to check that a user is logged in, and
// optionally raise an error if they're not.
export const requireAuth = ({ permission }) => {
if (!context.currentUser) {
throw new AuthenticationError('You are not logged into a user.')
}
if (
typeof permission !== 'undefined' &&
!context.currentUser.roles?.includes(permission)
) {
throw new ForbiddenError(
`You role does not have access rights to '${permission}'`
)
}
}
export const checkTeam = (teamId) => {
if (teamId !== context.currentUser.currentTeam.id) {
throw new ForbiddenError("You don't have access to that team!")
}
}
I can explain more if needed in a bit of a pinch of time right now 
this was for a custom solution with Magic.Links
how to make it dynamic? you attach and detatch the roles as needed from the teamMember. One user can have multiple be part of multiple teams and the connecting table in the middle is teamMember