Skip to content

Commit

Permalink
feat: saas oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
emmdim committed Feb 5, 2025
1 parent 921755c commit f5a924d
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 0 deletions.
19 changes: 19 additions & 0 deletions account/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package account
import (
"encoding/hex"
"fmt"
"strings"

"go.vocdoni.io/dvote/crypto/ethereum"
"go.vocdoni.io/dvote/util"
"go.vocdoni.io/proto/build/go/models"
"google.golang.org/protobuf/proto"
)
Expand Down Expand Up @@ -54,3 +56,20 @@ func SignMessage(message []byte, signer *ethereum.SignKeys) ([]byte, error) {
}
return signature, nil
}

func VerifySignature(message, signature, address string) error {
// calcAddress, err := ethereum.AddrFromSignature([]byte(message), []byte(signature))
messageBytes := []byte(message)
signatureBytes, err := hex.DecodeString(util.TrimHex(signature))
if err != nil {
return fmt.Errorf("could not decode signature: %w", err)
}
calcAddress, err := ethereum.AddrFromSignature(messageBytes, signatureBytes)
if err != nil {
return fmt.Errorf("could not calculate address from signature: %w", err)
}
if strings.EqualFold(calcAddress.String(), util.TrimHex(address)) {
return fmt.Errorf("signature verification failed")
}
return nil
}
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ func (a *API) initRouter() http.Handler {
// login
log.Infow("new route", "method", "POST", "path", authLoginEndpoint)
r.Post(authLoginEndpoint, a.authLoginHandler)
// oauth login
log.Infow("new route", "method", "POST", "path", oauthLoginEndpoint)
r.Post(oauthLoginEndpoint, a.oauthLoginHandler)
// register user
log.Infow("new route", "method", "POST", "path", usersEndpoint)
r.Post(usersEndpoint, a.registerHandler)
Expand Down
75 changes: 75 additions & 0 deletions api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package api

import (
"encoding/json"
"fmt"
"net/http"

"github.com/vocdoni/saas-backend/account"
"github.com/vocdoni/saas-backend/db"
"github.com/vocdoni/saas-backend/internal"
"go.vocdoni.io/dvote/log"
)

// refresh handles the refresh request. It returns a new JWT token.
Expand Down Expand Up @@ -64,6 +67,78 @@ func (a *API) authLoginHandler(w http.ResponseWriter, r *http.Request) {
httpWriteJSON(w, res)
}

// login handles the login request. It returns a JWT token if the login is successful.
func (a *API) oauthLoginHandler(w http.ResponseWriter, r *http.Request) {
// het the user info from the request body
loginInfo := &OauthLogin{}
if err := json.NewDecoder(r.Body).Decode(loginInfo); err != nil {
ErrMalformedBody.Write(w)
return
}
log.Debugf("%v", loginInfo)
// get the user information from the database by email
user, err := a.db.UserByEmail(loginInfo.Email)
if err != nil && err != db.ErrNotFound {
ErrGenericInternalServerError.Write(w)
return
}
// if the user doesn't exist, do oauth verifictaion and on success create the new user
if err == db.ErrNotFound {
// 1. extract from the external signature the user publey and verify matches the provided one
if err := account.VerifySignature(loginInfo.OauthSignature, loginInfo.UserOauthSignature, loginInfo.Address); err != nil {
ErrUnauthorized.WithErr(err).Write(w)
return
}
// 2. fetch oath service pubkey or address and verify the internal signature
resp, err := http.Get(fmt.Sprintf("%s/getAddress", OauthServiceURL))
if err != nil {
// TODO create new error for connection with the external service
ErrGenericInternalServerError.WithErr(err).Write(w)
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
// handle the error, for example log it
fmt.Println("Error closing response body:", err)
}
}()
var result OauthServiceResposne
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
// TODO create new error for connection with the external service
ErrGenericInternalServerError.WithErr(err).Write(w)
return
}
if err := account.VerifySignature(loginInfo.Email, loginInfo.OauthSignature, result.Address); err != nil {
ErrUnauthorized.WithErr(err).Write(w)
return
}
// genareate the new user and password and store it in the database
user = &db.User{
Email: loginInfo.Email,
Password: internal.HexHashPassword(passwordSalt, loginInfo.UserOauthSignature),
Verified: true,
}
if _, err := a.db.SetUser(user); err != nil {
ErrGenericInternalServerError.WithErr(err).Write(w)
}
} else {
// check that the address generated password matches the one in the database
// check the password
if pass := internal.HexHashPassword(passwordSalt, loginInfo.UserOauthSignature); pass != user.Password {
ErrUnauthorized.Write(w)
return
}
}
// generate a new token with the user name as the subject
res, err := a.buildLoginResponse(loginInfo.Email)
if err != nil {
ErrGenericInternalServerError.WithErr(err).Write(w)
return
}
// send the token back to the user
httpWriteJSON(w, res)
}

// writableOrganizationAddressesHandler returns the list of addresses of the
// organizations where the user has write access.
func (a *API) writableOrganizationAddressesHandler(w http.ResponseWriter, r *http.Request) {
Expand Down
1 change: 1 addition & 0 deletions api/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ const (
// InvitationExpiration is the duration of the invitation code before it is
// invalidated
InvitationExpiration = 5 * 24 * time.Hour // 5 days
OauthServiceURL = "http://localhost:8082/api/info"
)
2 changes: 2 additions & 0 deletions api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const (
authRefresTokenEndpoint = "/auth/refresh"
// POST /auth/login to login and get a JWT token
authLoginEndpoint = "/auth/login"
// POST /oauth/login to login verifying Oauth parameters Sand get a JWT token
oauthLoginEndpoint = "/oauth/login"
// GET /auth/addresses to get the writable organization addresses
authAddressesEndpoint = "/auth/addresses"

Expand Down
11 changes: 11 additions & 0 deletions api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ type LoginResponse struct {
Expirity time.Time `json:"expirity"`
}

type OauthLogin struct {
Email string `json:"email"`
OauthSignature string `json:"oauthSignature"`
UserOauthSignature string `json:"userOauthSignature"`
Address string `json:"address"`
}

type OauthServiceResposne struct {
Address string `json:"address"`
}

// TransactionData is the struct that contains the data of a transaction to
// be signed, but also is used to return the signed transaction.
type TransactionData struct {
Expand Down

0 comments on commit f5a924d

Please sign in to comment.