From c6af2c8a9879fcb25dc65332cbfaf762f0d78f64 Mon Sep 17 00:00:00 2001 From: Roberto Hidalgo Date: Wed, 4 Jan 2023 13:44:20 -0600 Subject: [PATCH] use different webauthn for the browser --- internal/server/admin.html | 2 +- internal/server/index.html | 2 +- internal/server/server.go | 12 ++++++--- internal/server/static/webauthn.js | 43 +++++++++++++++++++++++------- internal/user/user.go | 2 +- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/internal/server/admin.html b/internal/server/admin.html index 5d4e19f..382bf61 100644 --- a/internal/server/admin.html +++ b/internal/server/admin.html @@ -234,7 +234,7 @@ - + diff --git a/internal/server/index.html b/internal/server/index.html index 500d936..24ba3cf 100644 --- a/internal/server/index.html +++ b/internal/server/index.html @@ -22,7 +22,7 @@ - + diff --git a/internal/server/server.go b/internal/server/server.go index 90d369c..2b3b533 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -38,6 +38,8 @@ type HTTPConfig struct { Listen string `yaml:"listen"` // Origin describes the http origins to allow Origin string `yaml:"origin"` + // Protocol specifies the protocol for the webauthn origin + Protocol string `yaml:"protocol"` } type Config struct { @@ -52,8 +54,9 @@ func ConfigDefaults(dbPath string) *Config { return &Config{ DB: dbPath, HTTP: &HTTPConfig{ - Listen: "localhost:8000", - Origin: "localhost", + Listen: "localhost:8000", + Origin: "localhost", + Protocol: "http", }, } } @@ -178,8 +181,9 @@ func Initialize(config *Config) (http.Handler, error) { wan, err := webauthn.New(&webauthn.Config{ RPDisplayName: config.Name, RPID: config.HTTP.Origin, - RPOrigins: []string{config.HTTP.Origin}, - // RPIcon: "https://go-webauthn.local/logo.png", + RPOrigins: []string{config.HTTP.Protocol + "://" + config.HTTP.Origin}, + // For dev: + // RPOrigins: []string{config.HTTP.Protocol + "://" + config.HTTP.Listen}, }) if err != nil { return nil, err diff --git a/internal/server/static/webauthn.js b/internal/server/static/webauthn.js index a22b881..0b42bbd 100644 --- a/internal/server/static/webauthn.js +++ b/internal/server/static/webauthn.js @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright © 2022 Roberto Hidalgo -const { create: createCredentials, get: getCredentials } = hankoWebAuthn; - +import * as webauthnJSON from 'https://unpkg.com/@github/webauthn-json@2.0.2/dist/esm/webauthn-json.browser-ponyfill.js' const charsToEncode = /[\u007f-\uffff]/g; function JSONtob64(data) { return btoa(JSON.stringify(data).replace(charsToEncode, (c) => '\\u'+('000'+c.charCodeAt(0).toString(16)).slice(-4))) @@ -21,7 +20,6 @@ export async function withAuth(target, config) { return response } - console.log(Object.fromEntries(response.headers)) const challengeHeader = response.headers.get("webauthn") if (!challengeHeader || challengeHeader == "") { console.debug(`webauthn: success without auth`) @@ -42,20 +40,41 @@ export async function withAuth(target, config) { // we try to do that await register(challenge, target) // and retry the original request if successful - return await withAuth(target, config) + return await new Promise((res, rej) => { + setTimeout(async () => { + try { + res(await withAuth(target, config)) + } catch(err) { + rej(err) + } + }, 1000) + }) } else if (step == "login") { // server told us to use existing credential for request return await login(challenge, target, config) } - throw `Unknown webauthn step: <${kind}>` + throw `Unknown webauthn step: <${step}>` } async function register(challenge) { - console.info("webauthn: creating credentials") - const credential = await createCredentials(challenge); + console.info("webauthn: initializing registration from challenge") + console.dir(challenge) + const parsed = webauthnJSON.parseCreationOptionsFromJSON(challenge) + console.debug("webauthn: parsed challenge") + console.dir(parsed) + + console.info("webauthn: issuing credential creation request to browser") + let credential + try { + credential = await webauthnJSON.create(parsed); + } catch (err) { + console.error("sigh", err) + throw err + } console.debug(`webauthn: registering credentials with server: ${JSON.stringify(credential)}`) + let response = await window.fetch("/api/webauthn/register", { credentials: "include", method: "POST", @@ -81,8 +100,14 @@ async function register(challenge) { } async function login(challenge, target, config) { - console.info("webauthn: fetching stored client credentials") - const credential = await getCredentials(challenge); + console.info("webauthn: initializing login from challenge") + console.dir(challenge) + const parsed = webauthnJSON.parseRequestOptionsFromJSON(challenge) + console.debug("webauthn: parsed challenge") + console.dir(parsed) + + console.debug("webauthn: fetching stored client credentials") + let credential = await webauthnJSON.get(parsed); config.credentials = "include" config.headers = config.headers || {} diff --git a/internal/user/user.go b/internal/user/user.go index c8b6e92..444425c 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -40,7 +40,7 @@ type User struct { } func (u *User) WebAuthnID() []byte { - return []byte(fmt.Sprintf("%d", u.ID)) + return []byte(fmt.Sprintf("%d-%s", u.ID, u.Handle)) } // User Name according to the Relying Party