

@emilien
- Member Since
- Aug 25, 2024
- Lessons Completed
- 46
- Comments Contributed
- 14
- Hours Watched
- 10.86
Recent Activity
Here's what @emilien has been up to this past year
-
Upvoted comment That's awesome! I'm happy to hear you're making progress with...
-
Replied to Yeah, you'll want the HttpContext here to be optional. Then,...
Yeah I finally tried to recreate the
GetOrganizationAbilities
(GetWorkspaceAbilities in my case) action for another ressource. So I created aGetBoardAbilities
, then I created a new middleware that checks for the user's permissions for the specific board :@inject() export default class BoardPermissionsMiddleware { async handle(ctx: HttpContext, next: NextFn) { const user = ctx.auth.use('web').user! try { const boardId = ctx.params.boardId if (!boardId) { return await next() } const board = await Board.findOrFail(boardId) const boardUser = await db .from('task_board_users') .where('board_id', boardId) .where('user_id', user.id) .select('role_id as roleId') .first() if (!boardUser) { ctx.session.flash('errorBag', "Vous n'êtes pas membre de ce tableau") return ctx.response.redirect().back() } const boardRoleId = boardUser.roleId console.log(boardRoleId) ctx.can.board = GetBoardAbilities.handle({ roleId: boardRoleId }) ctx.inertia.share({ activeBoard: new BoardDto(board), can: ctx.can, }) } catch (error) { console.error(error) return ctx.response.redirect().toRoute('boards.index') } return await next() } }
Copied!This middleware is used just after the
WorkspaceMiddleware
and if there is noboardId
params in the route, it just callsnext()
so it doesn't do anything on routes unrelated to boards. Then I can access the abilities in the backend viactx.can.board
and in the frontend via inertia shared propscan.board.destroy
for example. Not sure if it is the best way to do it or the cleanest, but it looks easier to understand for me as I found your way to do ACL pretty easy to understand too. -
Upvoted comment Yeah, you'll want the HttpContext here to be optional. Then,...
-
Replied to Hi Emilien!No worries at all!! 😊Yeah, things spiral here quickly...
I went with the row level with model hooks. Works well, I had to configure the app to make the HttpContext available in the models via the Async Local Storage thing.
@afterFind() static async attachPermissions(workspace: Workspace) { const ctx = HttpContext.getOrFail() const user = ctx.auth.use('web').user! const userWorkspaceRole = await db .from('task_workspace_users') .where('workspace_id', workspace.id) .where('user_id', user.id) .first() let roleId if (userWorkspaceRole) { roleId = userWorkspaceRole.role_id } workspace.can = { read: roleId === Roles.ADMIN || roleId === Roles.MEMBER, edit: roleId === Roles.ADMIN, delete: roleId === Roles.ADMIN, createBoards: roleId === Roles.ADMIN || roleId === Roles.MEMBER, removeMembers: roleId === Roles.ADMIN, } }
Copied!My only problem is that my tests which are using models with those hooks are not working anymore since the HttpContext is not available outside an HTTP request.
test('should remove a user from workspace with multiple members', async ({ assert }) => { const users = await UserFactory.createMany(2) const workspace = await WorkspaceFactory.create() const board = await BoardFactory.merge({ workspaceId: workspace.id }).create() const column = await ColumnFactory.merge({ boardId: board.id, workspaceId: workspace.id, }).create() await TaskFactory.merge({ columnId: column.id, workspaceId: workspace.id }).createMany(3) await workspace.related('users').attach(users.map((user) => user.id)) await RemoveWorkspaceUser.handle({ workspace, removeUserId: users[0].id, }) await workspace.refresh() await workspace.load('users') assert.equal(workspace.users.length, 1) assert.equal(workspace.users[0].id, users[1].id) const workspaceExists = await Workspace.find(workspace.id) assert.isNotNull(workspaceExists) })
Copied! -
Completed lesson Generating A Unique Movie Slug With Model Hooks
-
Commented on post Rolling Our Own Authorization Access Controls
Hi again Tom !
Sorry I ask a lot of questions 😅
I was wondering how I could implement a more granular permission system where a user can have different roles at different levels - for example, being an admin at the org level but only a member for a course and an admin for another one within that workspace. First I would have to create another action
GetCourseAbilities
and create "rules" like we did forGetOrganizationAbilities
.My problem is to understand how to modify the middleware to get the roleId associated to the course. Since we only get the roleId for the workspace. Should I create a middleware that specifically check the
ctx.params
to retrieve thecourse.id
and find the user's role like we do in theworkspace_middleware
?I should have tested it before asking you, but I have analysis paralysis and I'm not sure this is the best and cleaner idea.
Also I was wondering how we can check more things than just the user's role for an organization's ability. For example, canceling an invite. An admin user can cancel all invites, but a member user cannot remove invites, unless he's the one who made the invite. Tried multiple things here but none of them is working. I think I didn't understand very well how things work 😅 -
Completed lesson Removing an Organization User
-
Replied to Hi emilien!I actually haven't used the i18n AdonisJS package...
Hi Tom,
I actually managed to make it work by reporting an custom error for the specific field with a rule, and use this rule inside my translation file. Not sure if it's the best solution, but it works for now 😅
-
Upvoted comment Hi emilien!I actually haven't used the i18n AdonisJS package...
-
Commented on post Sending an Invitation to Join Our Organization
Hi Tom,
I was wondering how we can set custom error messages for specific validation rules inside the validator. In cases where the user is already a member of the organization, the default message is "The email has already been taken." However, I’d like to display a custom message instead, such as "This user is already part of this organization."
I'm trying to use i18n to translate it in french but I can find the right field + rule conbination as specified in the AdonisJS doc
Thanks !
-
Completed lesson Forgot Password & Password Reset
-
Completed lesson Accepting an Organization Invitation
-
Completed lesson Adding the Organization Invite User Interface
-
Completed lesson Sending an Invitation to Join Our Organization
-
Completed lesson Listing Current Organization Members
-
Upvoted comment Oh nice! Yeah, this looks spot on, nicely done emilien! Thank...
-
Replied to Awesome, glad to hear that helped clear things up! Ah, please...
I've tried to make a flowchart of the middleware and how it uses both, GetActive and SetActive actions. It is related to my use case (workspace and not organization) though, but I think the logic stays the same.
I don't know if you'll find it useful, but just in case, here it is. Let me know if there are any errors or inconsistencies. It was really interesting to do and taught me a lot, maybe I could help with this kind of thing if needed, don't hesitate, it would be a pleasure. -
Upvoted comment Awesome, glad to hear that helped clear things up! Ah, please...
-
Completed lesson Introducing, Installing, & Configuring Bouncer
-
Replied to Alrighty, I think I see what's going on! Where your project ...
Of course this helps, could have spent a lot of time on this. That is so obvious with your explanation… Thanks a lot. Oh I feel so dumb, I created a route with a parameter that I don't use…
I think I also need to take time to document the flow of setting an active workspace etc and understand it much better. Completely forgot that the middleware kind of "block" access to workspaces that the user is not a member of.
Thanks again Tom ! Loving your tutorials and your fast answers to comments ! 😊
-
Upvoted comment Alrighty, I think I see what's going on! Where your project ...
-
Replied to Hi emilien! I think I'm following correctly, but let me know...
Hi Tom, thanks for your reply. After reading my first comment, I think I was not clear at all 😅
I also use a cookie for the active workspace.I'll try to explain my problem a little bit better :
user A is member and admin of workspace A → id = 1
user B is member and admin of workspace B → id = 2
Everything is fine for the moment, according to my ACL if the user A goes to '/workspaces/1' he can see the page. Same for user B if '/workspaces/2'. This URL will trigger the
show()
method in WorkspacesController. You won't see the use of the ACL in this method in the repo cause I did not commited, but I used it like you do in your tutorial.My problem is that if for some reasons, the user B goes to '/workspaces/1', he can still view the workspace, however he's not a member of it. I reproduced the same logic as you did to get the active workspace
I probably have missed something or there is a point that I do not understand. Maybe you already pointed it out in your answer, and if that's the case, I'm sorry. But I'm a bit confused, it's the first time that I have to work with permissions.
-
Upvoted comment Hi emilien! I think I'm following correctly, but let me know...
-
Commented on post Applying Our Server-Side Authorization Checks
Hi Tom, I'm currently building a task management application, and I find it pretty similiar to your project in termes of "architecture". I have a workspace model that could be assimilated to your organization model. So when it comes to setting the active workspace feature I did the same thing as you did, same for ACL, abilities etc. So basically if the authenticated user is not a member of a workspace, it doesn't appear in his list of workspaces and then cannot set it as active.
But I'm facing a problem when I tried to get access to a workspace page, that the authenticated user is not a member of by putting the workspace id in the URL. Since my user has another active workspace in which his role is ADMIN, then, the user can access the workspace even if he's not a member of it.
I was asking myself if you managed it, if I missed something, or if it's "normal" and I need to modify how I check the user's role and stop using the active workspace ?
Sorry, I think this is not very clear, pretty hard to explain it since I'm a bit lost in all of those concepts 😅 -
Completed lesson Rolling Our Own Authorization Access Controls
-
Completed lesson Creating the Settings Shell
-
Completed lesson Loading A Movie Using Route Parameters
-
Completed lesson Refreshing Partial Page Data
-
Completed lesson Showing A Course's Details
-
Completed lesson Editing the Active Organization
-
Completed lesson Creating A UseResourceActions Composable
-
Completed lesson Switching Between Organizations
-
Completed lesson The Form Dialog Component & Adding Organizations
-
Completed lesson Listing & Creating Difficulties
-
Completed lesson Logging In An Existing User
-
Commented on post Setting & Loading the User's Active Organization
Hi Tom, could you explain why you are using actions over services ? Would be great to have your advice on this 😊
-
Completed lesson Understanding Our Database Schema
-
Completed lesson Listing the User's Organizations
-
Upvoted comment Looks good, thank you emilien! I'll try and take a look into...
-
Replied to Yeah, if you could create an issue that'd be great, thank you...
Issue created. First time I write an issue, let me know if something is missing.
-
Upvoted comment Yeah, if you could create an issue that'd be great, thank you...
-
Completed lesson Setting Up TailwindCSS, Shadcn-Vue, and Automatic Component Imports
-
Completed lesson Setting & Loading the User's Active Organization
-
Completed lesson Completing Our AppLayout & Navigation Bar
-
Replied to Awesome! Glad to hear everything is working for ya now! 😊
Sorry to bother you again. Some of my model's columns are not "generated" in the dto, do you have any idea why ? Must be pretty difficult without context, do you want me to open an issue on github ?
-
Upvoted comment Awesome! Glad to hear everything is working for ya now! 😊
-
Replied to Hi emilien! Terribly sorry about that! I had missed normalizing...
No problem, don't apologize. Updated your package and everything is working fine now, thanks a lot !
-
Completed lesson Splitting Our Routes Between Auth & Web
-
Completed lesson Creating A Toast Message Manager
-
Commented on post Typing Lucid Models in Inertia with DTOs
Hi Tom, tried using your package to generate dtos, but it gives weird names to my dtos files, it take my whoe path, for example : c_users_emilien_documents_dev_sojatask_app_models_board.ts
Am I doing something wrong ? -
Completed lesson Typing Lucid Models in Inertia with DTOs
-
Completed lesson Creating A Lucid Model Mixin for our Organization Relationship
-
Completed lesson Logging In Users & Displaying Exceptions
-
Completed lesson Common useForm Methods & Options
-
Completed lesson Using Eager and Lazy Loading to Load A Movie's Writer and Director
-
Completed lesson Defining Our Lucid Models & Relationships
-
Completed lesson A Deep Dive Into Relationship CRUD with Models
-
Completed lesson Listing A Director's Movies with Relationship Existence Queries
-
Completed lesson The useForm Helper
-
Completed lesson Form Validation & Displaying Errors
-
Completed lesson Inertia Form Basics
-
Completed lesson Form Validation Error Handling
-
Completed lesson Default Layouts & Overwriting the Default Layout
-
Completed lesson Creating A Layout
-
Completed lesson Creating A Login Form and Validator
-
Completed lesson Displaying Validation Errors and Validating from our Request
-
Completed lesson Validating Form Data with VineJS
-
Completed lesson Defining Our Models
-
Completed lesson The Link Component and Programmatic Linking
-
Completed lesson Linking Between Pages & Page State Flow
-
Account created Welcome to Adocasts, @emilien!