feat: Add jwt token pkg

This commit is contained in:
Muyao CHEN 2024-10-13 13:21:09 +02:00
parent 71926b2197
commit 798b9a7695
4 changed files with 174 additions and 0 deletions

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/jackc/pgx/v5 v5.7.1 github.com/jackc/pgx/v5 v5.7.1

2
go.sum
View File

@ -35,6 +35,8 @@ github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=

View File

@ -0,0 +1,56 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package middleware
import (
"net/http"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/token"
"github.com/gin-gonic/gin"
)
var ErrTokenInvalid = &errno.Errno{
HTTP: http.StatusUnauthorized,
Code: errno.ErrorCode(errno.AuthFailureCode, "TokenInvalid"),
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) {
tk, err := token.ParseRequest(ctx)
if err != nil || tk == nil {
core.WriteResponse(ctx, ErrTokenInvalid, nil)
ctx.Abort()
}
// TODO: check if the key is on logout blacklist.
ctx.Header(XUserName, tk.Identity)
ctx.Next()
}
}

115
internal/pkg/token/token.go Normal file
View File

@ -0,0 +1,115 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package token
import (
"errors"
"fmt"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
type Config struct {
secretKey string
expiryTime time.Duration
}
type Claims struct {
IdentityKey string `json:"identity_key"`
jwt.RegisteredClaims
}
type TokenResp struct {
Identity string
Expiry time.Time
}
var (
once sync.Once
config *Config
)
var ErrMissingHeader = errors.New("Authorization is needed in the header")
func Init(secretKey string, expiryTime time.Duration) {
once.Do(func() {
config.secretKey = secretKey
config.expiryTime = expiryTime
})
}
func Sign(identityKey string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{
identityKey,
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(config.expiryTime)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
})
return token.SignedString([]byte(config.secretKey))
}
func Parse(tokenString string) (*TokenResp, error) {
token, err := jwt.ParseWithClaims(
tokenString,
&Claims{},
func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return []byte(config.secretKey), nil
},
)
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok {
return &TokenResp{
claims.IdentityKey,
claims.ExpiresAt.Time,
}, nil
}
return nil, nil
}
func ParseRequest(c *gin.Context) (*TokenResp, error) {
// NOTE: Authorization: Bearer sdkfjlsfjlskdfjlsjdflk...slkdfjlka
header := c.GetHeader("Authorization")
if len(header) == 0 {
return nil, ErrMissingHeader
}
// get the token
var t string
fmt.Sscanf(header, "Bearer %s", &t)
return Parse(t)
}