Overview
The revoke_access transition allows patients to immediately revoke a healthcare provider’s access to a medical record before the natural expiration time. This is useful if the patient changes their mind, suspects misuse, or no longer wants to share their medical information with a specific provider.
Function Signature
async transition revoke_access(
access_token: field
) -> Future
Source: main.leo:386-390
Parameters
The cryptographic access token that was generated by grant_access. This is the same token that was encoded in the QR code given to the doctor. Only the patient who originally granted the access can revoke it.
Returns
Async finalization that:
- Retrieves the existing
AccessGrant from the access_grants mapping
- Verifies the caller is the patient who granted the access
- Updates the grant with
is_revoked: true
- Marks the token as invalid in
access_token_valid mapping
The transaction succeeds if the caller is the grant owner, otherwise it fails.
Validation Rules
Patient Authorization
let grant: AccessGrant = access_grants.get(access_token);
assert_eq(grant.patient, caller);
Only the patient who originally granted the access can revoke it. The transaction fails if:
- The caller is not the patient in the grant
- The access token doesn’t exist
Revocation Process
let revoked_grant: AccessGrant = AccessGrant {
patient: grant.patient,
doctor: grant.doctor,
record_id: grant.record_id,
access_token: grant.access_token,
granted_at: grant.granted_at,
expires_at: grant.expires_at,
is_revoked: true, // Changed to true
};
access_grants.set(access_token, revoked_grant);
access_token_valid.set(access_token, false);
The revocation:
- Preserves all original grant data
- Sets
is_revoked: true
- Marks token as invalid
- Takes effect immediately
Code Example
import { AleoClient } from '@aleo/sdk';
// Initialize Aleo client
const client = new AleoClient();
// Get the access token to revoke
// (from patient's local database of active grants)
const accessToken = await getActiveAccessToken({
doctorAddress: 'aleo1doctor...',
recordId: 'record_xyz'
});
try {
// Revoke the access
await client.execute(
'salud_health_records_v6.aleo',
'revoke_access',
[accessToken]
);
console.log('Access revoked successfully');
// Update local database
await updateLocalGrant(accessToken, { status: 'revoked' });
// Optionally notify the doctor
await notifyDoctor({
message: 'Patient has revoked access to the medical record',
recordId: 'record_xyz',
revokedAt: new Date()
});
} catch (error) {
if (error.message.includes('assert_eq')) {
console.error('You are not authorized to revoke this access');
} else if (error.message.includes('get')) {
console.error('Access token not found');
} else {
console.error('Failed to revoke access:', error);
}
}
Patient Dashboard Integration
// Display all active access grants to patient
interface ActiveGrant {
token: string;
doctor: string;
recordId: string;
recordType: number;
grantedAt: number;
expiresAt: number;
isRevoked: boolean;
}
class PatientAccessManager {
private client: AleoClient;
private activeGrants: Map<string, ActiveGrant>;
// List all active grants
async listActiveGrants(): Promise<ActiveGrant[]> {
// Query from local database or on-chain
const grants = await this.queryPatientGrants();
return grants.filter(g =>
!g.isRevoked &&
this.getCurrentBlockHeight() <= g.expiresAt
);
}
// Revoke a specific grant
async revokeGrant(accessToken: string): Promise<void> {
const grant = this.activeGrants.get(accessToken);
if (!grant) {
throw new Error('Grant not found');
}
// Confirm with patient
const confirmed = await this.confirmRevocation({
doctor: grant.doctor,
recordType: this.getRecordTypeName(grant.recordType),
grantedAt: new Date(grant.grantedAt * 15000) // block to time
});
if (!confirmed) return;
// Execute revocation on-chain
await this.client.execute(
'salud_health_records_v6.aleo',
'revoke_access',
[accessToken]
);
// Update local state
grant.isRevoked = true;
this.activeGrants.set(accessToken, grant);
// Show confirmation
this.showNotification('Access revoked successfully');
}
// Revoke all grants for a specific record
async revokeAllForRecord(recordId: string): Promise<void> {
const grants = Array.from(this.activeGrants.values())
.filter(g => g.recordId === recordId && !g.isRevoked);
if (grants.length === 0) {
console.log('No active grants for this record');
return;
}
const confirmed = await this.confirmBulkRevocation(
grants.length,
recordId
);
if (!confirmed) return;
// Revoke all grants in parallel
const revocations = grants.map(grant =>
this.client.execute(
'salud_health_records_v6.aleo',
'revoke_access',
[grant.token]
)
);
await Promise.all(revocations);
console.log(`Revoked ${grants.length} access grants`);
}
}
UI Component Example
// React component for managing access grants
import React, { useState, useEffect } from 'react';
function AccessGrantsManager() {
const [grants, setGrants] = useState<ActiveGrant[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadActiveGrants();
}, []);
async function loadActiveGrants() {
const activeGrants = await patientManager.listActiveGrants();
setGrants(activeGrants);
}
async function handleRevoke(token: string) {
if (!confirm('Are you sure you want to revoke this access?')) {
return;
}
setLoading(true);
try {
await patientManager.revokeGrant(token);
await loadActiveGrants(); // Refresh list
alert('Access revoked successfully');
} catch (error) {
alert('Failed to revoke access: ' + error.message);
} finally {
setLoading(false);
}
}
return (
<div className="access-grants">
<h2>Active Access Grants</h2>
{grants.length === 0 ? (
<p>No active access grants</p>
) : (
<table>
<thead>
<tr>
<th>Doctor</th>
<th>Record Type</th>
<th>Granted</th>
<th>Expires</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{grants.map(grant => (
<tr key={grant.token}>
<td>{shortenAddress(grant.doctor)}</td>
<td>{getRecordTypeName(grant.recordType)}</td>
<td>{formatDate(grant.grantedAt)}</td>
<td>{formatDate(grant.expiresAt)}</td>
<td>
<button
onClick={() => handleRevoke(grant.token)}
disabled={loading}
>
Revoke
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
Revocation takes effect as soon as the transaction is confirmed:
// Before revocation
try {
await client.execute('verify_access', [token, doctor, recordId]);
console.log('Access valid'); // ✓ Success
} catch {
console.log('Access denied');
}
// Execute revocation
await client.execute('revoke_access', [token]);
// After revocation (same block or next block)
try {
await client.execute('verify_access', [token, doctor, recordId]);
console.log('Access valid');
} catch {
console.log('Access denied'); // ✗ Fails: is_revoked = true
}
On-Chain State Changes
AccessGrant Update
let revoked_grant: AccessGrant = AccessGrant {
patient: grant.patient,
doctor: grant.doctor,
record_id: grant.record_id,
access_token: grant.access_token,
granted_at: grant.granted_at,
expires_at: grant.expires_at,
is_revoked: true, // ← Changed
};
access_grants.set(access_token, revoked_grant);
The original grant data is preserved with only is_revoked changed to true.
Token Invalidation
access_token_valid.set(access_token, false);
Marks the token as invalid for quick validity checks.
Error Cases
Not the Grant Owner: Transaction fails if caller is not the patient who granted accessError: Assertion failed: assert_eq(grant.patient, caller)
Only the original patient can revoke access. Doctors cannot revoke their own access.
Token Not Found: Transaction fails if the access token doesn’t existError: Mapping get failed: access token not found
The token may never have existed or there’s a typo in the token value.
Already Revoked: You can revoke an already-revoked token without error. The function will update the grant again with is_revoked: true, which is idempotent.
Use Cases
1. Emergency Revocation
// Patient suspects unauthorized access
async function emergencyRevokeAll() {
const allGrants = await getAllActiveGrants();
console.log(`Revoking ${allGrants.length} active grants...`);
for (const grant of allGrants) {
await client.execute('revoke_access', [grant.token]);
}
console.log('All access revoked');
}
2. Selective Revocation
// Revoke access from specific doctor
async function revokeDoctorAccess(doctorAddress: string) {
const doctorGrants = await getGrantsForDoctor(doctorAddress);
for (const grant of doctorGrants) {
await client.execute('revoke_access', [grant.token]);
}
console.log(`Revoked access for ${doctorAddress}`);
}
3. Scheduled Revocation
// Revoke after specific event (e.g., treatment completed)
async function scheduleRevocation(
accessToken: string,
afterBlocks: number
) {
const currentBlock = await client.getBlockHeight();
const revokeAtBlock = currentBlock + afterBlocks;
// Store in local scheduler
await scheduleTask({
type: 'revoke_access',
token: accessToken,
executeAtBlock: revokeAtBlock
});
console.log(`Scheduled revocation at block ${revokeAtBlock}`);
}
Security Considerations
Authorization
- Patient-only operation: Only the patient who granted access can revoke it
- Cannot be reversed: Once revoked, the same token cannot be un-revoked
- No doctor self-revocation: Doctors cannot revoke their own access
- Instant invalidation: Takes effect as soon as transaction confirms
- No grace period: No delay between revocation and enforcement
- Atomic operation: Cannot fail partially
Auditability
// The revoked grant remains in the mapping for audit purposes
const grant = await queryAccessGrant(revokedToken);
console.log({
patient: grant.patient,
doctor: grant.doctor,
recordId: grant.record_id,
grantedAt: grant.granted_at,
expiresAt: grant.expires_at,
isRevoked: grant.is_revoked, // true
});
// This allows tracking:
// - Who had access
// - When access was granted
// - When access was revoked
// - Original expiration time
Impact on verify_access
After revocation, verify_access will fail:
// In verify_access finalize function:
assert(!grant.is_revoked); // ← This assertion fails
This prevents the doctor from accessing the record even if:
- The expiration time hasn’t been reached
- The token was valid before
- The doctor address and record ID match
Best Practices
- Track grants locally: Maintain a database of active grants for easy revocation
- Provide clear UI: Make it easy for patients to see and revoke active grants
- Confirm before revoking: Ask for patient confirmation to prevent accidental revocations
- Notify doctors: Optionally inform doctors when access is revoked (via separate channel)
- Log revocations: Keep audit logs of when and why access was revoked
- Batch revocations: If revoking multiple grants, do them in parallel for efficiency
- Handle errors gracefully: Token might not exist or patient might not own it