183 lines
6.3 KiB
Swift
183 lines
6.3 KiB
Swift
import Configuration
|
|||
|
|
import Hummingbird
|
||
|
|
import Logging
|
||
|
|
import Persistence
|
||
|
|
import WebsiteCore
|
||
|
|
|
||
|
|
package extension ConfigReader {
|
||
|
|
|
||
|
|
// MARK: Type aliases
|
||
|
|
|
||
|
|
/// The request context type the application serves its routes with; the security headers configuration is generic over it.
|
||
|
|
typealias AppRequestContext = WebsiteRequestContext
|
||
|
|
|
||
|
|
// MARK: Computed
|
||
|
|
|
||
|
|
/// The `Cache-Control` policy applied to static files, grouped by media type.
|
||
|
|
///
|
||
|
|
/// The max-ages are read from the `cache.maxAge.text`, `cache.maxAge.image`, and `cache.maxAge.default` keys. Text files (CSS,
|
||
|
|
/// JavaScript, plain text) additionally require revalidation once stale; images and everything else are served public with their max-age alone.
|
||
|
|
var cacheControl: CacheControl {
|
||
|
|
let maxAgeDefault = int(
|
||
|
|
forKey: .Cache.maxAgeDefault,
|
||
|
|
default: .Cache.maxAgeDefault
|
||
|
|
)
|
||
|
|
let maxAgeImage = int(
|
||
|
|
forKey: .Cache.maxAgeImage,
|
||
|
|
default: .Cache.maxAgeImage
|
||
|
|
)
|
||
|
|
let maxAgeText = int(
|
||
|
|
forKey: .Cache.maxAgeText,
|
||
|
|
default: .Cache.maxAgeText
|
||
|
|
)
|
||
|
|
|
||
|
|
return .init([
|
||
|
|
(.text, [.public, .maxAge(maxAgeText), .mustRevalidate]),
|
||
|
|
(.image, [.public, .maxAge(maxAgeImage)]),
|
||
|
|
(.init(type: .any), [.public, .maxAge(maxAgeDefault)]),
|
||
|
|
])
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The minimum response body size, in bytes, before a response is compressed — read from the `compression.minimumResponseSize` key.
|
||
|
|
var compressionMinResponseSize: Int {
|
||
|
|
int(
|
||
|
|
forKey: .Compression.minResponseSize,
|
||
|
|
default: .Compression.minResponseSize
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The persistence backend the service runs against, derived from the `database.*` keys.
|
||
|
|
///
|
||
|
|
/// When `database.driver` selects MySQL, the connection parameters are assembled from the `database.host`, `database.port`,
|
||
|
|
/// `database.name`, `database.username`, `database.password` (empty when unset), `database.tls`, and
|
||
|
|
/// `database.pool.maxPerEventLoop` keys. Any other driver value falls back to the in-memory database.
|
||
|
|
var driver: Driver {
|
||
|
|
switch string(
|
||
|
|
forKey: .Database.driver,
|
||
|
|
default: .Database.driver
|
||
|
|
) {
|
||
|
|
case .Database.driverMySQL:
|
||
|
|
return .mysql(
|
||
|
|
.init(
|
||
|
|
host: string(
|
||
|
|
forKey: .Database.host,
|
||
|
|
default: .Database.host
|
||
|
|
),
|
||
|
|
port: int(
|
||
|
|
forKey: .Database.port,
|
||
|
|
default: .Database.port
|
||
|
|
),
|
||
|
|
name: string(
|
||
|
|
forKey: .Database.name,
|
||
|
|
default: .Database.name
|
||
|
|
),
|
||
|
|
username: string(
|
||
|
|
forKey: .Database.username,
|
||
|
|
default: .Database.username
|
||
|
|
),
|
||
|
|
password: string(
|
||
|
|
forKey: .Database.password,
|
||
|
|
default: ""
|
||
|
|
),
|
||
|
|
tls: tls,
|
||
|
|
maxConnectionsPerEventLoop: int(
|
||
|
|
forKey: .Database.poolMaxPerEventLoop,
|
||
|
|
default: .Database.poolMaxPerEventLoop
|
||
|
|
)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
default:
|
||
|
|
return .inMemory
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The minimum log level the application emits at, read from the `log.level` key.
|
||
|
|
///
|
||
|
|
/// Falls back to `.info` when the key is unset or its value names no `Logger.Level` case.
|
||
|
|
var logLevel: Logger.Level {
|
||
|
|
string(
|
||
|
|
forKey: .Log.level,
|
||
|
|
as: Logger.Level.self,
|
||
|
|
default: .info
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Whether the executable runs in migrate-and-exit mode instead of serving, read from the `database.migrate` flag; off by default.
|
||
|
|
var migrate: Bool {
|
||
|
|
bool(
|
||
|
|
forKey: .Database.migrate,
|
||
|
|
default: false
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The security headers middleware configuration, built from the `security.*` keys.
|
||
|
|
///
|
||
|
|
/// Every header value has a default except `Strict-Transport-Security`, which is only sent when `security.strictTransportSecurity`
|
||
|
|
/// is set — the header is a commitment browsers cache, so it must be opted into for deployments actually served over HTTPS.
|
||
|
|
var securityHeaders: SecurityHeadersMiddleware<AppRequestContext>.Configuration {
|
||
|
|
.init(
|
||
|
|
contentSecurityPolicy: string(
|
||
|
|
forKey: .Security.contentSecurityPolicy,
|
||
|
|
default: .Security.contentSecurityPolicy
|
||
|
|
),
|
||
|
|
contentTypeOptions: string(
|
||
|
|
forKey: .Security.contentTypeOptions,
|
||
|
|
default: .Security.contentTypeOptions
|
||
|
|
),
|
||
|
|
frameOptions: string(
|
||
|
|
forKey: .Security.frameOptions,
|
||
|
|
default: .Security.frameOptions
|
||
|
|
),
|
||
|
|
referrerPolicy: string(
|
||
|
|
forKey: .Security.referrerPolicy,
|
||
|
|
default: .Security.referrerPolicy
|
||
|
|
),
|
||
|
|
permissionsPolicy: string(
|
||
|
|
forKey: .Security.permissionsPolicy,
|
||
|
|
default: .Security.permissionsPolicy
|
||
|
|
),
|
||
|
|
strictTransportSecurity: string(
|
||
|
|
forKey: .Security.strictTransportSecurity
|
||
|
|
)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The name the server reports in its `Server` response header, read from the `http.serverName` key.
|
||
|
|
var serverName: String {
|
||
|
|
string(
|
||
|
|
forKey: .HTTP.serverName,
|
||
|
|
default: .Server.name
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The directory the static files are served from, read from the `path.staticFiles` key.
|
||
|
|
var staticFilesPath: String {
|
||
|
|
string(
|
||
|
|
forKey: .Path.staticFiles,
|
||
|
|
default: .Path.staticResources
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: - Helpers
|
||
|
|
|
||
|
|
private extension ConfigReader {
|
||
|
|
|
||
|
|
// MARK: Properties
|
||
|
|
|
||
|
|
/// The TLS posture for the MySQL connection, mapped from the `database.tls` key: `off` and `require` map to their postures, and any
|
||
|
|
/// other value falls back to `prefer`.
|
||
|
|
var tls: TLS {
|
||
|
|
switch string(
|
||
|
|
forKey: .Database.tls,
|
||
|
|
default: .Database.tls
|
||
|
|
) {
|
||
|
|
case .Database.tlsOff: .off
|
||
|
|
case .Database.tlsRequire: .require
|
||
|
|
default: .prefer
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|