executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
extension-user-approvalApproval-based user management.
Independent GitHub snapshot · Automated classification · Original documentation governs
User approval extension for Caffeine AI.
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.
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 func removeApproval(state : UserApprovalState, user : Principal);
public type UserApprovalInfo = {
principal : Principal;
status : ApprovalStatus;
};
public func listApprovals(state : UserApprovalState) : [UserApprovalInfo];
}
include MixinUserApproval(accessControlState, approvalState) MUST be placed in main.mo, not in a custom mixin file. Declare approvalState at actor top level and pass it into the mixin. The mixin provides these public endpoints automatically:
isCallerApproved()requestApproval()setApproval(user, status)removeApproval(user)listApprovals()Keep approvalState in scope for custom approval guards in app-specific endpoints.
Do NOT redeclare any of the mixin-provided functions.
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.AccessControlState;
include MixinAuthorization(accessControlState, null);
let approvalState : UserApproval.UserApprovalState;
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");
// };
// };
};
The migration chain head — UserApproval.initState depends on the access-control state, so compute it in order inside the migration body:
import AccessControl "mo:caffeineai-authorization/access-control";
import UserApproval "mo:caffeineai-user-approval/approval";
module {
type NewActor = {
accessControlState : AccessControl.AccessControlState;
approvalState : UserApproval.UserApprovalState;
};
public func migration(_old : {}) : NewActor {
let accessControlState = AccessControl.initState();
{
accessControlState;
approvalState = UserApproval.initState(accessControlState);
};
};
};
On initState, existing admins are automatically approved. All other users are pending.
The anonymous principal is never approved: requestApproval and setApproval trap for it, and isCallerApproved returns false. isCallerApproved also returns false, rather than trapping, for a signed-in caller that has not registered yet.
IMPORTANT: Apply the right authorization and/or approval check to each custom public function.
Approval-based user management:
isCallerApproved)requestApproval). Show it only to signed-in users; it traps for the anonymous principalFor admin users, provide a dashboard to:
listApprovals)setApproval). A rejected user cannot request approval againremoveApproval), so they can request approval againgetCallerUserRole and assignCallerUserRole)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;
// Delete a user's approval entry, so they can request approval again (admin only) removeApproval(user: Principal): Promise;
// Assign a role to a user (admin only) assignCallerUserRole(user: Principal, role: UserRole): Promise;
// Get current role for a specific user getCallerUserRole(): Promise;