back
loading skill details...
Approval-based user management.
User Approval
User approval extension for Caffeine AI.
Overview
This skill adds approval-based user management. Users request access; admins approve or reject. Approved users gain access to protected features.
Prerequisite: You must follow extension-authorization first, as this integration depends on it.
Backend
Module API
The prefabricated module mo:caffeineai-user-approval/approval provides low-level approval state management. Do not modify it.
import AccessControl "mo:caffeineai-authorization/access-control";
module {
public type ApprovalStatus = {
#approved;
#rejected;
#pending;
};
public type UserApprovalState = { /* internal state */ };
public func initState(accessControlState: AccessControl.AccessControlState) : UserApprovalState;
public func isApproved(state : UserApprovalState, caller : Principal) : Bool;
public func requestApproval(state : UserApprovalState, caller : Principal);
public func setApproval(state : UserApprovalState, user : Principal, approval : ApprovalStatus);
public type UserApprovalInfo = {
principal : Principal;
status : ApprovalStatus;
};
public func listApprovals(state : UserApprovalState) : [UserApprovalInfo];
}
## Setup in main.mo
`include MixinUserApproval(accessControlState, approvalState)` MUST be placed in `main.mo`, not in a custom mixin file. Create `approvalState` at actor top level with `UserApproval.initState(accessControlState)` and pass it into the mixin. The mixin provides these public endpoints automatically:
- `isCallerApproved()`
- `requestApproval()`
- `setApproval(user, status)`
- `listApprovals()`
Keep `approvalState` in scope for custom approval guards in app-specific endpoints.
Do NOT redeclare any of the mixin-provided functions.
```motoko filepath=src/backend/main.mo
import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";
import MixinUserApproval "mo:caffeineai-user-approval/MixinUserApproval";
import UserApproval "mo:caffeineai-user-approval/approval";
import Runtime "mo:core/Runtime";
actor {
let accessControlState = AccessControl.initState();
include MixinAuthorization(accessControlState, null);
let approvalState = UserApproval.initState(accessControlState);
include MixinUserApproval(accessControlState, approvalState);
// Example custom endpoint with an approval guard:
// public shared ({ caller }) func protectedFeature() : async () {
// if (not (UserApproval.isApproved(approvalState, caller) or AccessControl.hasPermission(accessControlState, caller, #admin))) {
// Runtime.trap("Unauthorized: Only approved users can perform this action");
// };
// };
};
On initState, existing admins are automatically approved. All other users are pending.
IMPORTANT: Apply the right authorization and/or approval check to each custom public function.
Frontend
Approval-based user management:
User Approval Flow
Check approval status (isCallerApproved)
If not approved, show option to request approval (requestApproval)
Block access to main features for non-approved users
Admins have access to all features of the application
Display approval status clearly in the UI
Admin Dashboard
For admin users, provide a dashboard to:
List all users with their approval status (listApprovals)
Approve or reject users (setApproval)
View and assign user roles (using getCallerUserRole and assignCallerUserRole)
Backend Integration
The backend already implements the following functionality.
The full interface can be found in
// Check if current user is approved, admins are always approved
isCallerApproved(): Promise;
// Submit approval request
requestApproval(): Promise;
// Get all users and their approval status (admin only)
listApprovals(): Promise<Array>;
// Approve or reject a user (admin only)
setApproval(user: Principal, status: ApprovalStatus): Promise;
// Assign a role to a user (admin only)
assignCallerUserRole(user: Principal, role: UserRole): Promise;
// Get current role for a specific user
getCallerUserRole(): Promise;don't have the plugin yet? install it then click "run inline in claude" again.