import Vapor import Foundation import FoundationNetworking import Crypto import Leaf func routes(_ app: Application) throws { // Render home page when root domain is loaded app.get { req -> EventLoopFuture in // Get data from config file at /Resources/config.json let fileData = try String(contentsOfFile: "\(app.directory.resourcesDirectory)/config.json").data(using: .utf8) // Decode JSON file to Config struct let config: Config = try! JSONDecoder().decode(Config.self, from: fileData!) // Check if user is logged in let loginStatus = req.session.data["loggedIn"] ?? "false" // Change loginStatus to a boolean let loginBool = loginStatus == "true" ? true : false // Render home.leaf with config and login status as context return req.view.render("home", LContext(config: config, loggedIn: loginBool)) } // Return website status when /status/:url is loaded app.get("status", ":url") { req -> [String: String] in // Get URL from request parameters let url = URL(string: "http://\(req.parameters.get("url")!)") // Configure URLSession let config = URLSessionConfiguration.default // Set request timeouts to 5s, then assume offline config.timeoutIntervalForRequest = 5 config.timeoutIntervalForResource = 5 // Declare statusDict for storing website status var statusDict: [String:String] = [:] // Declare URLSession request using URL from request parameters var headReq = URLRequest(url: url!) // Set HEAD request headReq.httpMethod = "HEAD" // Set request timeout to 5s, then assume offline headReq.timeoutInterval = 5 // Create DispatchSemaphore to block thread until request is resolved and status stored let semaphore = DispatchSemaphore(value: 0) // Run Async URLSession dataTask URLSession.shared.dataTask(with: headReq, completionHandler: { (_, response, error) in // If the response is valid if let httpURLResponse = response as? HTTPURLResponse { // Store status code in statusDict statusDict["code"] = String(httpURLResponse.statusCode) // Store website status in statusDict statusDict["down"] = "false" } // Signal DispatchSemaphore to stop blocking semaphore.signal() }).resume() // Wait for semaphore signal semaphore.wait() // If statusDict is empty, return code 0, down true. Otherwise, return statusdict return statusDict.count > 0 ? statusDict : ["code": "0", "down": "true"] } // Render login page on GET request to /login app.get("login") { req -> EventLoopFuture in // Get data from config file at /Resources/config.json let fileData = try String(contentsOfFile: "\(app.directory.resourcesDirectory)/config.json").data(using: .utf8) // Decode JSON file to Config struct let config: Config = try! JSONDecoder().decode(Config.self, from: fileData!) // Render home.leaf with config as context return req.view.render("login", LContext(config: config, loggedIn: false)) } // Verify credentials and log in on POST request to /login app.post("login") { req -> Response in // Decode POST request data into Login struct let data = try req.content.decode(Login.self) // Get data from config file at /Resources/config.json let fileData = try String(contentsOfFile: "\(app.directory.resourcesDirectory)/config.json").data(using: .utf8) // Decode JSON file to Config struct let config: Config = try! JSONDecoder().decode(Config.self, from: fileData!) // Get password from POST request data let loginPassData = data.password?.data(using: .utf8) // Hash password in POST data using SHA256.hash() from SwiftCrypto let loginPassHash = SHA256.hash(data: loginPassData ?? "".data(using: .utf8)!) // Convert hash to string let stringHash = loginPassHash.map { String(format: "%02hhx", $0) }.joined() // If hash in config matches provided hash if stringHash == config.passwordHash { // Set logged in to true in session req.session.data["loggedIn"] = "true" // Redirect back to / return try req.redirect(to: "/") } else { // If hashes do not match, return unauthorized error throw Abort(.unauthorized) } } // Destroy session on GET request to logout app.get("logout") { req -> Response in // Destroy session req.session.destroy() // Redirect back to / return try req.redirect(to: "/") } }