UnlockOS Developers
← Back to blog
🛡️

Building Trust Through Audit-First Architecture in Smart Lock SDKs

Apr 13, 2026Apr 19, 2026
6 min
310 commits
Depth 8/10
securityaudit-loggingauthenticationauthorizationtype-safety

Building Trust Through Audit-First Architecture in Smart Lock SDKs

Introduction

In security-critical systems like smart lock management, trust isn't just built through encryption and access controls—it's fundamentally established through comprehensive audit trails and defensive programming patterns. Recent developments in the UnlockOS SDK demonstrate how audit-first architecture can create unshakeable confidence in physical access control systems.

Audit-First Lookup Patterns

The cornerstone of trustworthy access control is ensuring every operation is traceable. The audit-first lookup pattern prioritizes logging before data retrieval:

interface AuditFirstLookup<T> {
  performLookup(criteria: LookupCriteria): Promise<{
    auditEntry: AuditLogEntry;
    result: T | null;
  }>;
}

// Implementation ensures audit logging happens first
async function auditFirstGuestLookup(profileId: string) {
  // Audit the lookup attempt before accessing data
  const auditEntry = await createAuditEntry({
    action: 'GUEST_PROFILE_LOOKUP',
    resourceId: profileId,
    timestamp: new Date().toISOString(),
    context: { source: 'identity_federation' }
  });
  
  try {
    const profile = await fetchGuestProfile(profileId);
    await updateAuditEntry(auditEntry.id, { status: 'SUCCESS' });
    return { auditEntry, result: profile };
  } catch (error) {
    await updateAuditEntry(auditEntry.id, { 
      status: 'FAILED', 
      error: sanitizeError(error) 
    });
    throw error;
  }
}

This pattern ensures that even failed operations leave an audit trail, crucial for security investigations and compliance.

Concurrent-Safe Identity Federation

Smart lock systems often deal with concurrent access attempts. The ensureUnlockPass helper demonstrates how to handle identity federation safely:

interface UnlockPassEnsureResult {
  unlockPass: UnlockPass;
  wasCreated: boolean;
  auditTrail: AuditLogEntry[];
}

async function ensureUnlockPass(
  guestProfile: GuestProfile,
  organizationId: string
): Promise<UnlockPassEnsureResult> {
  const auditTrail: AuditLogEntry[] = [];
  
  // Use database-level constraints for concurrency safety
  try {
    const result = await db.transaction(async (tx) => {
      // Attempt insert with ON CONFLICT handling
      const insertResult = await tx
        .insert(unlockPasses)
        .values({
          guestProfileId: guestProfile.id,
          organizationId,
          createdAt: new Date(),
          status: 'active'
        })
        .onConflict([
          unlockPasses.guestProfileId,
          unlockPasses.organizationId
        ])
        .doUpdate({
          set: { lastAccessedAt: new Date() }
        })
        .returning();
      
      // Log the operation
      const auditEntry = await tx.insert(guestProfileAuditLog)
        .values({
          guestProfileId: guestProfile.id,
          action: insertResult.length > 0 ? 'CREATED' : 'ACCESSED',
          organizationId,
          metadata: { concurrencySafe: true }
        });
      
      auditTrail.push(auditEntry);
      return insertResult;
    });
    
    return {
      unlockPass: result[0],
      wasCreated: result.length > 0,
      auditTrail
    };
  } catch (error) {
    // Ensure failed attempts are also audited
    await logFailedUnlockPassCreation(guestProfile.id, organizationId, error);
    throw new SecurityError('Failed to ensure unlock pass', { cause: error });
  }
}

Defensive Authorization Checks

Every operation that can affect physical access must include robust authorization validation:

interface AuthorizationContext {
  userId: string;
  organizationId: string;
  facilityId?: string;
  requiredScopes: string[];
}

async function withAuthorizationCheck<T>(
  context: AuthorizationContext,
  operation: () => Promise<T>
): Promise<T> {
  // Multi-layer authorization validation
  const authResult = await validateAuthorization({
    userId: context.userId,
    organizationId: context.organizationId,
    facilityId: context.facilityId,
    requiredScopes: context.requiredScopes
  });
  
  if (!authResult.authorized) {
    // Log unauthorized access attempt
    await logSecurityEvent({
      type: 'UNAUTHORIZED_ACCESS_ATTEMPT',
      userId: context.userId,
      organizationId: context.organizationId,
      deniedScopes: authResult.missingScopes,
      timestamp: new Date().toISOString()
    });
    
    throw new AuthorizationError(
      `Insufficient permissions: missing ${authResult.missingScopes.join(', ')}`
    );
  }
  
  // Log authorized operation start
  const operationId = generateOperationId();
  await logSecurityEvent({
    type: 'AUTHORIZED_OPERATION_START',
    operationId,
    userId: context.userId,
    organizationId: context.organizationId,
    scopes: context.requiredScopes
  });
  
  try {
    const result = await operation();
    
    // Log successful completion
    await logSecurityEvent({
      type: 'AUTHORIZED_OPERATION_COMPLETE',
      operationId,
      status: 'SUCCESS'
    });
    
    return result;
  } catch (error) {
    // Log operation failure
    await logSecurityEvent({
      type: 'AUTHORIZED_OPERATION_FAILED',
      operationId,
      error: sanitizeError(error)
    });
    throw error;
  }
}

Idempotent State Management

Smart lock operations must be idempotent to handle network failures and retry scenarios safely:

interface IdempotentOperation<T> {
  key: string;
  operation: () => Promise<T>;
  isIdempotent: (existing: T, new: T) => boolean;
}

async function performIdempotentCheckin(
  membershipId: string,
  facilityId: string
): Promise<CheckinResult> {
  const idempotencyKey = `checkin:${membershipId}:${facilityId}`;
  
  // Check for existing active session
  const existingSession = await db
    .select()
    .from(checkinSessions)
    .where(
      and(
        eq(checkinSessions.membershipId, membershipId),
        eq(checkinSessions.facilityId, facilityId),
        eq(checkinSessions.status, 'checked_in')
      )
    )
    .limit(1);
  
  if (existingSession.length > 0) {
    // Idempotent response for active session
    await logAuditEvent({
      type: 'IDEMPOTENT_CHECKIN_RETURN',
      sessionId: existingSession[0].id,
      membershipId,
      facilityId
    });
    
    return {
      session: existingSession[0],
      wasCreated: false,
      idempotent: true
    };
  }
  
  // Create new session with partial unique index protection
  try {
    const newSession = await db.transaction(async (tx) => {
      const session = await tx
        .insert(checkinSessions)
        .values({
          membershipId,
          facilityId,
          status: 'checked_in',
          checkedInAt: new Date()
        })
        .returning();
      
      // Audit successful creation
      await tx.insert(auditLog).values({
        entityType: 'checkin_session',
        entityId: session[0].id,
        action: 'CREATED',
        userId: membershipId,
        metadata: { idempotencyKey }
      });
      
      return session[0];
    });
    
    return {
      session: newSession,
      wasCreated: true,
      idempotent: false
    };
  } catch (error) {
    if (isUniqueConstraintViolation(error)) {
      // Handle race condition - return existing session
      const raceConditionSession = await findExistingSession(
        membershipId,
        facilityId
      );
      return {
        session: raceConditionSession,
        wasCreated: false,
        idempotent: true
      };
    }
    throw error;
  }
}

Error Recovery with Audit Integrity

Even when operations fail, the audit trail must remain intact:

async function handleKeyRevocationWithRecovery(
  keyId: string,
  userId: string,
  reason: string
): Promise<KeyRevocationResult> {
  let auditEntryId: string | null = null;
  
  try {
    // Always record revocation timestamp first
    auditEntryId = await recordRevocationAttempt({
      keyId,
      userId,
      reason,
      timestamp: new Date(),
      status: 'ATTEMPTED'
    });
    
    // Attempt the actual revocation
    const revocationResult = await performKeyRevocation(keyId);
    
    // Update audit entry with success
    await updateAuditEntry(auditEntryId, {
      status: 'SUCCESS',
      completedAt: new Date(),
      revocationDetails: revocationResult
    });
    
    return { success: true, auditEntryId };
    
  } catch (error) {
    // Even if revocation fails, record the timestamp
    // This is crucial for security - we know a revocation was attempted
    if (auditEntryId) {
      await updateAuditEntry(auditEntryId, {
        status: 'FAILED',
        failedAt: new Date(),
        error: sanitizeError(error),
        // Still record entry_key_revoked_at for safety
        revokedAt: new Date()
      });
    }
    
    // Fail safely - assume key might be compromised
    await markKeyAsSuspicious(keyId, 'REVOCATION_FAILED');
    
    throw new SecurityError(
      'Key revocation failed but marked as suspicious',
      { keyId, originalError: error }
    );
  }
}

Summary

Building trust in security-critical systems requires more than just implementing security features—it demands an architecture that prioritizes auditability, handles failures gracefully, and maintains data integrity even under adverse conditions. The patterns demonstrated here show how audit-first design, concurrent-safe operations, comprehensive authorization checks, idempotent state management, and robust error recovery create a foundation of trust that users and auditors can rely on.

By implementing these patterns, smart lock SDKs can provide not just secure access control, but verifiable secure access control—a crucial distinction when physical safety and security are at stake.

Key Insights

1
Security

Audit-First Lookup Pattern

Prioritizing audit logging before data operations ensures complete traceability even for failed requests

2
Reliability

Concurrent-Safe Identity Federation

Using database constraints and transaction handling to manage concurrent access attempts safely

3
Security

Multi-Layer Authorization

Comprehensive authorization validation with detailed security event logging for all access attempts

4
Reliability

Idempotent State Management

Designing operations to handle retries and race conditions without compromising system integrity

5
Security

Error Recovery with Audit Integrity

Maintaining audit trails and failing safely even when primary operations encounter errors