Skip to content

Commit

Permalink
Update webauthn example for 2.x.x (#54)
Browse files Browse the repository at this point in the history
* Update webauthn for 2.x.x

* Using HummingbirdRouter

* Update README

* Missed a comment

* Fix hello app

* Update webauthn/README.md

Co-authored-by: Joannis Orlandos <[email protected]>

---------

Co-authored-by: Joannis Orlandos <[email protected]>
  • Loading branch information
adam-fowler and Joannis authored Jan 24, 2024
1 parent 6ab0ee0 commit 97aaf0b
Show file tree
Hide file tree
Showing 15 changed files with 318 additions and 246 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Examples converted to Hummingbird 2.0
- [todos-mongokitten-openapi](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/todos-mongokitten-openapi) - Todos application, using MongoDB driver [MongoKitten](https://github.com/orlandos-nl/MongoKitten) and the [OpenAPI runtime](https://github.com/apple/swift-openapi-runtime).
- [todos-postgres-tutorial](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/todos-postgres-tutorial) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using PostgresNIO. Sample code that goes along with the [Todos tutorial](https://hummingbird-project.github.io/hummingbird-docs/2.0/tutorials/todos).
- [upload](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/upload) - File uploading and downloading.
- [webauthn](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/webauthn) - Web app demonstrating WebAuthn(PassKey) authentication.

Examples still working with Hummingbird 1.0
- [auth-cognito](https://github.com/hummingbird-project/hummingbird-examples/tree/main/auth-cognito) - Authentication via AWS Cognito.
Expand All @@ -23,7 +24,6 @@ Examples still working with Hummingbird 1.0
- [todos-fluent](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-fluent) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using Fluent
- [todos-lambda](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-lambda) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB and running on AWS Lambda.
- [upload-s3](https://github.com/hummingbird-project/hummingbird-examples/tree/main/upload-s3) - File uploading and downloading using AWS S3 as backing store.
- [webauthn](https://github.com/hummingbird-project/hummingbird-examples/tree/main/webauthn) - Web app demonstrating WebAuthn(PassKey) authentication.
- [websocket-chat](https://github.com/hummingbird-project/hummingbird-examples/tree/main/websocket-chat) - Simple chat application using WebSockets.
- [websocket-echo](https://github.com/hummingbird-project/hummingbird-examples/tree/main/websocket-echo) - Simple WebSocket based echo server.

Expand Down
1 change: 0 additions & 1 deletion hello/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ let package = Package(
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Hummingbird", package: "hummingbird"),
.product(name: "HummingbirdFoundation", package: "hummingbird"),
],
swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of
Expand Down
14 changes: 7 additions & 7 deletions webauthn/Package.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
// swift-tools-version:5.6
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "webauthn",
platforms: [.macOS(.v12)],
platforms: [.macOS(.v14)],
products: [
.executable(name: "App", targets: ["App"]),
],
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "1.2.0"),
.package(url: "https://github.com/hummingbird-project/hummingbird-core.git", from: "1.0.0"),
.package(url: "https://github.com/hummingbird-project/hummingbird-auth.git", from: "1.2.0"),
.package(url: "https://github.com/hummingbird-project/hummingbird-fluent.git", from: "1.0.0"),
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0-alpha.1"),
.package(url: "https://github.com/hummingbird-project/hummingbird-auth.git", from: "2.0.0-alpha.1"),
.package(url: "https://github.com/hummingbird-project/hummingbird-fluent.git", from: "2.0.0-alpha.1"),
.package(url: "https://github.com/hummingbird-project/hummingbird-mustache.git", from: "1.0.0"),
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"),
.package(url: "https://github.com/swift-server/webauthn-swift.git", from: "1.0.0-alpha"),
Expand All @@ -26,10 +25,10 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
.product(name: "Hummingbird", package: "hummingbird"),
.product(name: "HummingbirdRouter", package: "hummingbird"),
.product(name: "HummingbirdAuth", package: "hummingbird-auth"),
.product(name: "HummingbirdFluent", package: "hummingbird-fluent"),
.product(name: "HummingbirdMustache", package: "hummingbird-mustache"),
.product(name: "HummingbirdTLS", package: "hummingbird-core"),
.product(name: "WebAuthn", package: "webauthn-swift"),
],
swiftSettings: [
Expand All @@ -43,6 +42,7 @@ let package = Package(
name: "AppTests",
dependencies: [
.byName(name: "App"),
.product(name: "Hummingbird", package: "hummingbird"),
.product(name: "HummingbirdXCT", package: "hummingbird"),
]
),
Expand Down
2 changes: 1 addition & 1 deletion webauthn/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# WebAuthn

Application demostrating authentication via WebAuthn passkeys. This example uses the webAuthn library https://github.com/swift-server/webauthn-swift.
Application demostrating authentication via WebAuthn passkeys. This example uses the webAuthn library https://github.com/swift-server/webauthn-swift. The sample also uses the result builder router `HBRouterBuilder` from HummingbirdRouter

In your browser go to `localhost:8080`.
15 changes: 4 additions & 11 deletions webauthn/Sources/App/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ArgumentParser
import Hummingbird

@main
struct App: ParsableCommand, AppArguments {
struct App: AsyncParsableCommand, AppArguments {
@Option(name: .shortAndLong)
var hostname: String = "127.0.0.1"

Expand All @@ -29,15 +29,8 @@ struct App: ParsableCommand, AppArguments {
var privateKey: String { "certs/server.key" }
var certificateChain: String { "certs/server.crt" }

func run() throws {
let app = HBApplication(
configuration: .init(
address: .hostname(self.hostname, port: self.port),
serverName: "Hummingbird"
)
)
try app.configure(self)
try app.start()
app.wait()
func run() async throws {
let app = try await buildApplication(self)
try await app.runService()
}
}
91 changes: 91 additions & 0 deletions webauthn/Sources/App/Application+build.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2023 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import FluentSQLiteDriver
import Foundation
import Hummingbird
import HummingbirdAuth
import HummingbirdFluent
import HummingbirdMustache
import HummingbirdRouter
import WebAuthn

/// Application arguments protocol. We use a protocol so we can call
/// `HBApplication.configure` inside Tests as well as in the App executable.
/// Any variables added here also have to be added to `App` in App.swift and
/// `TestArguments` in AppTest.swift
public protocol AppArguments {
var hostname: String { get }
var port: Int { get }
var inMemoryDatabase: Bool { get }
var certificateChain: String { get }
var privateKey: String { get }
}

func buildApplication(_ arguments: AppArguments) async throws -> some HBApplicationProtocol {
var logger = Logger(label: "webauthn")
logger.logLevel = .debug

let fluent = HBFluent(logger: logger)
// add sqlite database
if arguments.inMemoryDatabase {
fluent.databases.use(.sqlite(.memory), as: .sqlite)
} else {
fluent.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
}
await fluent.migrations.add(CreateUser())
await fluent.migrations.add(CreateWebAuthnCredential())
try await fluent.migrate()

// sessions are stored in memory
let memoryPersist = HBMemoryPersistDriver()
let sessionStorage = HBSessionStorage(memoryPersist)

// load mustache template library
let library = try HBMustacheLibrary(directory: "templates")
assert(library.getTemplate(named: "home") != nil, "Set your working directory to the root folder of this example to get it to work")

let router = HBRouterBuilder(context: WebAuthnRequestContext.self) {
// add logging middleware
HBLogRequestsMiddleware(.info)
// add file middleware to server HTML files
HBFileMiddleware(searchForIndexHtml: true, logger: logger)
// health check endpoint
Get("/health") { _, _ -> HTTPResponse.Status in
return .ok
}
HTMLController(
mustacheLibrary: library,
fluent: fluent,
sessionStorage: sessionStorage
).endpoints
RouteGroup("api") {
HBWebAuthnController(
webauthn: .init(
config: .init(
relyingPartyID: "localhost",
relyingPartyName: "Hummingbird WebAuthn example",
relyingPartyOrigin: "http://localhost:8080"
)
),
fluent: fluent,
sessionStorage: sessionStorage
).endpoints
}
}

var app = HBApplication(router: router)
app.addServices(fluent, memoryPersist)
return app
}
83 changes: 0 additions & 83 deletions webauthn/Sources/App/Application+configure.swift

This file was deleted.

44 changes: 30 additions & 14 deletions webauthn/Sources/App/Controllers/HTMLController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,61 @@
//===----------------------------------------------------------------------===//

import Hummingbird
import HummingbirdAuth
import HummingbirdFluent
import HummingbirdMustache
import HummingbirdRouter

/// Redirects to login page if no user has been authenticated
struct RedirectMiddleware: HBMiddleware {
struct RedirectMiddleware<Context: HBAuthRequestContextProtocol>: HBMiddlewareProtocol {
let to: String
func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse> {
if request.authHas(User.self) {
return next.respond(to: request)
func handle(_ request: HBRequest, context: Context, next: (HBRequest, Context) async throws -> HBResponse) async throws -> HBResponse {
// check if authenticated
if context.auth.has(AuthenticatedUser.self) {
return try await next(request, context)
} else {
return request.eventLoop.makeSucceededFuture(.redirect(to: "\(self.to)?from=\(request.uri)", type: .found))
// if not authenticated then redirect to login page
return .redirect(to: "\(self.to)?from=\(request.uri)", type: .found)
}
}
}

/// Serves HTML pages
struct HTMLController {
typealias Context = WebAuthnRequestContext

let homeTemplate: HBMustacheTemplate
let fluent: HBFluent
let sessionStorage: HBSessionStorage

init(mustacheLibrary: HBMustacheLibrary) {
init(
mustacheLibrary: HBMustacheLibrary,
fluent: HBFluent,
sessionStorage: HBSessionStorage
) {
// get the mustache templates from the library
guard let homeTemplate = mustacheLibrary.getTemplate(named: "home")
else {
preconditionFailure("Failed to load mustache templates")
}
self.homeTemplate = homeTemplate
self.fluent = fluent
self.sessionStorage = sessionStorage
}

/// Add routes for webpages
func addRoutes(to router: HBRouterBuilder) {
router.group()
.add(middleware: WebAuthnSessionAuthenticator())
.add(middleware: RedirectMiddleware(to: "/login.html"))
.get("/", use: self.home)
// return Route for home page
var endpoints: some HBMiddlewareProtocol<Context> {
Get("/") {
WebAuthnSessionAuthenticator(fluent: self.fluent, sessionStorage: self.sessionStorage)
RedirectMiddleware(to: "/login.html")
self.home
}
}

/// Home page listing todos and with add todo UI
func home(request: HBRequest) async throws -> HTML {
@Sendable func home(request: HBRequest, context: Context) async throws -> HTML {
// get user
let user = try request.authRequire(User.self)
let user = try context.auth.require(AuthenticatedUser.self)
// Render home template and return as HTML
let object: [String: Any] = [
"name": user.username,
Expand Down
Loading

0 comments on commit 97aaf0b

Please sign in to comment.