Skip to content

Commit 875408d

Browse files
authored
Merge pull request #18 from tayloraswift/https-everywhere
HTTPS everywhere
2 parents 746f96c + 676b058 commit 875408d

30 files changed

+309
-343
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
.vscode
66

77
Assets/secrets/
8+
TestCertificates/*.pem
89
TestDeployment/data/
910

1011
node_modules/

Assets/css/Main.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Guides/GeneratingCertificates.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Generating TLS certificates
2+
3+
To simplify the implementation and facilitate testing, Unidoc uses TLS (HTTPS) everywhere, even when running locally.
4+
5+
For a seamless development experience, we recommend using [mkcert](https://github.com/FiloSottile/mkcert) to generate a local certificate authority (CA) for your development environment.
6+
7+
## Installing mkcert
8+
9+
The easiest way to install `mkcert` is to download one of its prebuilt binaries.
10+
11+
```bash
12+
$ curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
13+
chmod +x mkcert-v*-linux-amd64
14+
```
15+
16+
## Generating a local certificate authority
17+
18+
```bash
19+
$ ./mkcert-v1.4.4-linux-amd64 -install
20+
``````
21+
22+
## Generating a local certificate
23+
24+
If the `mkcert-v1.4.4-linux-amd64` binary is located in your home directory, you can generate a certificate for `localhost` by running the following from the repository root:
25+
26+
```bash
27+
$ cd TestCertificates
28+
$ ~/mkcert-v1.4.4-linux-amd64 localhost
29+
30+
```
31+
32+
Then, rename the generated files to `fullchain.pem` and `privkey.pem`.
33+
34+
```bash
35+
$ mv localhost.pem fullchain.pem
36+
$ mv localhost-key.pem privkey.pem
37+
```
38+
39+
You should now be able to run the Unidoc server locally and access it without browser warnings.
40+
41+
Keep in mind that the certificate is only valid for `localhost`; hostnames like `0.0.0.0` will still raise browser warnings.

Package.resolved

+4-4
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,17 @@
5959
"kind" : "remoteSourceControl",
6060
"location" : "https://github.com/tayloraswift/swift-mongodb",
6161
"state" : {
62-
"revision" : "b2086401145ed500924ea1c6217fec06c0b0a212",
63-
"version" : "0.8.2"
62+
"revision" : "49a0dc9c7130810714ce062165323b4e499326f4",
63+
"version" : "0.8.3"
6464
}
6565
},
6666
{
6767
"identity" : "swift-nio",
6868
"kind" : "remoteSourceControl",
6969
"location" : "https://github.com/apple/swift-nio.git",
7070
"state" : {
71-
"revision" : "a2e487b77f17edbce9a65f2b7415f2f479dc8e48",
72-
"version" : "2.57.0"
71+
"revision" : "cf281631ff10ec6111f2761052aa81896a83a007",
72+
"version" : "2.58.0"
7373
}
7474
},
7575
{

Package.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ let package:Package = .init(
7474
.package(url: "https://github.com/tayloraswift/swift-hash", .upToNextMinor(
7575
from: "0.5.0")),
7676
.package(url: "https://github.com/tayloraswift/swift-mongodb", .upToNextMinor(
77-
from: "0.8.2")),
77+
from: "0.8.3")),
7878

7979
.package(url: "https://github.com/apple/swift-atomics", .upToNextMinor(
8080
from: "1.1.0")),
8181
.package(url: "https://github.com/apple/swift-nio", .upToNextMinor(
82-
from: "2.57.0")),
82+
from: "2.58.0")),
8383
.package(url: "https://github.com/apple/swift-nio-http2", .upToNextMinor(
8484
from: "1.27.0")),
8585
.package(url: "https://github.com/apple/swift-nio-ssl", .upToNextMinor(
@@ -177,6 +177,7 @@ let package:Package = .init(
177177
.target(name: "HTML"),
178178
.target(name: "HTTP"),
179179
.product(name: "NIOHTTP1", package: "swift-nio"),
180+
.product(name: "NIOHTTP2", package: "swift-nio-http2"),
180181
.product(name: "NIOSSL", package: "swift-nio-ssl"),
181182
.product(name: "TraceableErrors", package: "swift-grammar"),
182183
]),

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align="center">
22

3-
<strong><em><code>unidoc</code></em></strong><br><small><code>0.2.3</code></small>
3+
<strong><em><code>unidoc</code></em></strong><br><small><code>0.2.4</code></small>
44

55
[![ci build status](https://github.com/kelvin13/swift-unidoc/actions/workflows/build.yml/badge.svg)](https://github.com/kelvin13/swift-unidoc/actions/workflows/build.yml)
66

@@ -104,6 +104,8 @@ Unidoc is tightly-integrated with Swiftinit, but you can run it locally to previ
104104

105105
Unidoc uses [MongoDB](https://www.mongodb.com/) to persist documentation. This allows for fast startup times as Unidoc performs almost no initialization, but requires you to have an active MongoDB replica set running on your local machine. See [`Testing.md`](Guides/Testing.md) for instructions on setting up a local environment.
106106

107+
Unidoc uses HTTPS everywhere. See [`GeneratingCertificates.md`](Guides/GeneratingCertificates.md) for how to generate a local and certificate and certificate authority for `localhost`.
108+
107109
TODO: Add example for running the Unidoc server locally.
108110

109111
TODO: Add example for invoking the Unidoc compiler.
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1+
import NIOSSL
2+
13
@frozen public
24
struct Localhost:ServerAuthority
35
{
4-
@inlinable internal
5-
init()
6+
public
7+
let tls:NIOSSLContext
8+
9+
@inlinable public
10+
init(tls:NIOSSLContext)
611
{
12+
self.tls = tls
713
}
814

915
@inlinable public static
10-
var scheme:ServerScheme { .http }
16+
var scheme:ServerScheme { .https }
1117
@inlinable public static
1218
var domain:String { "127.0.0.1" }
1319
}

Sources/HTTPServer/Authorities/ServerAuthority.swift

+4-16
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,24 @@ import HTML
22
import NIOCore
33
import NIOHTTP1
44
import NIOPosix
5+
import NIOSSL
56
import TraceableErrors
67

78
public
8-
protocol ServerAuthority<SecurityContext>:Sendable
9+
protocol ServerAuthority:Sendable
910
{
10-
associatedtype SecurityContext = Never
11-
1211
static
1312
var scheme:ServerScheme { get }
1413
static
1514
var domain:String { get }
1615

17-
var tls:SecurityContext? { get }
16+
var tls:NIOSSLContext { get }
1817

1918
static
2019
func redact(error:any Error) -> String
2120
}
22-
23-
extension ServerAuthority where Self == Localhost
24-
{
25-
@inlinable public static
26-
var localhost:Self { .init() }
27-
}
28-
29-
extension ServerAuthority<Never>
21+
extension ServerAuthority
3022
{
31-
@inlinable public
32-
var tls:SecurityContext? { nil }
3323
/// Dumps detailed information about the caught error. This information will be shown to
3424
/// *anyone* accessing the server. In production, we strongly recommend overriding this
3525
/// default implementation to avoid inadvertently exposing sensitive data via type
@@ -66,8 +56,6 @@ extension ServerAuthority
6656
{
6757
switch self.scheme
6858
{
69-
case .http(port: 80): return "http://\(self.domain)\(uri)"
70-
case .http(port: let port): return "http://\(self.domain):\(port)\(uri)"
7159
case .https(port: 443): return "https://\(self.domain)\(uri)"
7260
case .https(port: let port): return "https://\(self.domain):\(port)\(uri)"
7361
}

Sources/HTTPServer/Authorities/ServerScheme.swift

-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@
22
enum ServerScheme
33
{
44
case https(port:Int = 443)
5-
case http(port:Int = 80)
65
}
76
extension ServerScheme
87
{
98
@inlinable public static
109
var https:Self { .https() }
11-
12-
@inlinable public static
13-
var http:Self { .http() }
1410
}
1511
extension ServerScheme
1612
{
@@ -19,7 +15,6 @@ extension ServerScheme
1915
{
2016
switch self
2117
{
22-
case .http(port: let port): return port
2318
case .https(port: let port): return port
2419
}
2520
}
@@ -28,7 +23,6 @@ extension ServerScheme
2823
{
2924
switch self
3025
{
31-
case .http: return "http"
3226
case .https: return "https"
3327
}
3428
}

Sources/HTTPServer/Channels/HTTPServerDelegate.swift

+7-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import HTTP
22
import NIOCore
33
import NIOPosix
4-
import NIOHTTP1
4+
import NIOHTTP2
55
import NIOSSL
66

77
public
@@ -30,26 +30,14 @@ extension HTTPServerDelegate
3030
{
3131
(channel:any Channel) -> EventLoopFuture<Void> in
3232

33-
let endpoint:ServerInterfaceHandler<Authority, Self> = .init(
34-
address: channel.remoteAddress,
35-
server: self)
36-
37-
guard let tls:NIOSSLContext = authority.tls as? NIOSSLContext
38-
else
39-
{
40-
return channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true)
41-
.flatMap
42-
{
43-
channel.pipeline.addHandler(endpoint)
44-
}
45-
}
46-
return channel.pipeline.addHandler(NIOSSLServerHandler.init(context: tls))
47-
.flatMap
33+
channel.pipeline.addHandler(NIOSSLServerHandler.init(context: authority.tls))
34+
.flatMap
4835
{
49-
channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true)
50-
.flatMap
36+
channel.configureCommonHTTPServerPipeline
5137
{
52-
channel.pipeline.addHandler(endpoint)
38+
$0.pipeline.addHandler(ServerInterfaceHandler<Authority, Self>.init(
39+
address: channel.remoteAddress,
40+
server: self))
5341
}
5442
}
5543
}

Sources/SemanticVersions/SemanticVersion.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ extension SemanticVersion
1616
}
1717
}
1818

19-
@available(*, deprecated, message: "use 'patch' or switch explicitly instead")
19+
/// Returns true if this is a release version, false if it is a prerelease.
2020
@inlinable public
21-
var release:PatchVersion?
21+
var release:Bool
2222
{
2323
switch self
2424
{
25-
case .release(let version, _): return version
26-
case .prerelease: return nil
25+
case .release: return true
26+
case .prerelease: return false
2727
}
2828
}
2929
}

Sources/UnidocDB/Packages/PackageDatabase.Editions.swift

+9-17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import BSON
22
import GitHubAPI
33
import JSONEncoding
44
import MongoDB
5+
import SemanticVersions
56
import SHA1
67
import UnidocAnalysis
78
import UnidocRecords
@@ -107,20 +108,21 @@ extension PackageDatabase.Editions
107108
public
108109
func register(_ tag:__owned GitHub.Tag,
109110
package:Int32,
111+
version:SemanticVersion,
110112
with session:Mongo.Session) async throws -> Int32?
111113
{
112-
// We use the SHA-1 hash as “proof” that the edition has at least one symbol graph.
113-
// Therefore, merely registering tags does not update hashes.
114114
let placement:Placement = try await self.register(package: package,
115+
version: version,
115116
refname: tag.name,
116-
sha1: nil,
117+
sha1: tag.hash,
117118
with: session)
118119

119120
return placement.new ? placement.coordinate : nil
120121
}
121122

122123
func register(
123124
package:Int32,
125+
version:SemanticVersion,
124126
refname:String,
125127
sha1:SHA1?,
126128
with session:Mongo.Session) async throws -> Placement
@@ -134,6 +136,8 @@ extension PackageDatabase.Editions
134136
let edition:PackageEdition = .init(id: .init(
135137
package: package,
136138
version: placement.coordinate),
139+
release: version.release,
140+
patch: version.patch,
137141
name: refname,
138142
sha1: sha1)
139143

@@ -146,24 +150,12 @@ extension PackageDatabase.Editions
146150
{
147151
switch placement.sha1
148152
{
149-
case nil:
150-
// If the edition would gain a hash, we should update it.
151-
152-
// FIXME: this can race another update, in which case we will store an
153-
// arbitrary choice of hash without marking the edition dirty.
154-
// We should use `placement.sha1` as a hint to skip the update only,
155-
// and set the dirty flag within a custom update statement.
156-
try await self.update(some: edition, with: session)
157-
158153
case sha1?:
159154
// Nothing to do.
160155
break
161156

162-
case _?:
163-
try await self.update(field: PackageEdition[.lost],
164-
of: edition.id,
165-
to: true,
166-
with: session)
157+
case _:
158+
try await self.update(some: edition, with: session)
167159
}
168160
}
169161

Sources/UnidocDB/Packages/PackageDatabase.swift

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import GitHubAPI
22
import ModuleGraphs
33
import MongoDB
4+
import SemanticVersions
45
import SymbolGraphs
56
import UnidocAnalysis
67
import UnidocLinker
@@ -74,10 +75,12 @@ extension PackageDatabase
7475

7576
let version:Int32
7677

77-
if let commit:SymbolGraphMetadata.Commit = docs.metadata.commit
78+
if let commit:SymbolGraphMetadata.Commit = docs.metadata.commit,
79+
let semver:SemanticVersion = .init(refname: commit.refname)
7880
{
7981
let placement:Editions.Placement = try await self.editions.register(
8082
package: placement.coordinate,
83+
version: semver,
8184
refname: commit.refname,
8285
sha1: commit.hash,
8386
with: session)
@@ -87,11 +90,21 @@ extension PackageDatabase
8790
else if case .swift = docs.metadata.package,
8891
let tagname:String = docs.metadata.commit?.refname
8992
{
93+
// FIXME: we need a better way to handle this
94+
let semver:SemanticVersion
95+
switch tagname
96+
{
97+
case "swift-5.8.1-RELEASE": semver = .release(.v(5, 8, 1))
98+
case "swift-5.9-RELEASE": semver = .release(.v(5, 9, 0))
99+
case _:
100+
fatalError("unimplemented")
101+
}
90102
/// Standard library symbol graphs don’t come with hashes, so we can’t efficiently
91103
/// “prove” that a particular edition has at least one symbol graph. But we don’t
92104
/// need to query that in the first place.
93105
let placement:Editions.Placement = try await self.editions.register(
94106
package: placement.coordinate,
107+
version: semver,
95108
refname: tagname,
96109
sha1: nil,
97110
with: session)

0 commit comments

Comments
 (0)