feat: add initial account services api
This commit is contained in:
parent
75d9c7f46e
commit
3c55ed2853
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue