diff --git a/.gitignore b/.gitignore index 9564a1a9..5173dc0f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ DerivedData *.ipa *.xcuserstate *.xcscmblueprint +*.resolved # CocoaPods # @@ -54,5 +55,12 @@ PerfectServer/perfectserverhttp # SwiftPM .build/ Packages/ -PerfectLib.xcodeproj/ +*.xcodeproj/ docs/ +.swiftpm/ +smtp.test.json + +.vscode/ +*.json +*.pem +*.sqlite* diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..b819d26f --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,32 @@ +cyclomatic_complexity: + - 64 # warning + - 128 # error +file_length: + - 2048 # warning + - 4096 # error +function_body_length: + - 128 # warning + - 256 # error +line_length: + - 256 # warning + - 512 # error +type_body_length: + - 512 # warning + - 1024 # error +disabled_rules: + - empty_enum_arguments + - function_parameter_count + - identifier_name + - inclusive_language + - large_tuple + - multiple_closures_with_trailing_closure + - nesting + - redundant_optional_initialization + - syntactic_sugar + - unused_optional_binding + - vertical_parameter_alignment + - void_return +excluded: + - .build/ + - Sources/PerfectHTTP/Mime*.swift + - Sources/PerfectHTTPServer/HTTP2/HPACK.swift \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f4ef4f6c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:20.04 +RUN apt-get update -y +RUN apt-get install -y wget +WORKDIR /tmp +ARG arch +COPY ./${arch}.url.txt /tmp/url.txt +RUN rm -rf /tmp/sw* +RUN wget -O /tmp/swift.tgz $(cat /tmp/url.txt) +RUN cd /tmp && tar xf /tmp/swift.tgz && rm -rf /tmp/swift.tgz && mv $(ls|grep swift) /tmp/swift/ +RUN cd /tmp/swift/usr/ && tar cf /tmp/sw.tar * +RUN cd /usr && tar xf /tmp/sw.tar +RUN rm -rf /tmp/sw* +RUN apt-get update -y +RUN apt-get install -y build-essential clang git +RUN apt-get install -y libcurl4-openssl-dev uuid-dev +RUN apt-get install -y libsqlite3-dev libncurses-dev +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libxml2-dev diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..e68406e0 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "COpenSSL", + "repositoryURL": "https://github.com/PerfectlySoft/Perfect-COpenSSL.git", + "state": { + "branch": null, + "revision": "ce3113e159b8c6d8565e5d8db2672b572c81aea9", + "version": "4.0.2" + } + }, + { + "package": "PerfectCZlib", + "repositoryURL": "https://github.com/RockfordWei/Perfect-CZlib-src.git", + "state": { + "branch": null, + "revision": "8295883fd760f601a2c8c3236af83c8c35f941c6", + "version": "0.0.6" + } + }, + { + "package": "cURL", + "repositoryURL": "https://github.com/PerfectlySoft/Perfect-libcurl.git", + "state": { + "branch": null, + "revision": "b3d7e65ef5c27c0a027cdc621f34835975301bf1", + "version": "2.1.0" + } + }, + { + "package": "LinuxBridge", + "repositoryURL": "https://github.com/PerfectlySoft/Perfect-LinuxBridge.git", + "state": { + "branch": null, + "revision": "d6e64c48e6b06b6f1ab7ab9338280447baa8ca5c", + "version": "3.1.0" + } + }, + { + "package": "PerfectCSQLite3", + "repositoryURL": "https://github.com/PerfectlySoft/Perfect-sqlite3-support.git", + "state": { + "branch": null, + "revision": "64c2bd87e1fd3a41cdeeba073bab794db7e97e42", + "version": "3.1.1" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index b6623ef2..b8dfcbc2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,50 +1,92 @@ -// swift-tools-version:5.1 -// -// Package.swift -// PerfectLib -// -// Created by Kyle Jessup on 3/22/16. -// Copyright (C) 2016 PerfectlySoft, Inc. -// -//===----------------------------------------------------------------------===// -// -// This source file is part of the Perfect.org open source project -// -// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors -// Licensed under Apache License v2.0 -// -// See http://perfect.org/licensing.html for license information -// -//===----------------------------------------------------------------------===// -// +// swift-tools-version: 5.4 +// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription #if os(Linux) -let package = Package( - name: "PerfectLib", - products: [ - .library(name: "PerfectLib", targets: ["PerfectLib"]) - ], - dependencies: [.package(url: "https://github.com/PerfectlySoft/Perfect-LinuxBridge.git", from: "3.0.0")], - targets: [ - .target(name: "PerfectLib", dependencies: ["LinuxBridge"]), - .testTarget(name: "PerfectLibTests", dependencies: ["PerfectLib"]) - ] -) +let pkdep: [Package.Dependency] = [ + .package(url: "https://github.com/PerfectlySoft/Perfect-LinuxBridge.git", from: "3.1.0"), + .package(url: "https://github.com/PerfectlySoft/Perfect-sqlite3-support.git", from: "3.1.1") +] + +let sqlite3dep: [Target.Dependency] = [ + .product(name: "PerfectCSQLite3", package: "Perfect-sqlite3-support") +] + +let osdep: [Target.Dependency] = sqlite3dep + [.product(name: "LinuxBridge", package: "Perfect-LinuxBridge")] +let sqldep: [Target.Dependency] = sqlite3dep + [.init(stringLiteral: "PerfectCRUD")] #else +let pkdep: [Package.Dependency] = [] +let osdep: [Target.Dependency] = [] +let sqldep: [Target.Dependency] = ["PerfectCRUD"] +#endif + let package = Package( - name: "PerfectLib", - platforms: [ - .macOS(.v10_15) - ], - products: [ - .library(name: "PerfectLib", targets: ["PerfectLib"]) - ], - dependencies: [], - targets: [ - .target(name: "PerfectLib", dependencies: []), - .testTarget(name: "PerfectLibTests", dependencies: ["PerfectLib"]) - ] + name: "Perfect", + products: [ + .library(name: "PerfectAuth", targets: ["PerfectAuth"]), + .library(name: "PerfectCRUD", targets: ["PerfectCRUD"]), + .library(name: "PerfectCrypto", targets: ["PerfectCrypto"]), + .library(name: "PerfectCURL", targets: ["PerfectCURL"]), + .library(name: "PerfectLib", targets: ["PerfectLib"]), + .library(name: "PerfectHTTP", targets: ["PerfectHTTP"]), + .library(name: "PerfectHTTPServer", targets: ["PerfectHTTPServer"]), + .library(name: "PerfectMustache", targets: ["PerfectMustache"]), + .library(name: "PerfectNet", targets: ["PerfectNet"]), + .library(name: "PerfectSMTP", targets: ["PerfectSMTP"]), + .library(name: "PerfectSQLite", targets: ["PerfectSQLite"]), + .library(name: "PerfectThread", targets: ["PerfectThread"]), + .executable(name: "httpd", targets: ["httpd"]) + ], + dependencies: pkdep + [ + .package(url: "https://github.com/PerfectlySoft/Perfect-libcurl.git", from: "2.0.0"), + .package(url: "https://github.com/PerfectlySoft/Perfect-COpenSSL.git", from: "4.0.2"), + .package(url: "https://github.com/RockfordWei/Perfect-CZlib-src.git", from: "0.0.6") + ], + targets: [ + .target(name: "PerfectAuth", dependencies: ["PerfectCrypto", "PerfectCRUD", "PerfectSQLite"]), + .target(name: "PerfectCHTTPParser"), + .target(name: "PerfectLib", dependencies: osdep), + .target(name: "PerfectThread", dependencies: osdep), + .target(name: "PerfectCRUD"), + .target(name: "PerfectCrypto", dependencies: [ + .init(stringLiteral: "PerfectLib"), + .init(stringLiteral: "PerfectThread"), + .product(name: "COpenSSL", package: "Perfect-COpenSSL") + ]), + .target(name: "PerfectCURL", dependencies: [ + .product(name: "cURL", package: "Perfect-libcurl"), + .init(stringLiteral: "PerfectLib"), + .init(stringLiteral: "PerfectThread") + ]), + .target(name: "PerfectHTTP", dependencies: ["PerfectLib", "PerfectNet"]), + .target(name: "PerfectHTTPServer", dependencies: [ + .init(stringLiteral: "PerfectCHTTPParser"), + .init(stringLiteral: "PerfectCrypto"), + .init(stringLiteral: "PerfectNet"), + .init(stringLiteral: "PerfectHTTP"), + .product(name: "PerfectCZlib", package: "Perfect-CZlib-src") + ]), + .target(name: "PerfectMustache", dependencies: ["PerfectLib"]), + .target(name: "PerfectNet", dependencies: ["PerfectCrypto", "PerfectThread"]), + .target(name: "PerfectSMTP", dependencies: ["PerfectCURL", "PerfectCrypto", "PerfectHTTP"]), + .target(name: "PerfectSQLite", dependencies: sqldep), + .testTarget(name: "PerfectAuthTests", dependencies: [ + "PerfectAuth", "PerfectCRUD", "PerfectCrypto", "PerfectLib", "PerfectSQLite" + ]), + .testTarget(name: "PerfectCryptoTests", dependencies: ["PerfectCrypto"]), + .testTarget(name: "PerfectCURLTests", dependencies: ["PerfectCURL"]), + .testTarget(name: "PerfectHTTPTests", dependencies: ["PerfectHTTP"]), + .testTarget(name: "PerfectHTTPServerTests", dependencies: ["PerfectHTTPServer"]), + .testTarget(name: "PerfectLibTests", dependencies: ["PerfectLib"]), + .testTarget(name: "PerfectMustacheTests", dependencies: ["PerfectMustache"]), + .testTarget(name: "PerfectNetTests", dependencies: ["PerfectNet"]), + .testTarget(name: "PerfectSMTPTests", dependencies: ["PerfectSMTP"]), + .testTarget(name: "PerfectSQLiteTests", dependencies: ["PerfectSQLite"]), + .testTarget(name: "PerfectThreadTests", dependencies: ["PerfectThread"]), + .executableTarget(name: "httpd", dependencies: [ + "PerfectAuth", "PerfectCrypto", "PerfectLib", "PerfectHTTPServer", "PerfectHTTP", + "PerfectMustache", "PerfectSMTP", "PerfectSQLite" + ]) + ] ) -#endif diff --git a/README.md b/README.md index e5b0ab46..190d3ad6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@
+**OS**|**Version**|**Chip**|**Status** +--|-------|----|------ +Ventura|macOS 13.6|Apple Silicon M2| +Ubuntu|22.04 LTS|i386| +Ubuntu|22.04 LTS|arm64| +**Package**|**Status**|**Package**|**Status** +PerfectLib||PerfectThread| +PerfectAuth||PerfectCRUD| +PerfectCrypto||PerfectCURL| +PerfectHTTP||PerfectHTTPServer| +PerfectMustache||PerfectNet| +PerfectSMTP||PerfectSQLite| + ## Perfect: Server-Side Swift Perfect is a complete and powerful toolbox, framework, and application server for Linux, iOS, and macOS (OS X). It provides everything a Swift engineer needs for developing lightweight, maintainable, and scalable apps and other REST services entirely in the Swift programming language for both client-facing and server-side applications. @@ -53,7 +66,6 @@ Your Perfect project can be deployed to any Swift compatible Linux server. We pr Our library continues to grow as members of [the Swift-Perfect development community have shared many samples and examples](https://github.com/PerfectExamples) of their projects in Perfect. Examples include: -- [WebSockets Server](https://github.com/PerfectExamples/PerfectExample-WebSocketsServer) - [URL Routing](https://github.com/PerfectExamples/PerfectExample-URLRouting) - [Upload Enumerator](https://github.com/PerfectExamples/PerfectExample-UploadEnumerator) @@ -90,7 +102,6 @@ Perfect project is divided into several repositories to make it easy for you to - [Perfect HTTP Server](https://github.com/PerfectlySoft/Perfect-HTTPServer) - HTTP 1.1 server for Perfect - [Perfect Mustache](https://github.com/PerfectlySoft/Perfect-Mustache) - Mustache template support for Perfect - [Perfect CURL](https://github.com/PerfectlySoft/Perfect-CURL) - cURL support for Perfect -- [Perfect WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) - WebSockets support for Perfect - [Perfect Zip](https://github.com/PerfectlySoft/Perfect-Zip) - provides simple zip and unzip functionality - [Perfect Notifications](https://github.com/PerfectlySoft/Perfect-Notifications) - provides support for Apple Push Notification Service (APNS). @@ -98,7 +109,7 @@ Perfect project is divided into several repositories to make it easy for you to Perfect operates using either a standalone [HTTP server](https://github.com/PerfectlySoft/Perfect-HTTP), [HTTPS server](https://github.com/PerfectlySoft/Perfect-HTTPServer), or through [FastCGI server](https://github.com/PerfectlySoft/Perfect-FastCGI). It provides a system for loading your Swift-based modules at startup, for interfacing those modules with its request/response objects, or to the built-in [Mustache template processing system](https://github.com/PerfectlySoft/Perfect-Mustache). -Perfect is built on a completely asynchronous, high-performance networking engine to provide a scalable option for internet services. It supports Secure Sockets Layer (SSL) encryption, and it features a suite of tools commonly required by internet servers such as [WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) and [iOS push notifications](https://github.com/PerfectlySoft/Perfect-Notifications), but you are not limited to those options. +Perfect is built on a completely asynchronous, high-performance networking engine to provide a scalable option for internet services. It supports Secure Sockets Layer (SSL) encryption, and it features a suite of tools commonly required by internet servers such as [iOS push notifications](https://github.com/PerfectlySoft/Perfect-Notifications), but you are not limited to those options. Feel free to use your favourite JSON or templating systems, etc. diff --git a/Sources/PerfectAuth/Nonce.swift b/Sources/PerfectAuth/Nonce.swift new file mode 100644 index 00000000..d16c5700 --- /dev/null +++ b/Sources/PerfectAuth/Nonce.swift @@ -0,0 +1,47 @@ +// +// Nonce.swift +// +// +// Created by Rockford Wei on 2022-06-27. +// + +import Foundation +import PerfectCrypto + +/// Nonce is a special server allocated JWT which can be typically used to check if a post is valid. +/// For example, any post method should include a valid nonce before action, so if not, the server can just simply ignore it. +public struct Nonce { + fileprivate struct Payload: Codable { + let host: UUID + let timestamp: TimeInterval + init(host h: UUID, timestamp t: TimeInterval = Date().timeIntervalSince1970) { + host = h; timestamp = t + } + } + + fileprivate static let algo = JWT.Alg.hs256 + fileprivate static let host = UUID() + + /// allocate a nonce string + public static func allocate(authorityPrivateKey: PEMKey) throws -> String { + let payload = Payload(host: host) + return try JWTCreator(payload: payload).sign(alg: algo, key: authorityPrivateKey) + } + + /// check if this nonce is valid + public static func validate(nonce: String, seconds: Int = 900, authorityPublicKey: PEMKey) throws { + // swiftlint:disable type_name + typealias exception = AuthenticationTokenClaim.Exception + guard let jwt = JWTVerifier(nonce) else { + throw exception.invalidJsonWebToken + } + try jwt.verify(algo: algo, key: authorityPublicKey) + let payload = try jwt.decode(as: Payload.self) + guard payload.host == host else { + throw exception.invalidHostKey + } + guard payload.timestamp + TimeInterval(seconds) > Date().timeIntervalSince1970 else { + throw exception.expired + } + } +} diff --git a/Sources/PerfectAuth/PerfectAuth.swift b/Sources/PerfectAuth/PerfectAuth.swift new file mode 100644 index 00000000..f038e77e --- /dev/null +++ b/Sources/PerfectAuth/PerfectAuth.swift @@ -0,0 +1,36 @@ +// +// Based on SAuth.swift +// [SAuthLib](https://github.com/kjessup/SAuthLib) +// +// Created by Kyle Jessup on 2018-02-26. +// Digested by Rockford Wei on 2022-06-23. +// + +import Foundation +import PerfectCrypto + +open class AuthenticationUtilities { + public static func hash(password: String) -> (hexSalt: String, hexHash: String)? { + let saltBytes = Array