From dd9b234dd0aa2e07e8b3d74576d76b9acdf1455c Mon Sep 17 00:00:00 2001 From: David Cox Date: Thu, 10 Oct 2024 16:19:49 -0700 Subject: [PATCH] feat(client): add method for removeUserFromGroup --- client/src/index.ts | 46 +++++++++++ client/src/rbac/index.ts | 1 + client/src/rbac/removeUserFromGroup.ts | 105 +++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 client/src/rbac/removeUserFromGroup.ts diff --git a/client/src/index.ts b/client/src/index.ts index bacf55b..24f7ae8 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -9,6 +9,7 @@ import { import { assignPermissionToRole, addUserToGroup, + removeUserFromGroup, createRole, createPermission, getParentRole, @@ -506,6 +507,51 @@ export class KeyHippo { } } + /** + * Removes a user from a group in the RBAC system. + * + * @param userId - The unique identifier of the user to be removed from the group. + * @param groupId - The unique identifier of the group from which the user is being removed. + * @returns A Promise that resolves when the removal is successful. + * + * User removal process: + * 1. Validates the input parameters. + * 2. Checks if the user is a member of the specified group. + * 3. Removes the user-group association from the user_groups table within the RBAC schema. + * 4. Handles potential cascading effects (e.g., removing associated roles or permissions). + * + * Usage example: + * ```typescript + * try { + * await keyHippo.removeUserFromGroup('user123', 'group456'); + * console.log('User successfully removed from group'); + * } catch (error) { + * console.error('Failed to remove user from group:', error); + * } + * ``` + + * + * Security implications: + * - Ensure that only authorized administrators can remove users from groups. + * - Removing a user from a group affects their access rights within the application. + * - Consider implementing an audit log for user-group removals. + * + * Error handling: + * - Throws an error if the user or group does not exist. + * - Throws an error if the user is not a member of the specified group. + * - Throws an error if there are database connectivity issues. + */ + async removeUserFromGroup(userId: UserId, groupId: GroupId): Promise { + try { + await removeUserFromGroup(this.supabase, userId, groupId, this.logger); + } catch (error) { + this.logger.error( + `Error removing user from group: ${error instanceof Error ? error.message : String(error)}`, + ); + throw error; + } + } + /** * Retrieves the parent role of a specified role in the RBAC system. * diff --git a/client/src/rbac/index.ts b/client/src/rbac/index.ts index b9de6ca..5b1a0ea 100644 --- a/client/src/rbac/index.ts +++ b/client/src/rbac/index.ts @@ -1,4 +1,5 @@ export * from "./addUserToGroup"; +export * from "./removeUserFromGroup"; export * from "./assignPermissionToRole"; export * from "./createPermission"; export * from "./createRole"; diff --git a/client/src/rbac/removeUserFromGroup.ts b/client/src/rbac/removeUserFromGroup.ts new file mode 100644 index 0000000..7c651ce --- /dev/null +++ b/client/src/rbac/removeUserFromGroup.ts @@ -0,0 +1,105 @@ +import { SupabaseClient } from "@supabase/supabase-js"; +import { Logger, UserId, GroupId } from "../types"; +import { logDebug, logInfo, logError, createDatabaseError } from "../utils"; + +/** + * Logs the attempt to remove a user from a group. + * @param logger - The logger instance used for logging. + * @param userId - The ID of the user being removed from the group. + * @param groupId - The ID of the group from which the user is being removed. + */ +const logRemoveUserFromGroupAttempt = ( + logger: Logger, + userId: UserId, + groupId: GroupId, +): void => { + logDebug( + logger, + `Attempting to remove user '${userId}' from group '${groupId}'`, + ); +}; + +/** + * Executes the RPC call to remove a user from a group in the database. + * @param supabase - The Supabase client instance. + * @param userId - The ID of the user being removed from the group. + * @param groupId - The ID of the group from which the user is being removed. + * @throws Error if the RPC call fails to remove the user from the group. + */ +const executeRemoveUserFromGroupRpc = async ( + supabase: SupabaseClient, + userId: UserId, + groupId: GroupId, +): Promise => { + const { error } = await supabase + .schema("keyhippo_rbac") + .rpc("remove_user_from_group", { + p_user_id: userId, + p_group_id: groupId, + }); + + if (error) { + throw new Error(`Remove User from Group RPC failed: ${error.message}`); + } +}; + +/** + * Logs the successful removal of a user from a group. + * @param logger - The logger instance used for logging. + * @param userId - The ID of the user removed from the group. + * @param groupId - The ID of the group from which the user was removed. + */ +const logRemoveUserFromGroupSuccess = ( + logger: Logger, + userId: UserId, + groupId: GroupId, +): void => { + logInfo( + logger, + `Successfully removed user '${userId}' from group '${groupId}'`, + ); +}; + +/** + * Handles errors that occur during the remove user from group process. + * @param error - The error encountered during the process. + * @param logger - The logger instance used for logging errors. + * @param userId - The ID of the user involved in the failed operation. + * @param groupId - The ID of the group involved in the failed operation. + * @throws ApplicationError encapsulating the original error with a descriptive message. + */ +const handleRemoveUserFromGroupError = ( + error: unknown, + logger: Logger, + userId: UserId, + groupId: GroupId, +): never => { + logError( + logger, + `Failed to remove user '${userId}' from group '${groupId}'. Error: ${error}`, + ); + throw createDatabaseError(`Failed to remove user from group: ${error}`); +}; + +/** + * Removes a user from a group. + * @param supabase - The Supabase client. + * @param userId - The ID of the user to be removed from the group. + * @param groupId - The ID of the group from which the user is being removed. + * @param logger - The logger instance. + * @throws ApplicationError if the process fails. + */ +export const removeUserFromGroup = async ( + supabase: SupabaseClient, + userId: UserId, + groupId: GroupId, + logger: Logger, +): Promise => { + try { + logRemoveUserFromGroupAttempt(logger, userId, groupId); + await executeRemoveUserFromGroupRpc(supabase, userId, groupId); + logRemoveUserFromGroupSuccess(logger, userId, groupId); + } catch (error) { + handleRemoveUserFromGroupError(error, logger, userId, groupId); + } +};