Compare commits

..

2 Commits

Author SHA1 Message Date
Muyao CHEN
544ccbe1ca doc: add diary
All checks were successful
Build and test / Build (push) Successful in 2m18s
2024-10-13 22:04:12 +02:00
Muyao CHEN
9290bcf88c fix: make create session works 2024-10-13 21:55:26 +02:00
10 changed files with 57 additions and 17 deletions

View File

@ -7,7 +7,7 @@ tmp_dir = "tmp"
bin = "./_output/howmuch"
cmd = "make build"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "_output"]
exclude_dir = ["assets", "tmp", "vendor", "testdata", "_output", "internal/howmuch/adapter/repo/sqlc"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false

View File

@ -342,3 +342,19 @@ front-end for now and concentrate on my backend Go app.
For now, I will just test my backend with `curl`.
And today's job is to get the login part done!
### 2024/10/13
Finally it took more than just one night for me to figure out the JWT.
The JWT token is simple because it doesn't need to be stored to and fetched
from a database. But there is no way to revoke it instead of waiting for the
expiry date.
To do so, we still have to use a database. We can store a logged out user's
jti into Redis, and each time we log in, look up the cache to find if the
user is logged out. And set the cache's timeout to the expiry time of the
token, so that it is removed automatically.
It'd better to inject the dependency of Redis connection into the `Authn`
middleware so that it's simpler to test.

View File

@ -3,6 +3,8 @@ dev-mode: true
web:
addr: :8000
shutdown-timeout: 10
token-secret: nzMC12IJBMiiV2AAktTFpZP4BbGAf09lFPV_sATKcwI
token-expiry-time: 24h
db:
# DB host

View File

@ -35,6 +35,7 @@ import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/infra/router"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/registry"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/token"
"git.vinchent.xyz/vinchent/howmuch/pkg/version/verflag"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5"
@ -125,6 +126,9 @@ func run() error {
}
defer dbConn.Close(context.Background())
// Init token
token.Init(viper.GetString("web.token-secret"), viper.GetDuration("web.token-expiry-time"))
// Register the core service
r := registry.NewRegistry(dbConn)

View File

@ -23,6 +23,8 @@
package router
import (
"net/http"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
@ -53,6 +55,12 @@ func Routes(engine *gin.Engine, c controller.AppController) *gin.Engine {
userV1 := v1.Group("/user")
{
userV1.POST("/create", func(ctx *gin.Context) { c.User.Create(ctx) })
userV1.Use(middleware.Authn())
userV1.GET(
":id/info",
func(ctx *gin.Context) { ctx.JSON(http.StatusOK, "Hello world") },
)
}
v1.POST("/session/create", func(ctx *gin.Context) { c.Session.Create(ctx) })

View File

@ -27,7 +27,7 @@ import (
"sync"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/middleware"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/shared"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
@ -110,8 +110,12 @@ func CtxLog(ctx core.Context) *zapLogger {
func (z *zapLogger) CtxLog(ctx core.Context) *zapLogger {
zz := z.clone()
if rid := ctx.GetHeader(middleware.XRequestID); rid != "" {
zz.z = zz.z.With(zap.Any(middleware.XRequestID, rid))
if rid := ctx.GetHeader(shared.XRequestID); rid != "" {
zz.z = zz.z.With(zap.Any(shared.XRequestID, rid))
}
if user := ctx.GetHeader(shared.XUserName); user != "" {
zz.z = zz.z.With(zap.Any(shared.XUserName, user))
}
return zz

View File

@ -27,6 +27,7 @@ import (
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/shared"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/token"
"github.com/gin-gonic/gin"
)
@ -37,8 +38,6 @@ var ErrTokenInvalid = &errno.Errno{
Message: "invalid token",
}
const XUserName = "X-Username"
// Authn authenticates a user's access by validating their token.
func Authn() gin.HandlerFunc {
return func(ctx *gin.Context) {
@ -46,11 +45,12 @@ func Authn() gin.HandlerFunc {
if err != nil || tk == nil {
core.WriteResponse(ctx, ErrTokenInvalid, nil)
ctx.Abort()
return
}
// TODO: check if the key is on logout blacklist.
ctx.Header(XUserName, tk.Identity)
ctx.Header(shared.XUserName, tk.Identity)
ctx.Next()
}
}

View File

@ -23,24 +23,23 @@
package middleware
import (
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/shared"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
const XRequestID = "X-Request-Id"
func RequestID() gin.HandlerFunc {
return func(ctx *gin.Context) {
var rid string
if rid = ctx.GetHeader(XRequestID); rid != "" {
ctx.Request.Header.Add(XRequestID, rid)
if rid = ctx.GetHeader(shared.XRequestID); rid != "" {
ctx.Request.Header.Add(shared.XRequestID, rid)
ctx.Next()
}
rid = uuid.NewString()
ctx.Request.Header.Add(XRequestID, rid)
ctx.Header(XRequestID, rid)
ctx.Request.Header.Add(shared.XRequestID, rid)
ctx.Header(shared.XRequestID, rid)
ctx.Next()
}
}

View File

@ -26,6 +26,7 @@ import (
"net/http"
"testing"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/shared"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/test"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
@ -39,12 +40,12 @@ func TestRequestID(t *testing.T) {
wanted := "123"
r.GET("/example", func(c *gin.Context) {
got = c.GetHeader(XRequestID)
got = c.GetHeader(shared.XRequestID)
c.Status(http.StatusOK)
})
r.POST("/example", func(c *gin.Context) {
got = c.GetHeader(XRequestID)
got = c.GetHeader(shared.XRequestID)
c.String(http.StatusAccepted, "ok")
})
@ -55,12 +56,12 @@ func TestRequestID(t *testing.T) {
"GET",
"/example?a=100",
nil,
test.Header{Key: XRequestID, Value: wanted},
test.Header{Key: shared.XRequestID, Value: wanted},
)
assert.Equal(t, "123", got)
res := test.PerformRequest(t, r, "GET", "/example?a=100", nil)
assert.NotEqual(t, "", got)
assert.NoError(t, uuid.Validate(got))
assert.Equal(t, res.Header()[XRequestID][0], got)
assert.Equal(t, res.Header()[shared.XRequestID][0], got)
}

View File

@ -0,0 +1,6 @@
package shared
const (
XRequestID = "X-Request-Id"
XUserName = "X-Username"
)