feat: add initial account services api

This commit is contained in:
Derrick Hammer 2024-02-13 19:07:24 -05:00
parent 75d9c7f46e
commit 3c55ed2853
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
6 changed files with 235 additions and 14 deletions

View File

@ -2,6 +2,7 @@ package account
import (
"crypto/ed25519"
"errors"
"git.lumeweb.com/LumeWeb/portal/db/models"
"github.com/spf13/viper"
"go.uber.org/fx"
@ -54,6 +55,17 @@ func (s AccountServiceDefault) AccountExists(id uint64) (bool, models.User) {
return result.RowsAffected > 0, model
}
func (s AccountServiceDefault) AccountExistsByEmail(email string) (bool, models.User) {
var model models.User
model.Email = email
result := s.db.Model(&models.User{}).Where(&model).First(&model)
return result.RowsAffected > 0, model
}
func (s AccountServiceDefault) CreateAccount(email string, password string) (*models.User, error) {
var user models.User
@ -73,6 +85,29 @@ func (s AccountServiceDefault) CreateAccount(email string, password string) (*mo
return &user, nil
}
func (s AccountServiceDefault) UpdateAccountName(userId uint, firstName string, lastName string) error {
var user models.User
user.ID = userId
if len(firstName) == 0 {
return errors.New("First name cannot be empty")
}
if len(lastName) == 0 {
return errors.New("Last name cannot be empty")
}
result := s.db.Model(&models.User{}).Where(&user).Updates(&models.User{FirstName: firstName, LastName: lastName})
if result.Error != nil {
return result.Error
}
return nil
}
func (s AccountServiceDefault) AddPubkeyToAccount(user models.User, pubkey string) error {
var model models.PublicKey
@ -87,26 +122,40 @@ func (s AccountServiceDefault) AddPubkeyToAccount(user models.User, pubkey strin
return nil
}
func (s AccountServiceDefault) LoginPassword(email string, password string) (string, error) {
func (s AccountServiceDefault) LoginPassword(email string, password string) (string, *models.User, error) {
valid, user, err := s.ValidLogin(email, password)
if err != nil {
return "", nil, err
}
if !valid {
return "", nil, nil
}
token, err := GenerateToken(s.config.GetString("core.domain"), s.identity, user.ID)
if err != nil {
return "", nil, err
}
return token, nil, nil
}
func (s AccountServiceDefault) ValidLogin(email string, password string) (bool, *models.User, error) {
var user models.User
result := s.db.Model(&models.User{}).Where(&models.User{Email: email}).First(&user)
if result.RowsAffected == 0 || result.Error != nil {
return "", result.Error
return false, nil, result.Error
}
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
if err != nil {
return "", err
return false, nil, err
}
token, err := GenerateToken(s.identity, user.ID)
if err != nil {
return "", err
}
return token, nil
return true, nil, nil
}
func (s AccountServiceDefault) LoginPubkey(pubkey string) (string, error) {
@ -118,7 +167,7 @@ func (s AccountServiceDefault) LoginPubkey(pubkey string) (string, error) {
return "", result.Error
}
token, err := GenerateToken(s.identity, model.UserID)
token, err := GenerateToken(s.config.GetString("core.domain"), s.identity, model.UserID)
if err != nil {
return "", err
}

View File

@ -6,13 +6,13 @@ import (
"time"
)
func GenerateToken(privateKey ed25519.PrivateKey, userID uint) (string, error) {
return GenerateTokenWithDuration(privateKey, userID, time.Hour*24)
func GenerateToken(domain string, privateKey ed25519.PrivateKey, userID uint) (string, error) {
return GenerateTokenWithDuration(domain, privateKey, userID, time.Hour*24)
}
func GenerateTokenWithDuration(privateKey ed25519.PrivateKey, userID uint, duration time.Duration) (string, error) {
func GenerateTokenWithDuration(domain string, privateKey ed25519.PrivateKey, userID uint, duration time.Duration) (string, error) {
// Define the claims
claims := jwt.MapClaims{
"iss": "portal",
"iss": domain,
"sub": userID,
"exp": time.Now().Add(duration).Unix(),
}

80
api/account/account.go Normal file
View File

@ -0,0 +1,80 @@
package account
import (
"context"
"git.lumeweb.com/LumeWeb/portal/account"
"git.lumeweb.com/LumeWeb/portal/api/middleware"
"git.lumeweb.com/LumeWeb/portal/api/registry"
"github.com/spf13/viper"
"go.sia.tech/jape"
"go.uber.org/fx"
)
var (
_ registry.API = (*AccountAPI)(nil)
)
type AccountAPI struct {
config *viper.Viper
accounts *account.AccountServiceDefault
httpHandler *HttpHandler
}
type AccountAPIParams struct {
fx.In
Config *viper.Viper
Accounts *account.AccountServiceDefault
HttpHandler *HttpHandler
}
func NewS5(params AccountAPIParams) AccountApiResult {
api := &AccountAPI{
config: params.Config,
accounts: params.Accounts,
httpHandler: params.HttpHandler,
}
return AccountApiResult{
API: api,
AccountAPI: api,
}
}
func InitAPI(api *AccountAPI) error {
return api.Init()
}
var Module = fx.Module("s5_api",
fx.Provide(NewS5),
fx.Provide(NewHttpHandler),
)
type AccountApiResult struct {
fx.Out
API registry.API `group:"api"`
AccountAPI *AccountAPI
}
func (a AccountAPI) Name() string {
return "account"
}
func (a *AccountAPI) Init() error {
middleware.RegisterProtocolSubdomain(a.config, jape.Mux(getRoutes(a)), "s5")
return nil
}
func (a AccountAPI) Start(ctx context.Context) error {
return nil
}
func (a AccountAPI) Stop(ctx context.Context) error {
return nil
}
func getRoutes(a *AccountAPI) map[string]jape.Handler {
return map[string]jape.Handler{
"/api/login": a.httpHandler.login,
"api/register": a.httpHandler.register,
}
}

73
api/account/http.go Normal file
View File

@ -0,0 +1,73 @@
package account
import (
"errors"
"git.lumeweb.com/LumeWeb/portal/account"
"go.sia.tech/jape"
"go.uber.org/fx"
"net/http"
)
var (
errInvalidLogin = errors.New("invalid login")
errFailedToCreateAccount = errors.New("failed to create account")
)
type HttpHandler struct {
accounts *account.AccountServiceDefault
}
type HttpHandlerParams struct {
fx.In
Accounts *account.AccountServiceDefault
}
func NewHttpHandler(params HttpHandlerParams) *HttpHandler {
return &HttpHandler{
accounts: params.Accounts,
}
}
func (h *HttpHandler) login(jc jape.Context) {
var request LoginRequest
if jc.Decode(&request) != nil {
return
}
exists, _ := h.accounts.AccountExistsByEmail(request.Email)
if !exists {
_ = jc.Error(errInvalidLogin, http.StatusUnauthorized)
return
}
jwt, _, err := h.accounts.LoginPassword(request.Email, request.Password)
if err != nil {
return
}
jc.ResponseWriter.Header().Set("Authorization", "Bearer "+jwt)
jc.ResponseWriter.WriteHeader(http.StatusOK)
}
func (h *HttpHandler) register(jc jape.Context) {
var request RegisterRequest
if jc.Decode(&request) != nil {
return
}
user, err := h.accounts.CreateAccount(request.Email, request.Password)
if err != nil {
_ = jc.Error(errFailedToCreateAccount, http.StatusBadRequest)
return
}
err = h.accounts.UpdateAccountName(user.ID, request.FirstName, request.LastName)
if err != nil {
_ = jc.Error(errors.Join(errFailedToCreateAccount, err), http.StatusBadRequest)
return
}
}

13
api/account/messages.go Normal file
View File

@ -0,0 +1,13 @@
package account
type LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
type RegisterRequest struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Password string `json:"password"`
}

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"git.lumeweb.com/LumeWeb/portal/api/account"
"git.lumeweb.com/LumeWeb/portal/api/registry"
"git.lumeweb.com/LumeWeb/portal/api/s5"
"github.com/samber/lo"
@ -15,6 +16,11 @@ func RegisterApis() {
Module: s5.Module,
InitFunc: s5.InitAPI,
})
registry.Register(registry.APIEntry{
Key: "account",
Module: account.Module,
InitFunc: account.InitAPI,
})
}
func BuildApis(config *viper.Viper) fx.Option {