Swift Package (Server)

Swift Package (Server)

Server-side Swift package for validating attestations, managing device intelligence, and integrating Grantiva into your Vapor, Perfect, or Kitura applications.

Requirements

  • Swift 5.7+
  • macOS 12+ / Linux (Ubuntu 20.04+)
  • OpenSSL 1.1+ (Linux only)

Installation

Swift Package Manager

Add the Grantiva server package to your Package.swift:

// swift-tools-version: 5.7
import PackageDescription

let package = Package(
    name: "YourServerApp",
    platforms: [
        .macOS(.v12)
    ],
    dependencies: [
        .package(url: "https://github.com/grantiva/swift-server", from: "1.0.0"),
        .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), // If using Vapor
    ],
    targets: [
        .target(
            name: "App",
            dependencies: [
                .product(name: "GrantivaServer", package: "swift-server"),
                .product(name: "Vapor", package: "vapor"),
            ]
        ),
    ]
)

Basic Setup

Vapor Integration

import Vapor
import GrantivaServer

// Configure Grantiva in configure.swift
public func configure(_ app: Application) throws {
    // Initialize Grantiva
    let grantiva = GrantivaServer(
        tenantId: Environment.get("GRANTIVA_TENANT_ID")!,
        apiKey: Environment.get("GRANTIVA_API_KEY")!, // Server-side API key
        environment: .production
    )
    
    // Register as service
    app.grantiva = grantiva
    
    // Configure middleware
    app.middleware.use(GrantivaMiddleware())
    
    // Register routes
    try routes(app)
}

// Extension for easy access
extension Application {
    var grantiva: GrantivaServer {
        get { self.storage[GrantivaKey.self]! }
        set { self.storage[GrantivaKey.self] = newValue }
    }
    
    private struct GrantivaKey: StorageKey {
        typealias Value = GrantivaServer
    }
}

Authentication Middleware

import Vapor
import GrantivaServer

struct GrantivaMiddleware: AsyncMiddleware {
    func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
        // Extract JWT from Authorization header
        guard let token = request.headers.bearerAuthorization?.token else {
            throw Abort(.unauthorized, reason: "Missing authentication token")
        }
        
        do {
            // Validate token with Grantiva
            let validation = try await request.application.grantiva.validateToken(token)
            
            // Check device risk
            if validation.deviceIntelligence.riskScore > 75 {
                throw Abort(.forbidden, reason: "Device risk too high")
            }
            
            // Store device info in request
            request.deviceIntelligence = validation.deviceIntelligence
            request.permissions = validation.permissions
            
            return try await next.respond(to: request)
            
        } catch {
            throw Abort(.unauthorized, reason: "Invalid token: \(error)")
        }
    }
}

// Request extensions
extension Request {
    var deviceIntelligence: DeviceIntelligence? {
        get { self.storage[DeviceIntelligenceKey.self] }
        set { self.storage[DeviceIntelligenceKey.self] = newValue }
    }
    
    var permissions: [String] {
        get { self.storage[PermissionsKey.self] ?? [] }
        set { self.storage[PermissionsKey.self] = newValue }
    }
    
    private struct DeviceIntelligenceKey: StorageKey {
        typealias Value = DeviceIntelligence
    }
    
    private struct PermissionsKey: StorageKey {
        typealias Value = [String]
    }
}

API Endpoints

Protected Routes

import Vapor
import GrantivaServer

func routes(_ app: Application) throws {
    // Public endpoint for attestation validation
    app.post("api", "attestation", "validate") { req async throws -> AttestationResponse in
        let attestation = try req.content.decode(AttestationRequest.self)
        
        // Forward to Grantiva for validation
        let result = try await req.application.grantiva.validateAttestation(
            keyId: attestation.keyId,
            attestation: attestation.attestation,
            challenge: attestation.challenge
        )
        
        return AttestationResponse(
            token: result.token,
            expiresAt: result.expiresAt,
            deviceIntelligence: result.deviceIntelligence
        )
    }
    
    // Protected routes group
    let protected = app.grouped(GrantivaMiddleware())
    
    // User profile endpoint
    protected.get("api", "user", "profile") { req async throws -> UserProfile in
        // Access device intelligence from middleware
        guard let device = req.deviceIntelligence else {
            throw Abort(.internalServerError)
        }
        
        // Customize response based on device trust
        if device.riskScore < 30 {
            return UserProfile(
                // Full profile for trusted devices
                includesSensitiveData: true
            )
        } else {
            return UserProfile(
                // Limited profile for higher risk
                includesSensitiveData: false
            )
        }
    }
    
    // Financial transaction endpoint
    protected.post("api", "transfer") { req async throws -> TransferResponse in
        // Check specific permission
        guard req.permissions.contains("transfer_funds") else {
            throw Abort(.forbidden, reason: "Insufficient permissions")
        }
        
        let transfer = try req.content.decode(TransferRequest.self)
        
        // Additional risk check for high-value transfers
        if transfer.amount > 10000 {
            guard let device = req.deviceIntelligence,
                  device.riskScore < 20 else {
                throw Abort(.forbidden, reason: "High-value transfers require low-risk devices")
            }
        }
        
        // Process transfer
        return try await processTransfer(transfer)
    }
}

Advanced Features

Custom Validation Rules

extension GrantivaServer {
    
    func configureCustomRules() {
        // Add custom validation rules
        self.addValidationRule { deviceIntelligence in
            // Block devices from specific countries
            let blockedCountries = ["XX", "YY"]
            if blockedCountries.contains(deviceIntelligence.country ?? "") {
                throw ValidationError.geographicRestriction
            }
        }
        
        // Time-based access control
        self.addValidationRule { deviceIntelligence in
            let hour = Calendar.current.component(.hour, from: Date())
            if hour < 6 || hour > 22 {
                // Require lower risk score during off-hours
                if deviceIntelligence.riskScore > 30 {
                    throw ValidationError.timeRestriction
                }
            }
        }
        
        // Device consistency check
        self.addValidationRule { deviceIntelligence in
            if deviceIntelligence.attestationCount == 1 {
                // New device - apply stricter rules
                if deviceIntelligence.riskScore > 40 {
                    throw ValidationError.newDeviceRestriction
                }
            }
        }
    }
}

Analytics Integration

import GrantivaServer

struct AnalyticsService {
    let grantiva: GrantivaServer
    
    func getDashboardData() async throws -> DashboardData {
        // Fetch analytics from Grantiva
        let analytics = try await grantiva.analytics.getDashboard(
            period: .days(7)
        )
        
        // Process for your dashboard
        return DashboardData(
            totalDevices: analytics.summary.totalDevices,
            activeDevices: analytics.summary.activeDevices,
            averageRiskScore: analytics.summary.averageRiskScore,
            fraudAttempts: analytics.summary.highRiskDevices,
            topRiskFactors: analytics.topRiskFactors.map { factor in
                RiskFactor(
                    name: factor.factor,
                    count: factor.count,
                    percentage: factor.percentage
                )
            }
        )
    }
    
    func getDeviceHistory(deviceId: String) async throws -> DeviceHistory {
        let history = try await grantiva.analytics.getDevice(deviceId)
        
        return DeviceHistory(
            attestations: history.attestationHistory,
            riskTrend: history.riskHistory,
            currentStatus: history.deviceIntelligence
        )
    }
    
    func exportComplianceReport(month: Date) async throws -> Data {
        let report = try await grantiva.compliance.generateReport(
            type: .securityAudit,
            period: .month(month),
            format: .pdf
        )
        
        return report.data
    }
}

WebSocket Support

// Real-time device monitoring via WebSocket
app.webSocket("ws", "device-monitor") { req, ws in
    // Authenticate WebSocket connection
    guard let token = req.headers.first(name: "Authorization") else {
        try await ws.close(code: .unsupportedData)
        return
    }
    
    do {
        let validation = try await req.application.grantiva.validateToken(token)
        
        // Subscribe to device events
        let subscription = req.application.grantiva.subscribeToDeviceEvents(
            deviceId: validation.deviceIntelligence.deviceId
        ) { event in
            // Send events to WebSocket client
            let message = try JSONEncoder().encode(event)
            ws.send(message)
        }
        
        // Handle client messages
        ws.onText { ws, text in
            // Process client commands
            if text == "ping" {
                ws.send("pong")
            }
        }
        
        // Clean up on disconnect
        ws.onClose.whenComplete { _ in
            subscription.cancel()
        }
        
    } catch {
        try await ws.close(code: .unsupportedData)
    }
}

Database Integration

Fluent Models

import Fluent
import Vapor

final class UserDevice: Model {
    static let schema = "user_devices"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "user_id")
    var userId: UUID
    
    @Field(key: "device_id")
    var deviceId: String
    
    @Field(key: "device_name")
    var deviceName: String
    
    @Field(key: "last_risk_score")
    var lastRiskScore: Int
    
    @Field(key: "trusted")
    var trusted: Bool
    
    @Timestamp(key: "first_seen", on: .create)
    var firstSeen: Date?
    
    @Timestamp(key: "last_seen", on: .update)
    var lastSeen: Date?
}

// Migration
struct CreateUserDevice: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("user_devices")
            .id()
            .field("user_id", .uuid, .required)
            .field("device_id", .string, .required)
            .field("device_name", .string, .required)
            .field("last_risk_score", .int, .required)
            .field("trusted", .bool, .required)
            .field("first_seen", .datetime)
            .field("last_seen", .datetime)
            .unique(on: "device_id")
            .create()
    }
    
    func revert(on database: Database) async throws {
        try await database.schema("user_devices").delete()
    }
}

Error Handling

import Vapor

enum GrantivaServerError: Error {
    case invalidConfiguration
    case networkError(Error)
    case validationFailed(reason: String)
    case rateLimitExceeded
    case serviceUnavailable
}

extension GrantivaServerError: AbortError {
    var status: HTTPResponseStatus {
        switch self {
        case .invalidConfiguration:
            return .internalServerError
        case .networkError:
            return .badGateway
        case .validationFailed:
            return .forbidden
        case .rateLimitExceeded:
            return .tooManyRequests
        case .serviceUnavailable:
            return .serviceUnavailable
        }
    }
    
    var reason: String {
        switch self {
        case .invalidConfiguration:
            return "Server configuration error"
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        case .validationFailed(let reason):
            return "Validation failed: \(reason)"
        case .rateLimitExceeded:
            return "Rate limit exceeded"
        case .serviceUnavailable:
            return "Service temporarily unavailable"
        }
    }
}

Testing

Unit Tests

import XCTVapor
@testable import App

final class GrantivaTests: XCTestCase {
    var app: Application!
    
    override func setUpWithError() throws {
        app = Application(.testing)
        
        // Configure test environment
        app.grantiva = GrantivaServer(
            tenantId: "test_tenant",
            apiKey: "test_key",
            environment: .testing
        )
        
        try configure(app)
    }
    
    override func tearDownWithError() throws {
        app.shutdown()
    }
    
    func testAttestationValidation() async throws {
        try app.test(.POST, "api/attestation/validate",
            beforeRequest: { req in
                try req.content.encode(AttestationRequest(
                    keyId: "test_key_id",
                    attestation: "test_attestation",
                    challenge: "test_challenge"
                ))
            },
            afterResponse: { res in
                XCTAssertEqual(res.status, .ok)
                let response = try res.content.decode(AttestationResponse.self)
                XCTAssertNotNil(response.token)
            }
        )
    }
    
    func testProtectedRoute() async throws {
        // Test without token
        try app.test(.GET, "api/user/profile") { res in
            XCTAssertEqual(res.status, .unauthorized)
        }
        
        // Test with valid token
        let token = "test_jwt_token"
        try app.test(.GET, "api/user/profile",
            headers: ["Authorization": "Bearer \(token)"]
        ) { res in
            XCTAssertEqual(res.status, .ok)
        }
    }
}

Performance Optimization

// Configure caching for better performance
extension Application {
    func configureGrantivaCache() {
        // Token validation cache
        self.caches.use(.redis)
        
        self.grantiva.configureCache(
            tokenCacheTTL: 3600, // 1 hour
            deviceCacheTTL: 300   // 5 minutes
        )
    }
}

// Batch validation for multiple devices
extension GrantivaServer {
    func validateBatch(_ tokens: [String]) async throws -> [ValidationResult] {
        try await withThrowingTaskGroup(of: ValidationResult.self) { group in
            for token in tokens {
                group.addTask {
                    try await self.validateToken(token)
                }
            }
            
            var results: [ValidationResult] = []
            for try await result in group {
                results.append(result)
            }
            return results
        }
    }
}

Next Steps