refactor: consolidate authentication and DID handling logic

This commit is contained in:
Prad Nukala
2024-12-09 18:19:46 -05:00
parent ce3d073abd
commit f8d362352d
23 changed files with 144 additions and 217 deletions
+1 -49
View File
@@ -2,8 +2,6 @@ package forms
import (
"fmt"
"strconv"
"errors"
"github.com/onsonr/sonr/pkg/blocks/layout"
)
@@ -17,30 +15,6 @@ func (d CreateProfileData) IsHumanLabel() string {
return fmt.Sprintf("What is %d + %d?", d.FirstNumber, d.LastNumber)
}
func ValidateCreateProfileForm(formData map[string][]string) error {
// Validate ishuman slider
ishumanValues := formData["is_human"]
if len(ishumanValues) == 0 {
return errors.New("human verification is required")
}
ishumanSum, err := strconv.Atoi(ishumanValues[0])
if err != nil {
return errors.New("invalid human verification value")
}
// Get the expected sum from the form data
firstNum, _ := strconv.Atoi(formData["first_number"][0])
lastNum, _ := strconv.Atoi(formData["last_number"][0])
expectedSum := firstNum + lastNum
if ishumanSum != expectedSum {
return errors.New("incorrect sum for human verification")
}
return nil
}
// ProfileForm is a standard form styled like a card
templ CreateProfile(action string, method string, data CreateProfileData) {
<form action={ templ.SafeURL(action) } method={ method }>
@@ -61,26 +35,7 @@ templ CreateProfile(action string, method string, data CreateProfileData) {
</div>
</sl-input>
@layout.Spacer()
<sl-range
name="is_human"
label={ data.IsHumanLabel() }
help-text="Prove you are a human."
min="0"
max={ fmt.Sprint(data.FirstNumber + data.LastNumber) }
step="1"
onchange="validateSum(this)"
></sl-range>
<script>
function validateSum(slider) {
const expectedSum = { fmt.Sprint(data.FirstNumber + data.LastNumber) };
const value = parseInt(slider.value);
if (value !== expectedSum) {
slider.setCustomValidity("Please select the correct sum");
} else {
slider.setCustomValidity("");
}
}
</script>
<sl-range name="is_human" label={ data.IsHumanLabel() } help-text="Prove you are a human." min="0" max="9" step="1"></sl-range>
<div slot="footer">
<sl-button href="/" outline>
<sl-icon slot="prefix" name="arrow-left" library="sonr"></sl-icon>
@@ -101,6 +56,3 @@ templ CreateProfile(action string, method string, data CreateProfileData) {
</sl-card>
</form>
}
templ isHumanSlider(targetSum string) {
}
-25
View File
@@ -126,29 +126,4 @@ func CreateProfile(action string, method string, data CreateProfileData) templ.C
})
}
func isHumanSlider(targetSum 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 {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
+46
View File
@@ -0,0 +1,46 @@
package forms
import "github.com/onsonr/sonr/pkg/blocks/layout"
type SetupWalletData struct {
TurnstileSiteKey string
FirstNumber int
LastNumber int
}
// SetupWallet is a standard form styled like a card
templ SetupWallet(action string, method string, data CreateProfileData) {
<form action={ templ.SafeURL(action) } method={ method }>
<sl-card class="card-form gap-4 max-w-lg">
<div slot="header">
<div class="w-full py-1">
<sl-progress-bar value="50"></sl-progress-bar>
</div>
</div>
@layout.Spacer()
<sl-input name="handle" placeholder="thoughtdiff" type="text" label="Handle" minlength="4" maxlength="12" required>
<div slot="prefix">
<sl-icon name="at-sign" library="sonr"></sl-icon>
</div>
</sl-input>
@layout.Spacer()
<div slot="footer">
<sl-button href="/" outline>
<sl-icon slot="prefix" name="arrow-left" library="sonr"></sl-icon>
Skip for now
</sl-button>
<sl-button type="submit">
Done
<sl-icon slot="suffix" name="arrow-right" library="sonr"></sl-icon>
</sl-button>
</div>
<style>
.card-form [slot='footer'] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
</sl-card>
</form>
}
+87
View File
@@ -0,0 +1,87 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.793
package forms
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "github.com/onsonr/sonr/pkg/blocks/layout"
type SetupWalletData struct {
TurnstileSiteKey string
FirstNumber int
LastNumber int
}
// SetupWallet is a standard form styled like a card
func SetupWallet(action string, method string, data CreateProfileData) 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 {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.SafeURL(action)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" method=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
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/forms/setup_wallet.templ`, Line: 13, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><sl-card class=\"card-form gap-4 max-w-lg\"><div slot=\"header\"><div class=\"w-full py-1\"><sl-progress-bar value=\"50\"></sl-progress-bar></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = layout.Spacer().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<sl-input name=\"handle\" placeholder=\"thoughtdiff\" type=\"text\" label=\"Handle\" minlength=\"4\" maxlength=\"12\" required><div slot=\"prefix\"><sl-icon name=\"at-sign\" library=\"sonr\"></sl-icon></div></sl-input>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = layout.Spacer().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div slot=\"footer\"><sl-button href=\"/\" outline><sl-icon slot=\"prefix\" name=\"arrow-left\" library=\"sonr\"></sl-icon> Skip for now</sl-button> <sl-button type=\"submit\">Done <sl-icon slot=\"suffix\" name=\"arrow-right\" library=\"sonr\"></sl-icon></sl-button></div><style>\n \t\t.card-form [slot='footer'] {\n \t\tdisplay: flex;\n \t\tjustify-content: space-between;\n \t\talign-items: center;\n \t\t}\n\t\t</style></sl-card></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
@@ -1,4 +1,4 @@
package controller
package dids
import (
"github.com/labstack/echo/v4"
@@ -1,7 +1,7 @@
//go:build js && wasm
// +build js,wasm
package controller
package dids
import (
"fmt"
-47
View File
@@ -1,47 +0,0 @@
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,
},
}
}
-59
View File
@@ -1,59 +0,0 @@
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,
},
}
}
-15
View File
@@ -1,15 +0,0 @@
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)
// }
@@ -1 +0,0 @@
package passkeys
+1 -1
View File
@@ -3,8 +3,8 @@ package handlers
import (
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common/response"
"github.com/onsonr/sonr/pkg/gateway/internal/pages/index"
"github.com/onsonr/sonr/pkg/gateway/internal/session"
"github.com/onsonr/sonr/pkg/gateway/pages/index"
)
func HandleIndex(c echo.Context) error {
+1 -11
View File
@@ -12,7 +12,7 @@ import (
"github.com/onsonr/sonr/pkg/blocks/forms"
"github.com/onsonr/sonr/pkg/common/response"
"github.com/onsonr/sonr/pkg/gateway/internal/database"
"github.com/onsonr/sonr/pkg/gateway/internal/pages/register"
"github.com/onsonr/sonr/pkg/gateway/pages/register"
)
func HandleRegisterView(c echo.Context) error {
@@ -24,16 +24,6 @@ func HandleRegisterView(c echo.Context) error {
}
func HandleRegisterStart(c echo.Context) error {
// Validate the form submission
formData := make(map[string][]string)
for key, values := range c.Request().Form {
formData[key] = values
}
if err := forms.ValidateCreateProfileForm(formData); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
challenge, _ := protocol.CreateChallenge()
handle := c.FormValue("handle")
firstName := c.FormValue("first_name")
+211
View File
@@ -0,0 +1,211 @@
//go:build js && wasm
// +build js,wasm
package vault
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"syscall/js"
)
var (
// Global buffer pool to reduce allocations
bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
// Cached JS globals
jsGlobal = js.Global()
jsUint8Array = jsGlobal.Get("Uint8Array")
jsResponse = jsGlobal.Get("Response")
jsPromise = jsGlobal.Get("Promise")
jsWasmHTTP = jsGlobal.Get("wasmhttp")
)
// ServeFetch serves HTTP requests with optimized handler management
func ServeFetch(handler http.Handler) func() {
h := handler
if h == nil {
h = http.DefaultServeMux
}
// Optimize prefix handling
prefix := strings.TrimRight(jsWasmHTTP.Get("path").String(), "/")
if prefix != "" {
mux := http.NewServeMux()
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
h = mux
}
// Create request handler function
cb := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
promise, resolve, reject := newPromiseOptimized()
go handleRequest(h, args[1], resolve, reject)
return promise
})
jsWasmHTTP.Call("setHandler", cb)
return cb.Release
}
// handleRequest processes the request with panic recovery
func handleRequest(h http.Handler, jsReq js.Value, resolve, reject func(interface{})) {
defer func() {
if r := recover(); r != nil {
var errMsg string
if err, ok := r.(error); ok {
errMsg = fmt.Sprintf("wasmhttp: panic: %+v", err)
} else {
errMsg = fmt.Sprintf("wasmhttp: panic: %v", r)
}
reject(errMsg)
}
}()
recorder := newResponseRecorder()
h.ServeHTTP(recorder, buildRequest(jsReq))
resolve(recorder.jsResponse())
}
// buildRequest creates an http.Request from JS Request
func buildRequest(jsReq js.Value) *http.Request {
// Get request body
arrayBuffer, err := awaitPromiseOptimized(jsReq.Call("arrayBuffer"))
if err != nil {
panic(err)
}
// Create body buffer
jsBody := jsUint8Array.New(arrayBuffer)
bodyLen := jsBody.Get("length").Int()
body := make([]byte, bodyLen)
js.CopyBytesToGo(body, jsBody)
// Create request
req := httptest.NewRequest(
jsReq.Get("method").String(),
jsReq.Get("url").String(),
bytes.NewReader(body),
)
// Set headers efficiently
headers := jsReq.Get("headers")
headersIt := headers.Call("entries")
for {
entry := headersIt.Call("next")
if entry.Get("done").Bool() {
break
}
pair := entry.Get("value")
req.Header.Set(pair.Index(0).String(), pair.Index(1).String())
}
return req
}
// ResponseRecorder with optimized buffer handling
type ResponseRecorder struct {
*httptest.ResponseRecorder
buffer *bytes.Buffer
}
func newResponseRecorder() *ResponseRecorder {
return &ResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
buffer: bufferPool.Get().(*bytes.Buffer),
}
}
// jsResponse creates a JS Response with optimized memory usage
func (rr *ResponseRecorder) jsResponse() js.Value {
defer func() {
rr.buffer.Reset()
bufferPool.Put(rr.buffer)
}()
res := rr.Result()
defer res.Body.Close()
// Prepare response body
body := js.Undefined()
if res.ContentLength != 0 {
if _, err := io.Copy(rr.buffer, res.Body); err != nil {
panic(err)
}
bodyBytes := rr.buffer.Bytes()
body = jsUint8Array.New(len(bodyBytes))
js.CopyBytesToJS(body, bodyBytes)
}
// Prepare response init object
init := make(map[string]interface{}, 3)
if res.StatusCode != 0 {
init["status"] = res.StatusCode
}
if len(res.Header) > 0 {
headers := make(map[string]interface{}, len(res.Header))
for k, v := range res.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
init["headers"] = headers
}
return jsResponse.New(body, init)
}
// newPromiseOptimized creates a new JavaScript Promise with optimized callback handling
func newPromiseOptimized() (js.Value, func(interface{}), func(interface{})) {
var (
resolve func(interface{})
reject func(interface{})
promiseFunc = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
resolve = func(v interface{}) { args[0].Invoke(v) }
reject = func(v interface{}) { args[1].Invoke(v) }
return js.Undefined()
})
)
defer promiseFunc.Release()
return jsPromise.New(promiseFunc), resolve, reject
}
// awaitPromiseOptimized waits for Promise resolution with optimized channel handling
func awaitPromiseOptimized(promise js.Value) (js.Value, error) {
done := make(chan struct{})
var (
result js.Value
err error
)
thenFunc := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
result = args[0]
close(done)
return nil
})
defer thenFunc.Release()
catchFunc := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
err = js.Error{Value: args[0]}
close(done)
return nil
})
defer catchFunc.Release()
promise.Call("then", thenFunc).Call("catch", catchFunc)
<-done
return result, err
}
+34
View File
@@ -0,0 +1,34 @@
//go:build js && wasm
// +build js,wasm
package vault
import (
"encoding/base64"
"encoding/json"
"github.com/labstack/echo/v4"
)
func WasmContextMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Extract WASM context from headers
if wasmCtx := c.Request().Header.Get("X-Wasm-Context"); wasmCtx != "" {
if ctx, err := DecodeWasmContext(wasmCtx); err == nil {
c.Set("wasm_context", ctx)
}
}
return next(c)
}
}
// decodeWasmContext decodes the WASM context from a base64 encoded string
func DecodeWasmContext(ctx string) (map[string]any, error) {
decoded, err := base64.StdEncoding.DecodeString(ctx)
if err != nil {
return nil, err
}
var ctxData map[string]any
err = json.Unmarshal(decoded, &ctxData)
return ctxData, err
}