Add authentication to uploads

This commit is contained in:
Christoph Hagen 2022-05-27 09:25:41 +02:00
parent f14da6bafc
commit ee41e3bcd3
5 changed files with 38 additions and 5 deletions

View File

@ -1,4 +1,7 @@
{
"logPath": "\/var\/log\/caps.log",
"serveFiles": true
"serveFiles": true,
"writers" : [
"auth_key_1"
]
}

View File

@ -16,6 +16,8 @@ final class CapServer {
private var saveImmediatelly = true
private var writers: Set<String>
private var caps = [Int: Cap]() {
didSet {
guard saveImmediatelly else {
@ -26,12 +28,13 @@ final class CapServer {
}
var nextClassifierVersion: Int {
caps.values.map { $0.classifierVersion }.max() ?? 1
caps.values.compactMap { $0.classifierVersion }.max() ?? 1
}
init(in folder: URL) throws {
init(in folder: URL, writers: [String]) throws {
self.imageFolder = folder.appendingPathComponent("images")
self.dbFile = folder.appendingPathComponent("caps.json")
self.writers = Set(writers)
var isDirectory: ObjCBool = false
guard fm.fileExists(atPath: folder.path, isDirectory: &isDirectory),
@ -65,6 +68,17 @@ final class CapServer {
func file(of cap: Int, version: Int) -> URL {
folder(of: cap).appendingPathComponent(String(format: "%04d-%02d.jpg", cap, version))
}
// MARK: Authentication
func hasAuthorization(for key: String) -> Bool {
// Note: This is not a constant-time compare, so there may be an opportunity
// for timing attack here. Sets perform hashed lookups, so this may be less of an issue,
// and we're not doing anything critical in this application.
// Worst case, an unauthorized person with a lot of free time and energy to hack this system
// is able to change contents of the database, which are backed up in any case.
writers.contains(key)
}
// MARK: Counts

View File

@ -2,15 +2,20 @@ import Foundation
struct Config: Codable {
/// The path to the log file
let logPath: String
/// Serve files in the Public directory using Vapor
let serveFiles: Bool
/// Authentication tokens for remotes allowed to write
let writers: [String]
var logURL: URL {
.init(fileURLWithPath: logPath)
}
static var `default`: Config {
.init(logPath: "/var/log/caps.log", serveFiles: true)
.init(logPath: "/var/log/caps.log", serveFiles: true, writers: [])
}
}

View File

@ -30,7 +30,8 @@ public func configure(_ app: Application) throws {
app.middleware.use(middleware)
}
server = try CapServer(in: URL(fileURLWithPath: publicDirectory))
server = try CapServer(in: URL(fileURLWithPath: publicDirectory),
writers: config.writers)
// Register routes to the router
try routes(app)

View File

@ -1,5 +1,12 @@
import Vapor
private func authorize(_ request: Request) throws {
let key = try request.query.get(String.self, at: "key")
guard server.hasAuthorization(for: key) else {
throw Abort(.forbidden)
}
}
/// Register your application's routes here.
///
/// [Learn More ](https://docs.vapor.codes/3.0/getting-started/structure/#routesswift)
@ -7,6 +14,7 @@ func routes(_ app: Application) throws {
// Set the name of a cap
app.postCatching("name", ":n") { request in
try authorize(request)
guard let cap = request.parameters.get("n", as: Int.self) else {
log("Invalid parameter for cap")
throw Abort(.badRequest)
@ -20,6 +28,7 @@ func routes(_ app: Application) throws {
// Upload an image
app.postCatching("images", ":n") { request -> Data in
try authorize(request)
guard let cap = request.parameters.get("n", as: Int.self) else {
log("Invalid parameter for cap")
throw Abort(.badRequest)
@ -35,6 +44,7 @@ func routes(_ app: Application) throws {
// Set a different version as the main image
app.getCatching("switch", ":n", ":v") { request in
try authorize(request)
guard let cap = request.parameters.get("n", as: Int.self), cap >= 0 else {
log("Invalid parameter for cap")
throw Abort(.badRequest)