feat: add initial account services api
This commit is contained in:
parent
75d9c7f46e
commit
3c55ed2853
|
@ -2,6 +2,7 @@ package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"errors"
|
||||||
"git.lumeweb.com/LumeWeb/portal/db/models"
|
"git.lumeweb.com/LumeWeb/portal/db/models"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
@ -54,6 +55,17 @@ func (s AccountServiceDefault) AccountExists(id uint64) (bool, models.User) {
|
||||||
|
|
||||||
return result.RowsAffected > 0, model
|
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) {
|
func (s AccountServiceDefault) CreateAccount(email string, password string) (*models.User, error) {
|
||||||
var user models.User
|
var user models.User
|
||||||
|
|
||||||
|
@ -73,6 +85,29 @@ func (s AccountServiceDefault) CreateAccount(email string, password string) (*mo
|
||||||
|
|
||||||
return &user, nil
|
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 {
|
func (s AccountServiceDefault) AddPubkeyToAccount(user models.User, pubkey string) error {
|
||||||
var model models.PublicKey
|
var model models.PublicKey
|
||||||
|
|
||||||
|
@ -87,26 +122,40 @@ func (s AccountServiceDefault) AddPubkeyToAccount(user models.User, pubkey strin
|
||||||
|
|
||||||
return nil
|
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
|
var user models.User
|
||||||
|
|
||||||
result := s.db.Model(&models.User{}).Where(&models.User{Email: email}).First(&user)
|
result := s.db.Model(&models.User{}).Where(&models.User{Email: email}).First(&user)
|
||||||
|
|
||||||
if result.RowsAffected == 0 || result.Error != nil {
|
if result.RowsAffected == 0 || result.Error != nil {
|
||||||
return "", result.Error
|
return false, nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := GenerateToken(s.identity, user.ID)
|
return true, nil, nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s AccountServiceDefault) LoginPubkey(pubkey string) (string, error) {
|
func (s AccountServiceDefault) LoginPubkey(pubkey string) (string, error) {
|
||||||
|
@ -118,7 +167,7 @@ func (s AccountServiceDefault) LoginPubkey(pubkey string) (string, error) {
|
||||||
return "", result.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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateToken(privateKey ed25519.PrivateKey, userID uint) (string, error) {
|
func GenerateToken(domain string, privateKey ed25519.PrivateKey, userID uint) (string, error) {
|
||||||
return GenerateTokenWithDuration(privateKey, userID, time.Hour*24)
|
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
|
// Define the claims
|
||||||
claims := jwt.MapClaims{
|
claims := jwt.MapClaims{
|
||||||
"iss": "portal",
|
"iss": domain,
|
||||||
"sub": userID,
|
"sub": userID,
|
||||||
"exp": time.Now().Add(duration).Unix(),
|
"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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"git.lumeweb.com/LumeWeb/portal/api/account"
|
||||||
"git.lumeweb.com/LumeWeb/portal/api/registry"
|
"git.lumeweb.com/LumeWeb/portal/api/registry"
|
||||||
"git.lumeweb.com/LumeWeb/portal/api/s5"
|
"git.lumeweb.com/LumeWeb/portal/api/s5"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
@ -15,6 +16,11 @@ func RegisterApis() {
|
||||||
Module: s5.Module,
|
Module: s5.Module,
|
||||||
InitFunc: s5.InitAPI,
|
InitFunc: s5.InitAPI,
|
||||||
})
|
})
|
||||||
|
registry.Register(registry.APIEntry{
|
||||||
|
Key: "account",
|
||||||
|
Module: account.Module,
|
||||||
|
InitFunc: account.InitAPI,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildApis(config *viper.Viper) fx.Option {
|
func BuildApis(config *viper.Viper) fx.Option {
|
||||||
|
|
Loading…
Reference in New Issue