feat: add passkey creation functionality

This commit is contained in:
Prad Nukala 2024-12-08 22:53:04 -05:00
parent 5c3966a9d6
commit e0ebe92361
15 changed files with 189 additions and 118 deletions

View File

@ -1,8 +1,8 @@
package cards
templ ProfileCard(address string, handle string, name string, chainID string, creationBlock string) {
templ SonrProfile(address string, handle string, name string, chainID string, creationBlock string) {
<div class="profile-card">
<div class="text-white max-w-xs my-auto mx-auto bg-gradient-to-r from-blue-900 to-blue-500 p-4 py-5 px-5 rounded-xl">
<div class="text-white max-w-xs my-auto mx-auto bg-gradient-to-r from-cyan-900 to-cyan-500 p-4 py-5 px-5 rounded-xl">
<div class="flex justify-between">
<div>
<h2>{ chainID }</h2>

View File

@ -8,7 +8,7 @@ package cards
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func ProfileCard(address string, handle string, name string, chainID string, creationBlock string) templ.Component {
func SonrProfile(address string, handle string, name string, chainID string, creationBlock 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 {
@ -29,14 +29,14 @@ func ProfileCard(address string, handle string, name string, chainID string, cre
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"profile-card\"><div class=\"text-white max-w-xs my-auto mx-auto bg-gradient-to-r from-blue-900 to-blue-500 p-4 py-5 px-5 rounded-xl\"><div class=\"flex justify-between\"><div><h2>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"profile-card\"><div class=\"text-white max-w-xs my-auto mx-auto bg-gradient-to-r from-cyan-900 to-cyan-500 p-4 py-5 px-5 rounded-xl\"><div class=\"flex justify-between\"><div><h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(chainID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile-card.templ`, Line: 8, Col: 18}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile.templ`, Line: 8, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@ -49,7 +49,7 @@ func ProfileCard(address string, handle string, name string, chainID string, cre
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(handle)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile-card.templ`, Line: 9, Col: 43}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile.templ`, Line: 9, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@ -62,7 +62,7 @@ func ProfileCard(address string, handle string, name string, chainID string, cre
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(address)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile-card.templ`, Line: 17, Col: 35}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile.templ`, Line: 17, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@ -75,7 +75,7 @@ func ProfileCard(address string, handle string, name string, chainID string, cre
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(creationBlock)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile-card.templ`, Line: 22, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile.templ`, Line: 22, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@ -88,7 +88,7 @@ func ProfileCard(address string, handle string, name string, chainID string, cre
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile-card.templ`, Line: 26, Col: 32}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/cards/profile.templ`, Line: 26, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {

View File

@ -113,17 +113,20 @@ templ CredentialsScripts() {
.replace(/\//g, '_')
.replace(/=/g, '');
}
// Function to create a passkey
function createPasskey(button) {
const passkey = window.crypto.getRandomValues(new Uint8Array(32)).join('');
// Assuming there is a hidden input field to store the passkey
const hiddenInput = button.closest('form').querySelector('input[name="passkey"]');
if (hiddenInput) {
hiddenInput.value = passkey;
}
console.log('Passkey generated:', passkey);
}
</script>
}
}
script CreatePasskey(id string) {
function createPasskey(id) {
const passkey = document.getElementById(id);
passkey.value = window.crypto.getRandomValues(new Uint8Array(32)).join('');
}
}
// Template for creating credentials
templ CreateCredential(options *RegisterOptions) {
@CredentialsScripts()

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,9 @@
package form
import "github.com/onsonr/sonr/pkg/blocks/layout"
import (
"github.com/go-webauthn/webauthn/protocol"
"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) {
@ -51,14 +54,30 @@ templ CodeInput(id string) {
<sl-input id={ id } placeholder="●" type="text" maxlength="1" pill class="w-min"></sl-input>
}
// Hidden input and button which calls a javascript function to generate a passkey
templ PasskeyInput(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
}));
});
}
// Hidden input and button which calls a JavaScript function to generate a passkey
templ PasskeyInput(options protocol.PublicKeyCredentialCreationOptions) {
@CredentialsScripts()
<sl-button type="submit" pill style="width: 100%;">
<sl-icon slot="prefix" name="passkey" library="sonr" style="font-size: 20px;"></sl-icon>
Create PassKey
<sl-icon slot="suffix" name="arrow-right" library="sonr" style="font-size: 20px;"></sl-icon>
</sl-button>
<form>
<input type="hidden" name="passkey"/>
<sl-button pill style="width: 100%;" onclick="createPasskey(this)">
<sl-icon slot="prefix" name="passkey" library="sonr" style="font-size: 20px;"></sl-icon>
Create PassKey
<sl-icon slot="suffix" name="arrow-right" library="sonr" style="font-size: 20px;"></sl-icon>
</sl-button>
</form>
}
templ TurnstileWidget(sitekey string) {

View File

@ -8,7 +8,10 @@ package form
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "github.com/onsonr/sonr/pkg/blocks/layout"
import (
"github.com/go-webauthn/webauthn/protocol"
"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 {
@ -48,7 +51,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: 7, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 10, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@ -61,7 +64,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: 11, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 14, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@ -201,7 +204,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: 51, Col: 18}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 54, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
@ -215,8 +218,8 @@ func CodeInput(id string) templ.Component {
})
}
// Hidden input and button which calls a javascript function to generate a passkey
func PasskeyInput(id string) templ.Component {
// Hidden input and button which calls a JavaScript function to generate a passkey
func PasskeyInput(options protocol.PublicKeyCredentialCreationOptions) 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 {
@ -241,7 +244,7 @@ func PasskeyInput(id string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<sl-button type=\"submit\" pill style=\"width: 100%;\"><sl-icon slot=\"prefix\" name=\"passkey\" library=\"sonr\" style=\"font-size: 20px;\"></sl-icon> Create PassKey <sl-icon slot=\"suffix\" name=\"arrow-right\" library=\"sonr\" style=\"font-size: 20px;\"></sl-icon></sl-button>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form><input type=\"hidden\" name=\"passkey\"> <sl-button pill style=\"width: 100%;\" onclick=\"createPasskey(this)\"><sl-icon slot=\"prefix\" name=\"passkey\" library=\"sonr\" style=\"font-size: 20px;\"></sl-icon> Create PassKey <sl-icon slot=\"suffix\" name=\"arrow-right\" library=\"sonr\" style=\"font-size: 20px;\"></sl-icon></sl-button></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -278,7 +281,7 @@ func TurnstileWidget(sitekey string) templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, 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: 67, Col: 50}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 73, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@ -321,7 +324,7 @@ func Submit(text string) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, 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: 73, Col: 8}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/blocks/form/form.templ`, Line: 79, Col: 8}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {

View File

@ -0,0 +1,47 @@
package passkeys
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)
func defaultPrimaryAttestationFormats() []protocol.AttestationFormat {
return []protocol.AttestationFormat{
protocol.AttestationFormatApple,
protocol.AttestationFormatAndroidKey,
protocol.AttestationFormatAndroidSafetyNet,
protocol.AttestationFormatFIDOUniversalSecondFactor,
}
}
func defaultSecondaryAttestationFormats() []protocol.AttestationFormat {
return []protocol.AttestationFormat{
protocol.AttestationFormatPacked,
protocol.AttestationFormatTPM,
}
}
func defaultAuthenticatorSelection() protocol.AuthenticatorSelection {
return protocol.AuthenticatorSelection{
AuthenticatorAttachment: protocol.Platform,
ResidentKey: protocol.ResidentKeyRequirementRequired,
UserVerification: protocol.VerificationRequired,
}
}
func buildCredentialParameters() []protocol.CredentialParameter {
return []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
}
}

View File

@ -0,0 +1,59 @@
package passkeys
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/crypto/mpc"
"github.com/onsonr/sonr/pkg/common"
)
func Create(c echo.Context, handle string, ks mpc.Keyset) protocol.PublicKeyCredentialCreationOptions {
origin := c.Request().Host
svcName := c.Request().Host
addr := ks.Address()
return buildRegisterOptions(addr, handle, ks, origin, svcName)
}
func buildRegisterOptions(addr string, handle string, ks mpc.Keyset, origin string, svcName string) protocol.PublicKeyCredentialCreationOptions {
return protocol.PublicKeyCredentialCreationOptions{
Attestation: protocol.PreferDirectAttestation,
AttestationFormats: defaultPrimaryAttestationFormats(),
AuthenticatorSelection: defaultAuthenticatorSelection(),
RelyingParty: buildServiceEntity(origin, svcName),
Extensions: buildExtensions(ks),
Parameters: buildCredentialParameters(),
Timeout: 10000,
User: buildUserEntity(addr, handle),
}
}
func buildExtensions(ks mpc.Keyset) protocol.AuthenticationExtensions {
return protocol.AuthenticationExtensions{
"largeBlob": common.LargeBlob{
Support: "required",
Write: ks.UserJSON(),
},
"payment": common.Payment{
IsPayment: true,
},
}
}
func buildServiceEntity(name string, host string) protocol.RelyingPartyEntity {
return protocol.RelyingPartyEntity{
CredentialEntity: protocol.CredentialEntity{
Name: name,
},
ID: host,
}
}
func buildUserEntity(userAddress string, userHandle string) protocol.UserEntity {
return protocol.UserEntity{
ID: userAddress,
DisplayName: userHandle,
CredentialEntity: protocol.CredentialEntity{
Name: userAddress,
},
}
}

View File

@ -0,0 +1,15 @@
package passkeys
//
// import (
// "github.com/go-webauthn/webauthn/protocol"
// "github.com/labstack/echo/v4"
// "github.com/onsonr/sonr/crypto/mpc"
// )
//
// func Link(c echo.Context, handle string, ks mpc.Keyset) protocol.PublicKeyCredentialCreationOptions {
// origin := c.Request().Host
// svcName := c.Request().Host
// addr := ks.Address()
// return c.String(200, origin+" "+svcName+" "+addr+" "+handle)
// }

View File

@ -0,0 +1 @@
package passkeys

View File

@ -1,65 +0,0 @@
package webauth
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
)
func buildRegisterOptions(user protocol.UserEntity, blob common.LargeBlob, service protocol.RelyingPartyEntity) protocol.PublicKeyCredentialCreationOptions {
return protocol.PublicKeyCredentialCreationOptions{
Timeout: 10000,
Attestation: protocol.PreferDirectAttestation,
AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: "platform",
ResidentKey: protocol.ResidentKeyRequirementPreferred,
UserVerification: "preferred",
},
RelyingParty: service,
User: user,
Extensions: protocol.AuthenticationExtensions{
"largeBlob": blob,
},
Parameters: []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
},
}
}
func buildLargeBlob(userKeyshareJSON string) common.LargeBlob {
return common.LargeBlob{
Support: "required",
Write: userKeyshareJSON,
}
}
func buildUserEntity(userAddress string, userHandle string) protocol.UserEntity {
return protocol.UserEntity{
ID: userAddress,
DisplayName: userHandle,
CredentialEntity: protocol.CredentialEntity{
Name: userAddress,
},
}
}
func buildServiceEntity(c echo.Context) protocol.RelyingPartyEntity {
return protocol.RelyingPartyEntity{
CredentialEntity: protocol.CredentialEntity{
Name: "Sonr.ID",
},
ID: c.Request().Host,
}
}

View File

@ -1 +0,0 @@
package webauth

View File

@ -8,6 +8,7 @@ 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"
@ -28,7 +29,9 @@ func HandleRegisterStart(c echo.Context) error {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return response.TemplEcho(c, register.LinkCredentialView(ks.Address(), handle))
opts := passkeys.Create(c, handle, ks)
return response.TemplEcho(c, register.LinkCredentialView(ks.Address(), handle, opts))
}
func HandleRegisterFinish(c echo.Context) error {

View File

@ -21,11 +21,11 @@ templ ProfileFormView(turnstileSiteKey string) {
}
}
templ LinkCredentialView(addr string, handle string) {
templ LinkCredentialView(addr string, handle string, registerOptions form.RegisterOptions) {
@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("passkey"), "65", false) {
@form.Form("/register/finish", "POST", form.PasskeyInput(registerOptions), "65", false) {
@details.PropertyList() {
@details.Property("Address", addr, "wallet")
@details.Property("Handle", handle, "at-sign")

View File

@ -122,7 +122,7 @@ func ProfileFormView(turnstileSiteKey string) templ.Component {
})
}
func LinkCredentialView(addr string, handle string) templ.Component {
func LinkCredentialView(addr string, handle string, registerOptions form.RegisterOptions) 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) templ.Component {
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = form.Form("/register/finish", "POST", form.PasskeyInput("passkey"), "65", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = form.Form("/register/finish", "POST", form.PasskeyInput(registerOptions), "65", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}