summaryrefslogtreecommitdiff
path: root/internal/api/auth
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2025-04-08 14:37:33 +0200
committerSanto Cariotti <santo@dcariotti.me>2025-04-08 14:39:13 +0200
commit1f0d9ec8452f15c27cd33c4e3874454c35993743 (patch)
treec453a31ae5eb823aaf48868eea9fc4daf65f108b /internal/api/auth
parentc5b10e28b358308d8349b940af09f64368172f2e (diff)
Use internal/pkg structure
Diffstat (limited to 'internal/api/auth')
-rw-r--r--internal/api/auth/auth.go57
-rw-r--r--internal/api/auth/auth_test.go74
2 files changed, 131 insertions, 0 deletions
diff --git a/internal/api/auth/auth.go b/internal/api/auth/auth.go
new file mode 100644
index 0000000..b382beb
--- /dev/null
+++ b/internal/api/auth/auth.go
@@ -0,0 +1,57 @@
+package auth
+
+import (
+ "errors"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+var jwtKey = []byte(os.Getenv("JWT_SECRET"))
+
+type Claims struct {
+ UserID int `json:"user_id"`
+ jwt.RegisteredClaims
+}
+
+func GenerateJWT(userID int) (string, error) {
+ expirationTime := time.Now().Add(5 * time.Hour)
+ claims := &Claims{
+ UserID: userID,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(expirationTime),
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString(jwtKey)
+ if err != nil {
+ return "", err
+ }
+ return tokenString, nil
+}
+
+func ValidateJWT(tokenString string) (*Claims, error) {
+ claims := &Claims{}
+ // A token has a form `Bearer ...`
+ tokenParts := strings.Split(tokenString, " ")
+ if len(tokenParts) != 2 {
+ return nil, errors.New("not valid JWT")
+ }
+
+ token, err := jwt.ParseWithClaims(tokenParts[1], claims, func(token *jwt.Token) (interface{}, error) {
+ return jwtKey, nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ if !token.Valid {
+ return nil, err
+ }
+
+ return claims, nil
+}
diff --git a/internal/api/auth/auth_test.go b/internal/api/auth/auth_test.go
new file mode 100644
index 0000000..50b6c9b
--- /dev/null
+++ b/internal/api/auth/auth_test.go
@@ -0,0 +1,74 @@
+package auth
+
+import (
+ "os"
+ "testing"
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGenerateAndValidateJWT(t *testing.T) {
+ // Set up the JWT secret for the test.
+ os.Setenv("JWT_SECRET", "testsecret")
+ jwtKey = []byte(os.Getenv("JWT_SECRET"))
+
+ userID := 123
+ tokenString, err := GenerateJWT(userID)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, tokenString)
+
+ claims, err := ValidateJWT(tokenString)
+ assert.NoError(t, err)
+ assert.NotNil(t, claims)
+ assert.Equal(t, userID, claims.UserID)
+ assert.True(t, claims.ExpiresAt.After(time.Now()))
+}
+
+func TestValidateJWT_InvalidToken(t *testing.T) {
+ os.Setenv("JWT_SECRET", "testsecret")
+ jwtKey = []byte(os.Getenv("JWT_SECRET"))
+
+ _, err := ValidateJWT("invalid_token")
+ assert.Error(t, err)
+}
+
+func TestValidateJWT_ExpiredToken(t *testing.T) {
+ os.Setenv("JWT_SECRET", "testsecret")
+ jwtKey = []byte(os.Getenv("JWT_SECRET"))
+
+ // Create a token that has already expired.
+ claims := &Claims{
+ UserID: 123,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Hour)),
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString(jwtKey)
+ assert.NoError(t, err)
+
+ _, err = ValidateJWT(tokenString)
+ assert.Error(t, err)
+}
+
+func TestValidateJWT_WrongSecret(t *testing.T) {
+ os.Setenv("JWT_SECRET", "testsecret")
+ jwtKey = []byte(os.Getenv("JWT_SECRET"))
+
+ userID := 123
+ tokenString, err := GenerateJWT(userID)
+ assert.NoError(t, err)
+
+ // Set a different secret for validation.
+ wrongKey := []byte("wrongsecret")
+
+ claims := &Claims{}
+ _, err = jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+ return wrongKey, nil
+ })
+
+ assert.Error(t, err)
+}