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", "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 saveImmediatelly = true
private var writers: Set<String>
private var caps = [Int: Cap]() { private var caps = [Int: Cap]() {
didSet { didSet {
guard saveImmediatelly else { guard saveImmediatelly else {
@ -26,12 +28,13 @@ final class CapServer {
} }
var nextClassifierVersion: Int { 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.imageFolder = folder.appendingPathComponent("images")
self.dbFile = folder.appendingPathComponent("caps.json") self.dbFile = folder.appendingPathComponent("caps.json")
self.writers = Set(writers)
var isDirectory: ObjCBool = false var isDirectory: ObjCBool = false
guard fm.fileExists(atPath: folder.path, isDirectory: &isDirectory), guard fm.fileExists(atPath: folder.path, isDirectory: &isDirectory),
@ -66,6 +69,17 @@ final class CapServer {
folder(of: cap).appendingPathComponent(String(format: "%04d-%02d.jpg", cap, version)) 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 // MARK: Counts
private func updateCounts() throws { private func updateCounts() throws {

View File

@ -2,15 +2,20 @@ import Foundation
struct Config: Codable { struct Config: Codable {
/// The path to the log file
let logPath: String let logPath: String
/// Serve files in the Public directory using Vapor
let serveFiles: Bool let serveFiles: Bool
/// Authentication tokens for remotes allowed to write
let writers: [String]
var logURL: URL { var logURL: URL {
.init(fileURLWithPath: logPath) .init(fileURLWithPath: logPath)
} }
static var `default`: Config { 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) 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 // Register routes to the router
try routes(app) try routes(app)

View File

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