Skip to content

Commit b1e91df

Browse files
committed
wip: improve website location, refactor auth
1 parent 65b8256 commit b1e91df

File tree

5 files changed

+146
-129
lines changed

5 files changed

+146
-129
lines changed

Sources/App/Middlewares/SiteMiddleware.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ struct SiteMiddleware<Context: RequestContext>: RouterController {
2929
case .api(.activity(.all)):
3030
return try JSONEncoder().encode(self.activityClient.activity(), from: req, context: ctx)
3131
case let .api(.activity(.location(location))):
32-
guard let auth = req.headers[.authorization].flatMap({ try? Auth.parser.parse($0) }) else {
32+
guard let auth = req.headers.authorization else {
3333
throw HTTPError(.notFound)
3434
}
3535
try auth.validate()
3636
self.activityClient.updateLocation(location)
3737
return Response(status: .ok)
3838
case let .api(.activity(.nowPlaying(nowPlaying))):
39-
guard let auth = req.headers[.authorization].flatMap({ try? Auth.parser.parse($0) }) else {
39+
guard let auth = req.headers.authorization else {
4040
throw HTTPError(.notFound)
4141
}
4242
try auth.validate()

Sources/App/Utils/Auth.swift

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,7 @@ import Foundation
33
import Dependencies
44
import Parsing
55

6-
enum Auth: Sendable, Equatable {
7-
/// Token
8-
case bearer(String)
9-
10-
/// base64-encoded credentials
11-
case basic(String, String)
12-
13-
/// sha256-algorithm
14-
case digest(String)
15-
16-
static var parser: some Parser<Substring, Auth> {
17-
OneOf {
18-
Parse(.case(Auth.bearer)) {
19-
"Bearer "
20-
Rest().map(.string)
21-
}
22-
23-
Parse(.case(Auth.basic)) {
24-
"Basic "
25-
26-
Rest().map(Base64SubstringToSubstring()).pipe {
27-
Prefix { $0 != ":" }.map(.string)
28-
":"
29-
Rest().map(.string)
30-
}
31-
}
32-
33-
Parse(.case(Auth.digest)) {
34-
"Digest "
35-
Rest().map(.string)
36-
}
37-
}
38-
}
39-
6+
extension HTTPFields.Authorization {
407
func validate() throws {
418
@Dependency(\.envVar) var env
429
switch self {
@@ -47,22 +14,4 @@ enum Auth: Sendable, Equatable {
4714
default: throw HTTPError(.notFound)
4815
}
4916
}
50-
}
51-
52-
struct Base64SubstringToSubstring: Conversion {
53-
@usableFromInline
54-
init() {}
55-
56-
@inlinable
57-
func apply(_ input: Substring) -> Substring {
58-
Data(base64Encoded: String(input)).flatMap {
59-
String(data: $0, encoding: .utf8)?[...]
60-
} ?? ""
61-
}
62-
63-
@inlinable
64-
func unapply(_ output: Substring) -> Substring {
65-
output.data(using: .utf8)?
66-
.base64EncodedString()[...] ?? ""
67-
}
6817
}

Sources/Pages/HomePage.swift

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,38 @@ public struct HomePage: Page {
1111
let svgIcon = Class(module: "svgIcon")
1212

1313
var body: some Rule {
14-
container => {
15-
Display(.flex)
16-
Background("#1c1c1c")
17-
Color(.white)
18-
// AnyProperty("justify-self", "center")
14+
Pseudo(class: .root) => {
15+
Variable("pico-background-color", value: "#1c1c1c")
1916
}
2017

21-
container > Class("main") => {
18+
// self.container => {
19+
// Display(.flex)
20+
// Background("#1c1c1c")
21+
// Color(.white)
22+
// // AnyProperty("justify-self", "center")
23+
// }
24+
25+
// self.container > Class("main") => {
2226
// Width(.full)
2327
// Height(.full)
2428

2529
// Media(maxWidth: 2xl) => {
2630
// MaxWidth(.rem(20))
2731
// }
28-
}
29-
30-
// .svg-icon {
31-
// display: inline-block;
32-
// vertical-align: middle;
33-
// position: relative;
34-
// bottom: 0.125em;
35-
// height: 1em;
36-
// width: 1em;
3732
// }
38-
svgIcon => {
33+
34+
self.svgIcon => {
3935
Display(.inlineBlock)
36+
AnyProperty("vertical-align", "middle")
37+
AnyProperty("position", "relative")
38+
AnyProperty("bottom", "0.125em")
39+
AnyProperty("width", "1em")
40+
AnyProperty("height", "1em")
41+
AnyProperty("margin-right", "0.25rem")
42+
}
43+
44+
Class("reversed") => {
45+
AnyProperty("scale", "calc(100% * -1) 100%")
4046
}
4147
}
4248
}
@@ -64,77 +70,76 @@ public struct HomePage: Page {
6470
meta(.charset(.utf8))
6571
meta(name: .viewport, content: "width=device-width, initial-scale=1.0")
6672
link(.rel(.stylesheet), .href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"))
67-
// style(styling)
73+
style(styling)
6874
// script(.src("https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"), .defer) {}
6975
}
70-
body {
71-
main(.class("container")) {
72-
section(.class("p-8 pb-8"), .ariaLabel("About")) {
73-
h1(.class("font-bold")) { "Erik Bautista Santibanez" }
74-
p(.class("opacity-70")) { "Swift & Web Developer" }
76+
body(.class("container")) {
77+
header(.ariaLabel("About")) {
78+
hgroup {
79+
h1 { "Erik Bautista Santibanez" }
80+
p { "Swift & Web Developer" }
7581

7682
let location = self.activityClient.location()
7783
let residency = location?.residency ?? .default
7884

79-
p(.ariaLabel("Residency")) {
80-
svg(.xmlns(), .fill("currentColor"), .viewBox("0 0 256 256"), .class("opacity-70 svg-icon mr-1"), .ariaLabel("Map pin icon")) {
85+
p(.ariaLabel("Location")) {
86+
svg(.xmlns(), .fill("currentColor"), .viewBox("0 0 256 256"), .ariaLabel("Map pin icon"), .class(styling.svgIcon)) {
8187
path(
8288
.d("M128,64a40,40,0,1,0,40,40A40,40,0,0,0,128,64Zm0,64a24,24,0,1,1,24-24A24,24,0,0,1,128,128Zm0-112a88.1,88.1,0,0,0-88,88c0,31.4,14.51,64.68,42,96.25a254.19,254.19,0,0,0,41.45,38.3,8,8,0,0,0,9.18,0A254.19,254.19,0,0,0,174,200.25c27.45-31.57,42-64.85,42-96.25A88.1,88.1,0,0,0,128,16Zm0,206c-16.53-13-72-60.75-72-118a72,72,0,0,1,144,0C200,161.23,144.53,209,128,222Z")
8389
)
8490
}
8591

8692
"\(residency)"
87-
}
8893

89-
if let location, location.city != residency.city || location.state != residency.state {
90-
p(.ariaLabel("Location"), .class("italic pt-1")) {
91-
svg(.xmlns(), .fill("currentColor"), .viewBox("0 0 256 256"), .class("-scale-x-100 svg-icon mr-1"), .ariaLabel("Navigation icon")) {
94+
if let location, location.city != residency.city || location.state != residency.state {
95+
" \u{2022} "
96+
svg(.xmlns(), .fill("currentColor"), .viewBox("0 0 256 256"), .class(styling.svgIcon), .class("reversed"), .ariaLabel("Navigation icon")) {
9297
path(.d("M234.35,129,152,152,129,234.35a8,8,0,0,1-15.21.27l-65.28-176A8,8,0,0,1,58.63,48.46l176,65.28A8,8,0,0,1,234.35,129Z"))
9398
path(.d("M237.33,106.21,61.41,41l-.16-.05A16,16,0,0,0,40.9,61.25a1,1,0,0,0,.05.16l65.26,175.92A15.77,15.77,0,0,0,121.28,248h.3a15.77,15.77,0,0,0,15-11.29l.06-.2,21.84-78,78-21.84.2-.06a16,16,0,0,0,.62-30.38ZM149.84,144.3a8,8,0,0,0-5.54,5.54L121.3,232l-.06-.17L56,56l175.82,65.22.16.06Z"))
9499
}
95100

96-
span(.class("opacity-60")) { "Currently in " }
101+
span { "Currently in " }
97102
[location.city, location.state, location.region == "United States" ? nil : location.region]
98103
.compactMap(\.self)
99104
.joined(separator: ", ")
100105
}
101106
}
102107
}
103-
104-
section(.class("px-8 pb-8"), .ariaLabel("Experience")) {
105-
h1(.class("font-bold")) { "Experience" }
108+
}
109+
main {
110+
section(.ariaLabel("Experience")) {
111+
h3 { "Experience" }
106112
}
107113

108-
section(.class("px-8 pb-8"), .ariaLabel("Projects")) {
109-
h1(.class("font-bold")) { "Projects" }
114+
section(.ariaLabel("Projects")) {
115+
h3 { "Projects" }
110116
}
111117

112-
section(.class("px-8 pb-8"), .ariaLabel("Education")) {
113-
h1(.class("font-bold")) { "Education" }
114-
div(.class("flex flex-col md:flex-row")) {
115-
div(.class("flex-1")) {
116-
h2(.class("font-medium")) {
117-
a(.target(.blank), .rel("noopener noreferrer"), .href("https://stedwards.edu/")) {
118-
"St. Edward's University"
118+
section(.ariaLabel("Education")) {
119+
h3 { "Education" }
120+
div {
121+
div {
122+
h6 {
123+
a(.target(.blank), .rel("noopener noreferrer"), .href("https://stedwards.edu/")) {
124+
"St. Edward's University"
119125
}
120126
}
121-
p(.class("opacity-80")) { "Bachelor of Science in Computer Science" }
127+
p { "Bachelor of Science in Computer Science" }
122128
}
123129

124-
p(.class("md:self-baseline opacity-80")) { "2018-2023" }
130+
p { "2018-2023" }
125131
}
126132
}
127-
128-
footer(.class("px-8 pb-16 text-xs opacity-70"), .ariaLabel("Credits")) {
129-
hr(.class("pb-6 border-top border-neutral-600"))
130-
p(.class("px-6 text-center")) { "©\(self.year) Erik Bautista Santibanez" }
131-
p(.class("pt-2 px-6 text-center")) {
132-
"Made with \u{2764} using "
133-
a(.target(.blank), .rel("noopener noreferrer"), .href("https://swift.org")) { "Swift" }
134-
" + "
135-
a(.target(.blank), .rel("noopener noreferrer"), .href("https://hummingbird.codes")) { "Hummingbird" }
136-
"."
137-
}
133+
}
134+
footer(.ariaLabel("Credits")) {
135+
hr()
136+
p { "©\(self.year) Erik Bautista Santibanez" }
137+
p {
138+
"Made with \u{2764} using "
139+
a(.target(.blank), .rel("noopener noreferrer"), .href("https://swift.org")) { "Swift" }
140+
" + "
141+
a(.target(.blank), .rel("noopener noreferrer"), .href("https://hummingbird.codes")) { "Hummingbird" }
142+
"."
138143
}
139144
}
140145
}
@@ -144,7 +149,7 @@ public struct HomePage: Page {
144149

145150
// private enum PackageTheme: Theme {
146151
// static var className: String? { "swift-package-code-block" }
147-
//
152+
//
148153
// static func style(for tokenSyntax: TokenSyntax) -> [Token] {
149154
// switch tokenSyntax.tokenKind {
150155
// case .keyword: [Token("\(tokenSyntax)", className: "text-blue-400")]

Sources/Routes/Utils/URLRequestData+Hummingbird.swift

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import struct Hummingbird.Request
33
import struct HTTPTypes.HTTPFields
44
import struct Foundation.Data
55
import struct NIO.ByteBuffer
6+
import Parsing
67

78
public extension URLRequestData {
89
init(request: Hummingbird.Request) async {
@@ -16,11 +17,13 @@ public extension URLRequestData {
1617
body = nil
1718
}
1819

20+
let authorization = request.headers.basicAuthorization
21+
1922
self.init(
2023
method: request.method.rawValue,
2124
scheme: request.uri.scheme?.rawValue,
22-
user: request.headers.basicAuthorization?.0,
23-
password: request.headers.basicAuthorization?.1,
25+
user: authorization?.0,
26+
password: authorization?.1,
2427
host: request.uri.host,
2528
port: request.uri.port,
2629
path: request.uri.path,
@@ -46,27 +49,83 @@ public extension URLRequestData {
4649

4750
extension HTTPFields {
4851
public var basicAuthorization: (String, String)? {
52+
if case let .basic(username, password) = self.authorization {
53+
return (username, password)
54+
} else {
55+
return nil
56+
}
57+
}
58+
59+
public var authorization: Authorization? {
4960
if let string = self[.authorization] {
50-
let headerParts = string
51-
.split(separator: " ", maxSplits: 1, omittingEmptySubsequences: false)
61+
try? Authorization.parser.parse(string)
62+
} else {
63+
nil
64+
}
65+
}
5266

53-
guard headerParts.count == 2 else {
54-
return nil
55-
}
56-
guard headerParts[0].lowercased() == "basic" else {
57-
return nil
58-
}
59-
guard let decodedToken = Data(base64Encoded: String(headerParts[1])) else {
60-
return nil
61-
}
62-
let parts = String(decoding: decodedToken, as: UTF8.self)
63-
.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
64-
guard parts.count == 2 else {
65-
return nil
67+
public enum Authorization: Sendable, Equatable {
68+
/// Token
69+
case bearer(String)
70+
71+
/// base64-encoded credentials
72+
case basic(String, String)
73+
74+
/// sha256-algorithm
75+
case digest(String)
76+
77+
static var parser: some Parser<Substring, Self> {
78+
OneOf {
79+
Parse(.case(Self.bearer)) {
80+
OneOf {
81+
"Bearer"
82+
"bearer"
83+
}
84+
" "
85+
Rest().map(.string)
86+
}
87+
88+
Parse(.case(Self.basic)) {
89+
OneOf {
90+
"Basic"
91+
"basic"
92+
}
93+
" "
94+
95+
Rest().map(Base64EncodedSubstringToSubstring()).pipe {
96+
Prefix { $0 != ":" }.map(.string)
97+
":"
98+
Rest().map(.string)
99+
}
100+
}
101+
102+
Parse(.case(Self.digest)) {
103+
OneOf {
104+
"Digest"
105+
"digest"
106+
}
107+
" "
108+
Rest().map(.string)
109+
}
66110
}
67-
return (String(parts[0]), String(parts[1]))
68-
} else {
69-
return nil
70111
}
71112
}
72113
}
114+
115+
private struct Base64EncodedSubstringToSubstring: Conversion {
116+
@usableFromInline
117+
init() {}
118+
119+
@inlinable
120+
func apply(_ input: Substring) -> Substring {
121+
Data(base64Encoded: String(input)).flatMap {
122+
String(decoding: $0, as: UTF8.self)[...]
123+
} ?? ""
124+
}
125+
126+
@inlinable
127+
func unapply(_ output: Substring) -> Substring {
128+
output.data(using: .utf8)?
129+
.base64EncodedString()[...] ?? ""
130+
}
131+
}

0 commit comments

Comments
 (0)