initial commit
This commit is contained in:
37
pkg/jwt/jwt.go
Normal file
37
pkg/jwt/jwt.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AccessRefreshTokenPair struct {
|
||||
AccessToken string
|
||||
AccessTokenExpiresAt time.Time
|
||||
RefreshToken string
|
||||
RefreshTokenExpiresAt time.Time
|
||||
}
|
||||
|
||||
type TokenPayload struct {
|
||||
Sub string
|
||||
Aud []string
|
||||
Iat time.Time
|
||||
Exp time.Time
|
||||
Iss string
|
||||
}
|
||||
|
||||
type GenerateTokenInput struct {
|
||||
Sub string
|
||||
Aud string
|
||||
Exp time.Time
|
||||
}
|
||||
|
||||
type TokenData struct {
|
||||
Sub string
|
||||
}
|
||||
|
||||
type TokenService interface {
|
||||
GenerateAccessRefreshTokenPair(ctx context.Context, tokenData *TokenData) (*AccessRefreshTokenPair, error)
|
||||
VerifyToken(ctx context.Context, accessToken string) (*TokenPayload, error)
|
||||
GenerateToken(ctx context.Context, input *GenerateTokenInput) (string, error)
|
||||
}
|
||||
22
pkg/jwt/provider.go
Normal file
22
pkg/jwt/provider.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"base/config"
|
||||
)
|
||||
|
||||
// NewTokenService creates a new JWT TokenService from config
|
||||
func NewTokenService(cfg *config.AppConfig) TokenService {
|
||||
secret := cfg.Server.JWTSecret
|
||||
if secret == "" {
|
||||
// Default secret if not configured (should be set in production)
|
||||
secret = "default-secret-key-change-in-production"
|
||||
}
|
||||
|
||||
// Default token expiration times
|
||||
accessTokenExpiration := 24 * time.Hour
|
||||
refreshTokenExpiration := 7 * 24 * time.Hour
|
||||
|
||||
return New(secret, accessTokenExpiration, refreshTokenExpiration)
|
||||
}
|
||||
121
pkg/jwt/token_generator.go
Normal file
121
pkg/jwt/token_generator.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTokenVerificationFailed = errors.New("token verification failed")
|
||||
)
|
||||
|
||||
type tokenService struct {
|
||||
secretKey []byte
|
||||
accessTokenExpiration time.Duration
|
||||
refreshTokenExpiration time.Duration
|
||||
}
|
||||
|
||||
func New(secret string, ate, rfe time.Duration) TokenService {
|
||||
secretKey := []byte(secret)
|
||||
return &tokenService{
|
||||
secretKey: secretKey,
|
||||
accessTokenExpiration: ate,
|
||||
refreshTokenExpiration: rfe,
|
||||
}
|
||||
}
|
||||
|
||||
func (ts tokenService) GenerateAccessRefreshTokenPair(
|
||||
ctx context.Context,
|
||||
tokenData *TokenData,
|
||||
) (*AccessRefreshTokenPair, error) {
|
||||
accessTokenExp := time.Now().Add(ts.accessTokenExpiration)
|
||||
generateAccessJwt, err := ts.generateJwt(accessTokenExp, tokenData.Sub, "alinme-web")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refreshTokenExp := time.Now().Add(ts.refreshTokenExpiration)
|
||||
generateRefreshJwt, err := ts.generateJwt(refreshTokenExp, tokenData.Sub, "alinme-web")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AccessRefreshTokenPair{
|
||||
AccessToken: generateAccessJwt,
|
||||
AccessTokenExpiresAt: accessTokenExp,
|
||||
RefreshToken: generateRefreshJwt,
|
||||
RefreshTokenExpiresAt: refreshTokenExp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ts tokenService) generateJwt(exp time.Time, sub string, aud string) (string, error) {
|
||||
t, err := jwt.NewBuilder().
|
||||
Subject(sub).
|
||||
IssuedAt(time.Now()).
|
||||
Issuer("alinme-server").
|
||||
Audience([]string{aud}).
|
||||
Expiration(exp).
|
||||
Build()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signed, err := jwt.Sign(t, jwt.WithKey(jwa.HS256(), ts.secretKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(signed), nil
|
||||
}
|
||||
|
||||
func (ts tokenService) VerifyToken(ctx context.Context, accessToken string) (*TokenPayload, error) {
|
||||
parsed, err := jwt.Parse([]byte(accessToken), jwt.WithKey(jwa.HS256(), ts.secretKey))
|
||||
if err != nil {
|
||||
return nil, ErrTokenVerificationFailed
|
||||
}
|
||||
|
||||
sub, ok := parsed.Subject()
|
||||
if !ok {
|
||||
return nil, ErrTokenVerificationFailed
|
||||
}
|
||||
|
||||
aud, ok := parsed.Audience()
|
||||
if !ok {
|
||||
return nil, ErrTokenVerificationFailed
|
||||
}
|
||||
|
||||
iat, ok := parsed.IssuedAt()
|
||||
if !ok {
|
||||
return nil, ErrTokenVerificationFailed
|
||||
}
|
||||
|
||||
exp, ok := parsed.Expiration()
|
||||
if !ok {
|
||||
return nil, ErrTokenVerificationFailed
|
||||
}
|
||||
|
||||
iss, ok := parsed.Issuer()
|
||||
if !ok {
|
||||
return nil, ErrTokenVerificationFailed
|
||||
}
|
||||
|
||||
return &TokenPayload{
|
||||
Sub: sub,
|
||||
Aud: aud,
|
||||
Iat: iat,
|
||||
Exp: exp,
|
||||
Iss: iss,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ts tokenService) GenerateToken(ctx context.Context, input *GenerateTokenInput) (string, error) {
|
||||
generateJwt, err := ts.generateJwt(time.Now().Add(time.Minute*5), input.Sub, input.Aud)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return generateJwt, nil
|
||||
}
|
||||
Reference in New Issue
Block a user