Skip to main content

Audit Log, Token Manager & Privacy Guard

Security observability, authentication token lifecycle, and privacy protection — the operational side of PrismSecurity.

Security Audit Log

Tamper-evident, append-only event log using SHA-256 hash chains. Each entry’s hash depends on the previous — breaking one link invalidates the chain.

Record Events

let log = PrismSecurityAuditLog()

log.record(PrismSecurityEvent(kind: .biometricSuccess, detail: "Face ID"))
log.record(PrismSecurityEvent(
    kind: .keychainRead,
    detail: "read apiToken",
    metadata: ["key": "apiToken"]
))

24 Event Kinds

CategoryEvents
BiometricbiometricSuccess, biometricFailure, biometricLockout
KeychainkeychainRead, keychainWrite, keychainDelete
EncryptionencryptionSuccess, encryptionFailure, decryptionSuccess, decryptionFailure
TokentokenRefresh, tokenExpired, tokenRevoked
PermissionpermissionGranted, permissionDenied
IntegrityintegrityViolation, jailbreakDetected, debuggerDetected
TransportcertificatePinningSuccess, certificatePinningFailure
PrivacydataRedacted, clipboardCleared
SessionsessionStarted, sessionEnded

Query and Filter

// All entries
let all = log.allEntries

// By kind
let biometric = log.entries(ofKind: .biometricSuccess)

// By date range
let today = log.entries(from: startOfDay, to: .now)

// Recent N
let latest = log.recentEntries(10)

// Count
log.count

Verify Integrity

if log.verifyIntegrity() {
    // Hash chain intact — no entries tampered
} else {
    // ALERT: audit log has been modified
}

Export

let exporter = PrismAuditExporter()

// JSON export (ISO 8601 dates)
let jsonData = try exporter.exportJSON(log.allEntries)
let jsonString = try exporter.exportJSONString(log.allEntries)

// Summary
let summary = exporter.exportSummary(log.allEntries)
print(summary.totalEntries)                    // 42
print(summary.eventCounts[.biometricSuccess])  // 15

Capacity Management

// Cap at 1000 entries (oldest pruned automatically)
let log = PrismSecurityAuditLog(maxEntries: 1000)

// Manual clear
log.clear()
The hash chain uses SHA256(event.id + event.kind + event.detail + event.timestamp + previousHash). Verifying integrity is O(n) — call it periodically, not on every write.

Token Manager

Actor-based JWT token lifecycle with automatic refresh and concurrent request handling.

Store Tokens

let manager = PrismTokenManager()

await manager.store(PrismTokenPair(
    accessToken: jwtString,
    refreshToken: refreshString
))

Get Valid Token (Auto-Refresh)

// Returns current token if valid, refreshes if expiring soon
let token = try await manager.validAccessToken { pair in
    // Called when token needs refresh
    let newPair = try await authService.refresh(pair.refreshToken!)
    return newPair
}

print(token.subject)    // "user123"
print(token.isExpired)  // false

Concurrent Refresh Handling

Multiple concurrent callers won’t trigger parallel refresh requests — the actor queues them and shares the result:
// 10 concurrent API calls — only ONE refresh request
await withTaskGroup(of: Void.self) { group in
    for _ in 0..<10 {
        group.addTask {
            let token = try await manager.validAccessToken(refresher: refresh)
            // All 10 get the same refreshed token
        }
    }
}

JWT Decode

let token = try PrismAccessToken.decode(jwtString)

token.subject       // "user123"
token.issuer        // "auth.example.com"
token.isExpired     // Bool
token.expiresAt     // Date?
token.issuedAt      // Date?

// Custom claims
let role: String? = token.claim("role")
let org: Int? = token.claim("org_id")

// Expiry check with buffer
token.expiresWithin(300)  // true if < 5 min left
PrismAccessToken.decode parses the JWT payload without verifying the signature — it’s for client-side claim inspection only. Always validate tokens server-side.

Token Configuration

let config = PrismTokenConfiguration(
    service: "MyAuth",
    refreshThreshold: 60,           // refresh when < 60s left
    refreshStrategy: .proactive     // refresh before expiry
)

let manager = PrismTokenManager(configuration: config)
StrategyBehavior
.proactiveRefresh before token expires (default, 300s threshold)
.reactiveRefresh only when token is expired
.manualOnly refresh when explicitly called

Intercept Requests

let interceptor = PrismTokenInterceptor(manager: manager)

var request = URLRequest(url: apiURL)
request = try await interceptor.intercept(request)
// Adds: Authorization: Bearer <token>

Privacy Guard

PII Redaction

Detect and redact personally identifiable information using regex patterns.
let redactor = PrismRedactor()

redactor.redact("Email: john@example.com")
// → "Email: j***@***.***"

redactor.redact("Call 555-123-4567")
// → "Call ***-***-4567"

redactor.redact("Card: 4111 1111 1111 1234")
// → "Card: ****-****-****-1234"

redactor.redact("SSN: 123-45-6789")
// → "SSN: ***-**-6789"

redactor.redact("IP: 192.168.1.100")
// → "IP: ***.***.***.***"

Redaction Styles

// Mask (default) — partial reveal
let mask = PrismRedactor(style: .mask)
mask.redactValue("john@example.com", type: .email)
// → "j***@***.***"

// Remove — full replacement
let remove = PrismRedactor(style: .remove)
remove.redactValue("john@example.com", type: .email)
// → "[REDACTED]"

// Hash — first 8 hex chars
let hash = PrismRedactor(style: .hash)
hash.redactValue("john@example.com", type: .email)
// → "a1b2c3d4..."

PII Types

TypePattern
.emailuser@domain.tld
.phone123-456-7890
.creditCard4111 1111 1111 1234
.ssn123-45-6789
.ipAddress192.168.1.1
.customUser-defined regex

Privacy Guard (Unified Facade)

let guard = PrismPrivacyGuard()

// Redact PII in strings
let safe = guard.redact("Email: test@test.com")

// Classify field sensitivity
guard.classify("password")    // .restricted
guard.classify("email")       // .sensitive
guard.classify("user_id")     // .internal
guard.classify("name")        // .public

// Auto-protect by field name
guard.protect(field: "password", value: "secret123")
// → "[RESTRICTED]"

guard.protect(field: "email", value: "john@example.com")
// → "j***@***.***"

guard.protect(field: "name", value: "John")
// → "John" (public — unchanged)

Privacy Levels

enum PrismPrivacyLevel: Comparable {
    case `public`     // display names, usernames
    case `internal`   // user IDs, session IDs
    case sensitive    // emails, phone numbers
    case restricted   // passwords, tokens, SSNs
}

// Comparable: public < internal < sensitive < restricted

Screen Protection (SwiftUI)

Hide sensitive content when the app goes to background or appears in the app switcher.
SensitiveDataView()
    .prismScreenProtection()
Or wrap in a secure container:
PrismSecureView {
    Text("API Key: \(apiKey)")
    Text("Balance: \(balance)")
}
When the app leaves the foreground, a lock overlay with a shield icon replaces the content.

Clipboard Guard

Auto-clear the clipboard after copying sensitive data.
let clipboard = PrismClipboardGuard(clearAfter: 30) // seconds

clipboard.copySecurely("my-api-token")
// Clipboard auto-clears after 30 seconds

clipboard.clearNow()      // immediate clear
clipboard.cancelClear()   // cancel pending auto-clear
Screen protection uses @Environment(\.scenePhase) — it must be applied inside a SwiftUI view hierarchy that receives scene phase changes. It won’t work in isolated view previews.