From 5280209075e823f9adac612f18b86e213e23d84b Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sun, 8 Dec 2024 23:10:40 -0500 Subject: [PATCH] feat: implement passkey registration flow --- pkg/blocks/form/form.templ | 75 ++++++++---- pkg/blocks/form/form_templ.go | 108 ++++++++++++++---- pkg/gateway/handlers/register_handler.go | 12 +- .../internal/pages/register/page.templ | 4 +- .../internal/pages/register/page_templ.go | 4 +- 5 files changed, 146 insertions(+), 57 deletions(-) diff --git a/pkg/blocks/form/form.templ b/pkg/blocks/form/form.templ index 2ff7d073d..8086d1290 100644 --- a/pkg/blocks/form/form.templ +++ b/pkg/blocks/form/form.templ @@ -1,9 +1,6 @@ package form -import ( - "github.com/go-webauthn/webauthn/protocol" - "github.com/onsonr/sonr/pkg/blocks/layout" -) +import "github.com/onsonr/sonr/pkg/blocks/layout" // Form is a standard form styled like a card templ Form(action string, method string, submit templ.Component, progress string, enableCancel bool) { @@ -54,30 +51,60 @@ templ CodeInput(id string) { } -script createPasskey(options protocol.PublicKeyCredentialCreationOptions) { - navigator.credentials.create(options).then(function(credential) { - // Dispatch event with credential data - window.dispatchEvent(new CustomEvent('credentialCreated', { - detail: credential - })); - }).catch(function(err) { - window.dispatchEvent(new CustomEvent('credentialError', { - detail: err.message - })); - }); +script createPasskey(userId string, userHandle string, userName string, challenge string) { +const publicKey = { + challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)), + rp: { + name: "Sonr.ID", + }, + user: { + // Assuming that userId is ASCII-only + id: Uint8Array.from(userId, (c) => c.charCodeAt(0)), + name: userName, + displayName: userHandle, + }, + pubKeyCredParams: [ + { + type: "public-key", + alg: -7, // "ES256" + }, + { + type: "public-key", + alg: -257, // "RS256" + }, + ], + authenticatorSelection: { + userVerification: "required", + residentKey: "required", + authenticatorAttachment: "platform", + }, + timeout: 60000, // 1 minute + extensions: { + payment: { + isPayment: true, + }, + }, +}; +navigator.credentials + .create({ publicKey }) + .then((newCredentialInfo) => { + console.log(newCredentialInfo); + // Send new credential info to server for verification and registration. + }) + .catch((err) => { + console.error(err); + // No acceptable authenticator or user refused consent. Handle appropriately. + }); } // Hidden input and button which calls a JavaScript function to generate a passkey -templ PasskeyInput(options protocol.PublicKeyCredentialCreationOptions) { +templ PasskeyInput(userId string, userHandle string, userName string, challenge string) { @CredentialsScripts() -
- - - - Create PassKey - - -
+ + + Create PassKey + + } templ TurnstileWidget(sitekey string) { diff --git a/pkg/blocks/form/form_templ.go b/pkg/blocks/form/form_templ.go index 82ef4794c..7b6ddc6e8 100644 --- a/pkg/blocks/form/form_templ.go +++ b/pkg/blocks/form/form_templ.go @@ -8,10 +8,7 @@ package form import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import ( - "github.com/go-webauthn/webauthn/protocol" - "github.com/onsonr/sonr/pkg/blocks/layout" -) +import "github.com/onsonr/sonr/pkg/blocks/layout" // Form is a standard form styled like a card func Form(action string, method string, submit templ.Component, progress string, enableCancel bool) templ.Component { @@ -51,7 +48,7 @@ func Form(action string, method string, submit templ.Component, progress string, var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(method) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 10, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 7, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -64,7 +61,7 @@ func Form(action string, method string, submit templ.Component, progress string, var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(progress) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 14, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 11, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -204,7 +201,7 @@ func CodeInput(id string) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(id) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 54, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 51, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -218,8 +215,60 @@ func CodeInput(id string) templ.Component { }) } +func createPasskey(userId string, userHandle string, userName string, challenge string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_createPasskey_9257`, + Function: `function __templ_createPasskey_9257(userId, userHandle, userName, challenge){const publicKey = { + challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)), + rp: { + name: "Sonr.ID", + }, + user: { + // Assuming that userId is ASCII-only + id: Uint8Array.from(userId, (c) => c.charCodeAt(0)), + name: userName, + displayName: userHandle, + }, + pubKeyCredParams: [ + { + type: "public-key", + alg: -7, // "ES256" + }, + { + type: "public-key", + alg: -257, // "RS256" + }, + ], + authenticatorSelection: { + userVerification: "required", + residentKey: "required", + authenticatorAttachment: "platform", + }, + timeout: 60000, // 1 minute + extensions: { + payment: { + isPayment: true, + }, + }, +}; +navigator.credentials + .create({ publicKey }) + .then((newCredentialInfo) => { + console.log(newCredentialInfo); + // Send new credential info to server for verification and registration. + }) + .catch((err) => { + console.error(err); + // No acceptable authenticator or user refused consent. Handle appropriately. + }); +}`, + Call: templ.SafeScript(`__templ_createPasskey_9257`, userId, userHandle, userName, challenge), + CallInline: templ.SafeScriptInline(`__templ_createPasskey_9257`, userId, userHandle, userName, challenge), + } +} + // Hidden input and button which calls a JavaScript function to generate a passkey -func PasskeyInput(options protocol.PublicKeyCredentialCreationOptions) templ.Component { +func PasskeyInput(userId string, userHandle string, userName string, challenge string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -244,7 +293,20 @@ func PasskeyInput(options protocol.PublicKeyCredentialCreationOptions) templ.Com if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Create PassKey
") + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, createPasskey(userId, userHandle, userName, challenge)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Create PassKey ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -268,9 +330,9 @@ func TurnstileWidget(sitekey string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var11 := templ.GetChildren(ctx) - if templ_7745c5c3_Var11 == nil { - templ_7745c5c3_Var11 = templ.NopComponent + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent } ctx = templ.ClearChildren(ctx) if sitekey != "" { @@ -278,12 +340,12 @@ func TurnstileWidget(sitekey string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(sitekey) + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(sitekey) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 73, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 113, Col: 50} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -312,21 +374,21 @@ func Submit(text string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var13 := templ.GetChildren(ctx) - if templ_7745c5c3_Var13 == nil { - templ_7745c5c3_Var13 = templ.NopComponent + templ_7745c5c3_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(text) + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(text) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 79, Col: 8} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 119, Col: 8} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/gateway/handlers/register_handler.go b/pkg/gateway/handlers/register_handler.go index ac56ae888..5f5fad6a0 100644 --- a/pkg/gateway/handlers/register_handler.go +++ b/pkg/gateway/handlers/register_handler.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "github.com/go-webauthn/webauthn/protocol" @@ -8,7 +9,6 @@ import ( "github.com/labstack/echo/v4" "github.com/onsonr/sonr/crypto/mpc" "github.com/onsonr/sonr/pkg/common" - "github.com/onsonr/sonr/pkg/common/passkeys" "github.com/onsonr/sonr/pkg/common/response" "github.com/onsonr/sonr/pkg/gateway/config" "github.com/onsonr/sonr/pkg/gateway/internal/pages/register" @@ -22,16 +22,16 @@ func HandleRegisterView(env config.Env) echo.HandlerFunc { } func HandleRegisterStart(c echo.Context) error { + challenge, _ := protocol.CreateChallenge() handle := c.FormValue("handle") - + firstName := c.FormValue("first_name") + lastName := c.FormValue("last_name") + fullName := fmt.Sprintf("%s %s", firstName, lastName) ks, err := mpc.NewKeyset() if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - - opts := passkeys.Create(c, handle, ks) - - return response.TemplEcho(c, register.LinkCredentialView(ks.Address(), handle, opts)) + return response.TemplEcho(c, register.LinkCredentialView(ks.Address(), handle, fullName, challenge.String())) } func HandleRegisterFinish(c echo.Context) error { diff --git a/pkg/gateway/internal/pages/register/page.templ b/pkg/gateway/internal/pages/register/page.templ index ed8e014e5..08fbf8475 100644 --- a/pkg/gateway/internal/pages/register/page.templ +++ b/pkg/gateway/internal/pages/register/page.templ @@ -21,11 +21,11 @@ templ ProfileFormView(turnstileSiteKey string) { } } -templ LinkCredentialView(addr string, handle string, registerOptions form.RegisterOptions) { +templ LinkCredentialView(addr string, handle string, name string, challenge string) { @layout.Root("Register | Sonr.ID") { @layout.Container() { @text.Header("Link a PassKey", "This will be used to login to your vault.") - @form.Form("/register/finish", "POST", form.PasskeyInput(registerOptions), "65", false) { + @form.Form("/register/finish", "POST", form.PasskeyInput(addr, handle, name, challenge), "65", false) { @details.PropertyList() { @details.Property("Address", addr, "wallet") @details.Property("Handle", handle, "at-sign") diff --git a/pkg/gateway/internal/pages/register/page_templ.go b/pkg/gateway/internal/pages/register/page_templ.go index 75ccb39a8..9c0341477 100644 --- a/pkg/gateway/internal/pages/register/page_templ.go +++ b/pkg/gateway/internal/pages/register/page_templ.go @@ -122,7 +122,7 @@ func ProfileFormView(turnstileSiteKey string) templ.Component { }) } -func LinkCredentialView(addr string, handle string, registerOptions form.RegisterOptions) templ.Component { +func LinkCredentialView(addr string, handle string, name string, challenge string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -219,7 +219,7 @@ func LinkCredentialView(addr string, handle string, registerOptions form.Registe } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = form.Form("/register/finish", "POST", form.PasskeyInput(registerOptions), "65", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = form.Form("/register/finish", "POST", form.PasskeyInput(addr, handle, name, challenge), "65", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }