From 891ca20a729d8ec9e7a403c4d6ac1ba2ab97ec75 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Tue, 16 Jan 2024 13:32:47 -0500 Subject: [PATCH] feat: implement AccountRegister, and switch to using structs for request/response --- api/s5/http.go | 179 ++++++++++++++++++++++++++++++++++++++++++--- api/s5/messages.go | 15 ++++ 2 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 api/s5/messages.go diff --git a/api/s5/http.go b/api/s5/http.go index 9d07e32..d0c93ec 100644 --- a/api/s5/http.go +++ b/api/s5/http.go @@ -2,6 +2,7 @@ package s5 import ( "bytes" + "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/hex" @@ -10,12 +11,14 @@ import ( "git.lumeweb.com/LumeWeb/libs5-go/types" "git.lumeweb.com/LumeWeb/portal/db/models" "git.lumeweb.com/LumeWeb/portal/interfaces" + emailverifier "github.com/AfterShip/email-verifier" "go.sia.tech/jape" "go.uber.org/zap" "io" "mime/multipart" "net/http" "strings" + "time" ) const ( @@ -25,19 +28,40 @@ const ( errClosingStream = "Error closing the stream" errUploadingFile = "Error uploading the file" errAccountGenerateChallenge = "Error generating challenge" + errAccountRegister = "Error registering account" ) var ( errUploadingFileErr = errors.New(errUploadingFile) errAccountGenerateChallengeErr = errors.New(errAccountGenerateChallenge) + errAccountRegisterErr = errors.New(errAccountRegister) + errInvalidChallengeErr = errors.New("Invalid challenge") + errInvalidSignatureErr = errors.New("Invalid signature") + errPubkeyNotSupported = errors.New("Only ed25519 keys are supported") + errInvalidEmail = errors.New("Invalid email") + errEmailAlreadyExists = errors.New("Email already exists") + errGeneratingPassword = errors.New("Error generating password") + errPubkeyAlreadyExists = errors.New("Pubkey already exists") ) type HttpHandler struct { - portal interfaces.Portal + portal interfaces.Portal + verifier *emailverifier.Verifier } func NewHttpHandler(portal interfaces.Portal) *HttpHandler { - return &HttpHandler{portal: portal} + + verifier := emailverifier.NewVerifier() + + verifier.DisableSMTPCheck() + verifier.DisableGravatarCheck() + verifier.DisableDomainSuggest() + verifier.DisableAutoUpdateDisposable() + + return &HttpHandler{ + portal: portal, + verifier: verifier, + } } func (h *HttpHandler) SmallFileUpload(jc jape.Context) { @@ -151,7 +175,9 @@ func (h *HttpHandler) SmallFileUpload(jc jape.Context) { return } - jc.Encode(map[string]string{"hash": cidStr}) + jc.Encode(&SmallUploadResponse{ + CID: cidStr, + }) } func (h *HttpHandler) AccountRegisterChallenge(jc jape.Context) { @@ -193,20 +219,151 @@ func (h *HttpHandler) AccountRegisterChallenge(jc jape.Context) { return } - jc.Encode(map[string]string{"challenge": base64.RawURLEncoding.EncodeToString(challenge)}) + jc.Encode(&AccountRegisterChallengeResponse{ + Challenge: base64.RawURLEncoding.EncodeToString(challenge), + }) } -func (h *HttpHandler) AccountRegister(context jape.Context) { +func (h *HttpHandler) AccountRegister(jc jape.Context) { + var request AccountRegisterRequest + + if jc.Decode(&request) != nil { + return + } + + errored := func(err error) { + _ = jc.Error(errAccountRegisterErr, http.StatusInternalServerError) + h.portal.Logger().Error(errAccountRegister, zap.Error(err)) + } + + decodedKey, err := base64.RawURLEncoding.DecodeString(request.Pubkey) + + if err != nil { + errored(err) + return + } + + if len(decodedKey) != 32 { + errored(err) + return + } + + var challenge models.S5Challenge + + result := h.portal.Database().Model(&models.S5Challenge{}).Where(&models.S5Challenge{Pubkey: request.Pubkey}).First(&challenge) + + if result.RowsAffected == 0 || result.Error != nil { + errored(err) + return + } + + decodedResponse, err := base64.RawURLEncoding.DecodeString(request.Response) + + if err != nil { + errored(err) + return + } + + if len(decodedResponse) != 64 { + errored(err) + return + } + + decodedChallenge, err := base64.RawURLEncoding.DecodeString(challenge.Challenge) + + if err != nil { + errored(err) + return + } + + if !bytes.Equal(decodedResponse, decodedChallenge) { + errored(errInvalidChallengeErr) + return + } + + if int(decodedKey[0]) != int(types.HashTypeEd25519) { + errored(errPubkeyNotSupported) + return + } + + decodedSignature, err := base64.RawURLEncoding.DecodeString(request.Signature) + + if err != nil { + errored(err) + return + } + + if !ed25519.Verify(decodedKey, decodedChallenge, decodedSignature) { + errored(errInvalidSignatureErr) + return + } + + verify, _ := h.verifier.Verify(request.Email) + + if !verify.Syntax.Valid { + errored(errInvalidEmail) + return + } + + accountExists, _ := h.portal.Accounts().EmailExists(request.Email) + + if accountExists { + errored(errEmailAlreadyExists) + return + } + + pubkeyExists, _ := h.portal.Accounts().PubkeyExists(request.Pubkey) + + if pubkeyExists { + errored(errPubkeyAlreadyExists) + return + } + + passwd := make([]byte, 32) + + _, err = rand.Read(passwd) + + if accountExists { + errored(errGeneratingPassword) + return + } + + newAccount, err := h.portal.Accounts().CreateAccount(request.Email, string(passwd)) + if err != nil { + errored(errAccountRegisterErr) + return + } + + err = h.portal.Accounts().AddPubkeyToAccount(*newAccount, request.Pubkey) + if err != nil { + errored(errAccountRegisterErr) + return + } + + jwt, err := h.portal.Accounts().LoginPubkey(request.Pubkey) + if err != nil { + errored(errAccountRegisterErr) + return + } + + authCookie := http.Cookie{ + Name: "s5-auth-token", + Value: jwt, + Path: "/", + HttpOnly: true, + MaxAge: int(time.Hour.Seconds() * 24), + Secure: true, + } + + http.SetCookie(jc.ResponseWriter, &authCookie) +} + +func (h *HttpHandler) AccountLoginChallenge(jc jape.Context) { //TODO implement me panic("implement me") } -func (h *HttpHandler) AccountLoginChallenge(context jape.Context) { - //TODO implement me - panic("implement me") -} - -func (h *HttpHandler) AccountLogin(context jape.Context) { +func (h *HttpHandler) AccountLogin(jc jape.Context) { //TODO implement me panic("implement me") } diff --git a/api/s5/messages.go b/api/s5/messages.go new file mode 100644 index 0000000..199fdb3 --- /dev/null +++ b/api/s5/messages.go @@ -0,0 +1,15 @@ +package s5 + +type AccountRegisterRequest struct { + Pubkey string `json:"pubkey"` + Response string `json:"response"` + Signature string `json:"signature"` + Email string `json:"email"` +} + +type SmallUploadResponse struct { + CID string `json:"cid"` +} +type AccountRegisterChallengeResponse struct { + Challenge string `json:"challenge"` +}