Swift Package (Server)
Quick Start
Advanced Features
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
}
}
}