mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-04-12 08:56:28 +00:00
join handler implemented
This commit is contained in:
parent
710c464989
commit
16cf2c2d0a
@ -1,14 +1,27 @@
|
|||||||
|
PROJECT_DIR = $(CURDIR)
|
||||||
|
PROJECT_BIN = ${PROJECT_DIR}/bin
|
||||||
|
TOOLS_BIN = ${PROJECT_BIN}/tools
|
||||||
|
|
||||||
bin.build:
|
bin.build:
|
||||||
mkdir -p build
|
mkdir -p ${PROJECT_DIR}/build
|
||||||
rm -f build/blockd
|
rm -f ${PROJECT_DIR}/build/blockd
|
||||||
go build -ldflags="-s -w" -o build/blockd cmd/main.go
|
go build -ldflags="-s -w" -o ${PROJECT_DIR}/build/blockd ${PROJECT_DIR}/cmd/main.go
|
||||||
|
|
||||||
d.build:
|
d.build:
|
||||||
sudo docker buildx build . -t blockd:latest
|
sudo docker buildx build . -t blockd:latest
|
||||||
|
|
||||||
|
d.net:
|
||||||
|
sudo docker network create --driver bridge --subnet=192.168.2.0/24 --attachable blockd-net
|
||||||
|
|
||||||
|
d.drop-net:
|
||||||
|
sudo docker network rm blockd-net
|
||||||
|
|
||||||
|
up:
|
||||||
|
sudo docker compose up -d
|
||||||
|
|
||||||
.PHONY: run.local
|
.PHONY: run.local
|
||||||
run.local: bin.build
|
run.local: bin.build
|
||||||
./build/blockd \
|
${PROJECT_DIR}/build/blockd \
|
||||||
-log-level=debug \
|
-log-level=debug \
|
||||||
-log-local=true \
|
-log-local=true \
|
||||||
-log-add-source=true \
|
-log-add-source=true \
|
||||||
@ -21,7 +34,7 @@ run.local: bin.build
|
|||||||
|
|
||||||
.PHONY: run.debug
|
.PHONY: run.debug
|
||||||
run.debug: bin.build
|
run.debug: bin.build
|
||||||
./build/blockd \
|
${PROJECT_DIR}/build/blockd \
|
||||||
-log-level=debug \
|
-log-level=debug \
|
||||||
-log-local=false \
|
-log-local=false \
|
||||||
-log-add-source=true \
|
-log-add-source=true \
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/emochka2007/block-accounting/cmd/commands"
|
"github.com/emochka2007/block-accounting/cmd/commands"
|
||||||
"github.com/emochka2007/block-accounting/internal/config"
|
|
||||||
"github.com/emochka2007/block-accounting/internal/factory"
|
"github.com/emochka2007/block-accounting/internal/factory"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/config"
|
||||||
|
|
||||||
cli "github.com/urfave/cli/v2"
|
cli "github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -5,12 +5,15 @@ go 1.22.2
|
|||||||
require (
|
require (
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/go-chi/chi/v5 v5.0.12
|
github.com/go-chi/chi/v5 v5.0.12
|
||||||
|
github.com/go-chi/render v1.0.3
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/google/wire v0.6.0
|
github.com/google/wire v0.6.0
|
||||||
github.com/urfave/cli/v2 v2.27.2
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/crypto v0.18.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||||
@ -24,6 +30,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
@ -41,8 +48,6 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -7,10 +7,11 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/emochka2007/block-accounting/internal/config"
|
|
||||||
"github.com/emochka2007/block-accounting/internal/interface/controllers"
|
|
||||||
"github.com/emochka2007/block-accounting/internal/interface/rest"
|
"github.com/emochka2007/block-accounting/internal/interface/rest"
|
||||||
"github.com/emochka2007/block-accounting/internal/logger"
|
"github.com/emochka2007/block-accounting/internal/interface/rest/controllers"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/config"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/logger"
|
||||||
"github.com/emochka2007/block-accounting/internal/service"
|
"github.com/emochka2007/block-accounting/internal/service"
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
@ -54,6 +55,10 @@ func provideControllers(
|
|||||||
) *controllers.RootController {
|
) *controllers.RootController {
|
||||||
return &controllers.RootController{
|
return &controllers.RootController{
|
||||||
Ping: controllers.NewPingController(log.WithGroup("ping-controller")),
|
Ping: controllers.NewPingController(log.WithGroup("ping-controller")),
|
||||||
|
Auth: controllers.NewAuthController(
|
||||||
|
log.WithGroup("auth-controller"),
|
||||||
|
presenters.NewAuthPresenter(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,11 @@
|
|||||||
package factory
|
package factory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/emochka2007/block-accounting/internal/config"
|
|
||||||
"github.com/emochka2007/block-accounting/internal/interface/controllers"
|
|
||||||
"github.com/emochka2007/block-accounting/internal/interface/rest"
|
"github.com/emochka2007/block-accounting/internal/interface/rest"
|
||||||
"github.com/emochka2007/block-accounting/internal/logger"
|
"github.com/emochka2007/block-accounting/internal/interface/rest/controllers"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/config"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/logger"
|
||||||
"github.com/emochka2007/block-accounting/internal/service"
|
"github.com/emochka2007/block-accounting/internal/service"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
@ -57,6 +58,9 @@ func provideControllers(
|
|||||||
) *controllers.RootController {
|
) *controllers.RootController {
|
||||||
return &controllers.RootController{
|
return &controllers.RootController{
|
||||||
Ping: controllers.NewPingController(log.WithGroup("ping-controller")),
|
Ping: controllers.NewPingController(log.WithGroup("ping-controller")),
|
||||||
|
Auth: controllers.NewAuthController(
|
||||||
|
log.WithGroup("auth-controller"), presenters.NewAuthPresenter(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
72
backend/internal/interface/rest/controllers/auth.go
Normal file
72
backend/internal/interface/rest/controllers/auth.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/bip32"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthController interface {
|
||||||
|
Join(w http.ResponseWriter, req *http.Request) error
|
||||||
|
Login(w http.ResponseWriter, req *http.Request) error
|
||||||
|
Invite(w http.ResponseWriter, req *http.Request) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type authController struct {
|
||||||
|
log *slog.Logger
|
||||||
|
presenter presenters.AuthPresenter
|
||||||
|
// interactors ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthController(
|
||||||
|
log *slog.Logger,
|
||||||
|
presenter presenters.AuthPresenter,
|
||||||
|
) AuthController {
|
||||||
|
return &authController{
|
||||||
|
log: log,
|
||||||
|
presenter: presenter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mnemonicEntropyBitSize int = 256
|
||||||
|
|
||||||
|
func (c *authController) Join(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
entropy, err := bip32.NewEntropy(mnemonicEntropyBitSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error generate new entropy. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mnemonic, err := bip32.NewMnemonic(entropy)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error generate mnemonic from entropy. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo create user
|
||||||
|
|
||||||
|
// move to presenter
|
||||||
|
out, err := json.Marshal(domain.JoinResponse{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshal join response. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(out)
|
||||||
|
|
||||||
|
// move to presenter
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *authController) Login(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
return nil // implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *authController) Invite(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
return nil // implement
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PingController interface {
|
type PingController interface {
|
||||||
HandlePing(ctx context.Context, req *http.Request, w http.ResponseWriter) error
|
Ping(w http.ResponseWriter, req *http.Request) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type pingController struct {
|
type pingController struct {
|
||||||
@ -22,7 +21,7 @@ func NewPingController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *pingController) HandlePing(ctx context.Context, req *http.Request, w http.ResponseWriter) error {
|
func (c *pingController) Ping(w http.ResponseWriter, req *http.Request) error {
|
||||||
_, err := w.Write([]byte("pong"))
|
_, err := w.Write([]byte("pong"))
|
||||||
|
|
||||||
return err
|
return err
|
@ -2,12 +2,15 @@ package controllers
|
|||||||
|
|
||||||
type RootController struct {
|
type RootController struct {
|
||||||
Ping PingController
|
Ping PingController
|
||||||
|
Auth AuthController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRootController(
|
func NewRootController(
|
||||||
ping PingController,
|
ping PingController,
|
||||||
|
auth AuthController,
|
||||||
) *RootController {
|
) *RootController {
|
||||||
return &RootController{
|
return &RootController{
|
||||||
Ping: ping,
|
Ping: ping,
|
||||||
|
Auth: auth,
|
||||||
}
|
}
|
||||||
}
|
}
|
20
backend/internal/interface/rest/domain/dto.go
Normal file
20
backend/internal/interface/rest/domain/dto.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JoinResponse struct {
|
||||||
|
Mnemonic string `json:"mnemonic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildRequest[T any](data []byte) (*T, error) {
|
||||||
|
var req T
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &req); err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshal request. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &req, nil
|
||||||
|
}
|
34
backend/internal/interface/rest/presenters/auth.go
Normal file
34
backend/internal/interface/rest/presenters/auth.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package presenters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthPresenter interface {
|
||||||
|
ResponseJoin(w http.ResponseWriter, mnemonic string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type authPresenter struct{}
|
||||||
|
|
||||||
|
func NewAuthPresenter() AuthPresenter {
|
||||||
|
return &authPresenter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *authPresenter) ResponseJoin(w http.ResponseWriter, mnemonic string) error {
|
||||||
|
out, err := json.Marshal(domain.JoinResponse{
|
||||||
|
Mnemonic: mnemonic,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshal join response. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = w.Write(out); err != nil {
|
||||||
|
return fmt.Errorf("error write response. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -7,11 +7,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/emochka2007/block-accounting/internal/config"
|
"github.com/emochka2007/block-accounting/internal/interface/rest/controllers"
|
||||||
"github.com/emochka2007/block-accounting/internal/interface/controllers"
|
"github.com/emochka2007/block-accounting/internal/pkg/config"
|
||||||
"github.com/emochka2007/block-accounting/internal/logger"
|
"github.com/emochka2007/block-accounting/internal/pkg/logger"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
mw "github.com/go-chi/chi/v5/middleware"
|
mw "github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@ -63,23 +64,25 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||||||
|
|
||||||
func (s *Server) Close() {
|
func (s *Server) Close() {
|
||||||
s.closeMu.Lock()
|
s.closeMu.Lock()
|
||||||
defer s.closeMu.Unlock()
|
|
||||||
|
|
||||||
s.closed = true
|
s.closed = true
|
||||||
|
|
||||||
|
s.closeMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) buildRouter() {
|
func (s *Server) buildRouter() {
|
||||||
s.Mux = chi.NewRouter()
|
s.Mux = chi.NewRouter()
|
||||||
|
|
||||||
s.With(mw.Recoverer)
|
s.Use(mw.Recoverer)
|
||||||
s.With(mw.RequestID)
|
s.Use(mw.RequestID)
|
||||||
s.With(s.handleMw)
|
s.Use(s.handleMw)
|
||||||
|
s.Use(render.SetContentType(render.ContentTypeJSON))
|
||||||
|
|
||||||
s.Get("/ping", s.handlePing) // debug
|
s.Get("/ping", s.handlePing) // debug
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
s.Post("/join", nil) // new user
|
s.Post("/join", s.handleJoin) // new user
|
||||||
s.Post("/login", nil) // login
|
s.Post("/login", nil) // login
|
||||||
|
|
||||||
s.Route("/organization/{organization_id}", func(r chi.Router) {
|
s.Route("/organization/{organization_id}", func(r chi.Router) {
|
||||||
s.Route("/transactions", func(r chi.Router) {
|
s.Route("/transactions", func(r chi.Router) {
|
||||||
@ -118,22 +121,30 @@ func (s *Server) responseError(w http.ResponseWriter, e error) {
|
|||||||
func (s *Server) handleMw(next http.Handler) http.Handler {
|
func (s *Server) handleMw(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
s.closeMu.RLock()
|
s.closeMu.RLock()
|
||||||
defer s.closeMu.Unlock()
|
defer s.closeMu.RUnlock()
|
||||||
|
|
||||||
if s.closed { // keep mutex closed
|
if s.closed { // keep mutex closed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlePing(w http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleJoin(w http.ResponseWriter, req *http.Request) {
|
||||||
s.log.Debug("ping request")
|
if err := s.controllers.Auth.Join(w, req); err != nil {
|
||||||
|
s.responseError(w, err)
|
||||||
if err := s.controllers.Ping.HandlePing(s.ctx, req, w); err != nil {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handlePing(w http.ResponseWriter, req *http.Request) {
|
||||||
|
s.log.Debug("ping request")
|
||||||
|
|
||||||
|
if err := s.controllers.Ping.Ping(w, req); err != nil {
|
||||||
s.responseError(w, err)
|
s.responseError(w, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
348
backend/internal/pkg/bip32/bip32.go
Normal file
348
backend/internal/pkg/bip32/bip32.go
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
package bip32
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Some bitwise operands for working with big.Ints.
|
||||||
|
last11BitsMask = big.NewInt(2047)
|
||||||
|
shift11BitsMask = big.NewInt(2048)
|
||||||
|
bigOne = big.NewInt(1)
|
||||||
|
bigTwo = big.NewInt(2)
|
||||||
|
|
||||||
|
// wordLengthChecksumMasksMapping is used to isolate the checksum bits from
|
||||||
|
//the entropy+checksum byte array.
|
||||||
|
wordLengthChecksumMasksMapping = map[int]*big.Int{
|
||||||
|
12: big.NewInt(15),
|
||||||
|
15: big.NewInt(31),
|
||||||
|
18: big.NewInt(63),
|
||||||
|
21: big.NewInt(127),
|
||||||
|
24: big.NewInt(255),
|
||||||
|
}
|
||||||
|
|
||||||
|
// wordLengthChecksumShiftMapping is used to lookup the number of operand
|
||||||
|
// for shifting bits to handle checksums.
|
||||||
|
wordLengthChecksumShiftMapping = map[int]*big.Int{
|
||||||
|
12: big.NewInt(16),
|
||||||
|
15: big.NewInt(8),
|
||||||
|
18: big.NewInt(4),
|
||||||
|
21: big.NewInt(2),
|
||||||
|
}
|
||||||
|
|
||||||
|
// wordList is the set of words to use.
|
||||||
|
wordList []string
|
||||||
|
|
||||||
|
// wordMap is a reverse lookup map for wordList.
|
||||||
|
wordMap map[string]int
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidMnemonic is returned when trying to use a malformed mnemonic.
|
||||||
|
ErrInvalidMnemonic = errors.New("Invalid mnenomic")
|
||||||
|
|
||||||
|
// ErrEntropyLengthInvalid is returned when trying to use an entropy set with
|
||||||
|
// an invalid size.
|
||||||
|
ErrEntropyLengthInvalid = errors.New("Entropy length must be [128, 256] and a multiple of 32")
|
||||||
|
|
||||||
|
// ErrValidatedSeedLengthMismatch is returned when a validated seed is not the
|
||||||
|
// same size as the given seed. This should never happen is present only as a
|
||||||
|
// sanity assertion.
|
||||||
|
ErrValidatedSeedLengthMismatch = errors.New("Seed length does not match validated seed length")
|
||||||
|
|
||||||
|
// ErrChecksumIncorrect is returned when entropy has the incorrect checksum.
|
||||||
|
ErrChecksumIncorrect = errors.New("Checksum incorrect")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetWordList(English)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWordList sets the list of words to use for mnemonics. Currently the list
|
||||||
|
// that is set is used package-wide.
|
||||||
|
func SetWordList(list []string) {
|
||||||
|
wordList = list
|
||||||
|
wordMap = map[string]int{}
|
||||||
|
|
||||||
|
for i, v := range wordList {
|
||||||
|
wordMap[v] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWordList gets the list of words to use for mnemonics.
|
||||||
|
func GetWordList() []string {
|
||||||
|
return wordList
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWordIndex gets word index in wordMap.
|
||||||
|
func GetWordIndex(word string) (int, bool) {
|
||||||
|
idx, ok := wordMap[word]
|
||||||
|
return idx, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEntropy will create random entropy bytes
|
||||||
|
// so long as the requested size bitSize is an appropriate size.
|
||||||
|
//
|
||||||
|
// bitSize has to be a multiple 32 and be within the inclusive range of {128, 256}.
|
||||||
|
func NewEntropy(bitSize int) ([]byte, error) {
|
||||||
|
if err := validateEntropyBitSize(bitSize); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entropy := make([]byte, bitSize/8)
|
||||||
|
_, _ = rand.Read(entropy) // err is always nil
|
||||||
|
|
||||||
|
return entropy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntropyFromMnemonic takes a mnemonic generated by this library,
|
||||||
|
// and returns the input entropy used to generate the given mnemonic.
|
||||||
|
// An error is returned if the given mnemonic is invalid.
|
||||||
|
func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
|
||||||
|
mnemonicSlice, isValid := splitMnemonicWords(mnemonic)
|
||||||
|
if !isValid {
|
||||||
|
return nil, ErrInvalidMnemonic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the words into a big.Int.
|
||||||
|
var (
|
||||||
|
wordBytes [2]byte
|
||||||
|
b = big.NewInt(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, v := range mnemonicSlice {
|
||||||
|
index, found := wordMap[v]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("word `%v` not found in reverse map", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(wordBytes[:], uint16(index))
|
||||||
|
b.Mul(b, shift11BitsMask)
|
||||||
|
b.Or(b, big.NewInt(0).SetBytes(wordBytes[:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and add the checksum to the big.Int.
|
||||||
|
checksum := big.NewInt(0)
|
||||||
|
checksumMask := wordLengthChecksumMasksMapping[len(mnemonicSlice)]
|
||||||
|
checksum = checksum.And(b, checksumMask)
|
||||||
|
|
||||||
|
b.Div(b, big.NewInt(0).Add(checksumMask, bigOne))
|
||||||
|
|
||||||
|
// The entropy is the underlying bytes of the big.Int. Any upper bytes of
|
||||||
|
// all 0's are not returned so we pad the beginning of the slice with empty
|
||||||
|
// bytes if necessary.
|
||||||
|
entropy := b.Bytes()
|
||||||
|
entropy = padByteSlice(entropy, len(mnemonicSlice)/3*4)
|
||||||
|
|
||||||
|
// Generate the checksum and compare with the one we got from the mneomnic.
|
||||||
|
entropyChecksumBytes := computeChecksum(entropy)
|
||||||
|
entropyChecksum := big.NewInt(int64(entropyChecksumBytes[0]))
|
||||||
|
|
||||||
|
if l := len(mnemonicSlice); l != 24 {
|
||||||
|
checksumShift := wordLengthChecksumShiftMapping[l]
|
||||||
|
entropyChecksum.Div(entropyChecksum, checksumShift)
|
||||||
|
}
|
||||||
|
|
||||||
|
if checksum.Cmp(entropyChecksum) != 0 {
|
||||||
|
return nil, ErrChecksumIncorrect
|
||||||
|
}
|
||||||
|
|
||||||
|
return entropy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMnemonic will return a string consisting of the mnemonic words for
|
||||||
|
// the given entropy.
|
||||||
|
// If the provide entropy is invalid, an error will be returned.
|
||||||
|
func NewMnemonic(entropy []byte) (string, error) {
|
||||||
|
// Compute some lengths for convenience.
|
||||||
|
entropyBitLength := len(entropy) * 8
|
||||||
|
checksumBitLength := entropyBitLength / 32
|
||||||
|
sentenceLength := (entropyBitLength + checksumBitLength) / 11
|
||||||
|
|
||||||
|
// Validate that the requested size is supported.
|
||||||
|
err := validateEntropyBitSize(entropyBitLength)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add checksum to entropy.
|
||||||
|
entropy = addChecksum(entropy)
|
||||||
|
|
||||||
|
// Break entropy up into sentenceLength chunks of 11 bits.
|
||||||
|
// For each word AND mask the rightmost 11 bits and find the word at that index.
|
||||||
|
// Then bitshift entropy 11 bits right and repeat.
|
||||||
|
// Add to the last empty slot so we can work with LSBs instead of MSB.
|
||||||
|
|
||||||
|
// Entropy as an int so we can bitmask without worrying about bytes slices.
|
||||||
|
entropyInt := new(big.Int).SetBytes(entropy)
|
||||||
|
|
||||||
|
// Slice to hold words in.
|
||||||
|
words := make([]string, sentenceLength)
|
||||||
|
|
||||||
|
// Throw away big.Int for AND masking.
|
||||||
|
word := big.NewInt(0)
|
||||||
|
|
||||||
|
for i := sentenceLength - 1; i >= 0; i-- {
|
||||||
|
// Get 11 right most bits and bitshift 11 to the right for next time.
|
||||||
|
word.And(entropyInt, last11BitsMask)
|
||||||
|
entropyInt.Div(entropyInt, shift11BitsMask)
|
||||||
|
|
||||||
|
// Get the bytes representing the 11 bits as a 2 byte slice.
|
||||||
|
wordBytes := padByteSlice(word.Bytes(), 2)
|
||||||
|
|
||||||
|
// Convert bytes to an index and add that word to the list.
|
||||||
|
words[i] = wordList[binary.BigEndian.Uint16(wordBytes)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(words, " "), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MnemonicToByteArray takes a mnemonic string and turns it into a byte array
|
||||||
|
// suitable for creating another mnemonic.
|
||||||
|
// An error is returned if the mnemonic is invalid.
|
||||||
|
func MnemonicToByteArray(mnemonic string, raw ...bool) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
mnemonicSlice = strings.Split(mnemonic, " ")
|
||||||
|
entropyBitSize = len(mnemonicSlice) * 11
|
||||||
|
checksumBitSize = entropyBitSize % 32
|
||||||
|
fullByteSize = (entropyBitSize-checksumBitSize)/8 + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Turn into raw entropy.
|
||||||
|
rawEntropyBytes, err := EntropyFromMnemonic(mnemonic)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we want the raw entropy then we're done.
|
||||||
|
if len(raw) > 0 && raw[0] {
|
||||||
|
return rawEntropyBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise add the checksum before returning
|
||||||
|
return padByteSlice(addChecksum(rawEntropyBytes), fullByteSize), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSeedWithErrorChecking creates a hashed seed output given the mnemonic string and a password.
|
||||||
|
// An error is returned if the mnemonic is not convertible to a byte array.
|
||||||
|
func NewSeedWithErrorChecking(mnemonic string, password string) ([]byte, error) {
|
||||||
|
_, err := MnemonicToByteArray(mnemonic)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewSeed(mnemonic, password), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSeed creates a hashed seed output given a provided string and password.
|
||||||
|
// No checking is performed to validate that the string provided is a valid mnemonic.
|
||||||
|
func NewSeed(mnemonic string, password string) []byte {
|
||||||
|
return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMnemonicValid attempts to verify that the provided mnemonic is valid.
|
||||||
|
// Validity is determined by both the number of words being appropriate,
|
||||||
|
// and that all the words in the mnemonic are present in the word list.
|
||||||
|
func IsMnemonicValid(mnemonic string) bool {
|
||||||
|
_, err := EntropyFromMnemonic(mnemonic)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends to data the first (len(data) / 32)bits of the result of sha256(data)
|
||||||
|
// Currently only supports data up to 32 bytes.
|
||||||
|
func addChecksum(data []byte) []byte {
|
||||||
|
// Get first byte of sha256
|
||||||
|
hash := computeChecksum(data)
|
||||||
|
firstChecksumByte := hash[0]
|
||||||
|
|
||||||
|
// len() is in bytes so we divide by 4
|
||||||
|
checksumBitLength := uint(len(data) / 4)
|
||||||
|
|
||||||
|
// For each bit of check sum we want we shift the data one the left
|
||||||
|
// and then set the (new) right most bit equal to checksum bit at that index
|
||||||
|
// staring from the left
|
||||||
|
dataBigInt := new(big.Int).SetBytes(data)
|
||||||
|
|
||||||
|
for i := uint(0); i < checksumBitLength; i++ {
|
||||||
|
// Bitshift 1 left
|
||||||
|
dataBigInt.Mul(dataBigInt, bigTwo)
|
||||||
|
|
||||||
|
// Set rightmost bit if leftmost checksum bit is set
|
||||||
|
if firstChecksumByte&(1<<(7-i)) > 0 {
|
||||||
|
dataBigInt.Or(dataBigInt, bigOne)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataBigInt.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeChecksum(data []byte) []byte {
|
||||||
|
hasher := sha256.New()
|
||||||
|
_, _ = hasher.Write(data) // This error is guaranteed to be nil
|
||||||
|
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateEntropyBitSize ensures that entropy is the correct size for being a
|
||||||
|
// mnemonic.
|
||||||
|
func validateEntropyBitSize(bitSize int) error {
|
||||||
|
if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 {
|
||||||
|
return ErrEntropyLengthInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// padByteSlice returns a byte slice of the given size with contents of the
|
||||||
|
// given slice left padded and any empty spaces filled with 0's.
|
||||||
|
func padByteSlice(slice []byte, length int) []byte {
|
||||||
|
offset := length - len(slice)
|
||||||
|
if offset <= 0 {
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
newSlice := make([]byte, length)
|
||||||
|
copy(newSlice[offset:], slice)
|
||||||
|
|
||||||
|
return newSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareByteSlices returns true of the byte slices have equal contents and
|
||||||
|
// returns false otherwise.
|
||||||
|
func compareByteSlices(a, b []byte) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitMnemonicWords(mnemonic string) ([]string, bool) {
|
||||||
|
// Create a list of all the words in the mnemonic sentence
|
||||||
|
words := strings.Fields(mnemonic)
|
||||||
|
|
||||||
|
// Get num of words
|
||||||
|
numOfWords := len(words)
|
||||||
|
|
||||||
|
// The number of words should be 12, 15, 18, 21 or 24
|
||||||
|
if numOfWords%3 != 0 || numOfWords < 12 || numOfWords > 24 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return words, true
|
||||||
|
}
|
2071
backend/internal/pkg/bip32/wordslist.go
Normal file
2071
backend/internal/pkg/bip32/wordslist.go
Normal file
File diff suppressed because it is too large
Load Diff
6
backend/internal/pkg/models/user.go
Normal file
6
backend/internal/pkg/models/user.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type UserIdentity interface {
|
||||||
|
Id() string
|
||||||
|
Mnemonic() string
|
||||||
|
}
|
@ -43,6 +43,8 @@ func (s *ServiceImpl) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
s.log.Info("shutting down service")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
case err := <-errch:
|
case err := <-errch:
|
||||||
return fmt.Errorf("error at service runtime. %w", err)
|
return fmt.Errorf("error at service runtime. %w", err)
|
||||||
|
39
backend/internal/usecase/interactors/jwt/jwt.go
Normal file
39
backend/internal/usecase/interactors/jwt/jwt.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWTInteractor interface {
|
||||||
|
NewToken(user models.UserIdentity, duration time.Duration) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwtInteractor struct {
|
||||||
|
secret []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWardenJWT(secret []byte) JWTInteractor {
|
||||||
|
return &jwtInteractor{secret}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewToken creates new JWT token for given user
|
||||||
|
func (w *jwtInteractor) NewToken(user models.UserIdentity, duration time.Duration) (string, error) {
|
||||||
|
token := jwt.New(jwt.SigningMethodHS256)
|
||||||
|
|
||||||
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
|
claims["uid"] = user.Id()
|
||||||
|
claims["exp"] = time.Now().Add(duration).Unix()
|
||||||
|
|
||||||
|
secret := w.secret
|
||||||
|
|
||||||
|
tokenString, err := token.SignedString([]byte(secret))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error sign token. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user