feat: Add jwt token pkg
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@ -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
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@ -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=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										56
									
								
								internal/pkg/middleware/authn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								internal/pkg/middleware/authn.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										115
									
								
								internal/pkg/token/token.go
									
									
									
									
									
										Normal 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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user