Skip to content

Commit

Permalink
feat: add jwt login
Browse files Browse the repository at this point in the history
Signed-off-by: ShuangxueWu <qingchoulove@hotmail.com>
  • Loading branch information
qingchoulove committed Mar 22, 2024
1 parent b3bcd88 commit eeb5471
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 3 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
module github.com/openapp-dev/openapp

go 1.20
go 1.21

toolchain go1.22.1

require (
github.com/ghodss/yaml v1.0.0
github.com/gin-contrib/cors v1.5.0
github.com/gin-gonic/gin v1.9.1
github.com/go-git/go-git/v5 v5.11.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
k8s.io/api v0.29.2
Expand Down
38 changes: 38 additions & 0 deletions go.sum

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pkg/apiserver/handler/config_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ func UpdateConfigHandler(ctx *gin.Context) {
openappHelper, err := getOpenAPPHelper(ctx)
if err != nil {
klog.Errorf("Failed to get openapp lister: %v", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get openapp lister"})
returnFormattedData(ctx, http.StatusInternalServerError, "Failed to get openapp lister", nil)
return
}
systemCfg, err := openappHelper.ConfigMapLister.ConfigMaps(utils.SystemNamespace).Get(utils.SystemConfigMap)
if err != nil {
klog.Errorf("Failed to get config: %v", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get config"})
returnFormattedData(ctx, http.StatusInternalServerError, "Failed to get config", nil)
return
}
updatedCfg := systemCfg.DeepCopy()
Expand Down
60 changes: 60 additions & 0 deletions pkg/apiserver/handler/login_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package handler

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/openapp-dev/openapp/pkg/utils"
"k8s.io/klog"
)

type loginResponse struct {
Token string `json:"token"`
}

func LoginHandler(ctx *gin.Context) {
klog.V(4).Info("Start to login...")
openappHelper, err := getOpenAPPHelper(ctx)
if err != nil {
klog.Errorf("Failed to get openapp lister: %v", err)
returnFormattedData(ctx, http.StatusInternalServerError, "Failed to get openapp lister", nil)
return
}
json := make(map[string]interface{})
err = ctx.BindJSON(&json)
if err != nil {
klog.Errorf("Failed to bind json: %v", err)
returnFormattedData(ctx, http.StatusBadRequest, "Failed to bind json", nil)
return
}
username, ok := json["username"].(string)
if !ok {
klog.Errorf("Failed to get username from json")
returnFormattedData(ctx, http.StatusBadRequest, "Failed to get username from json", nil)
return
}
password, ok := json["password"].(string)
if !ok {
klog.Errorf("Failed to get password from json")
returnFormattedData(ctx, http.StatusBadRequest, "Failed to get password from json", nil)
return
}
cfg, err := openappHelper.ConfigMapLister.ConfigMaps(utils.SystemNamespace).Get(utils.SystemConfigMap)
if err != nil {
klog.Errorf("Failed to get config: %v", err)
returnFormattedData(ctx, http.StatusInternalServerError, "Failed to get config", nil)
return
}
if cfg.Data["username"] != username || cfg.Data["password"] != password {
klog.Errorf("Failed to login")
returnFormattedData(ctx, http.StatusUnauthorized, "Failed to login", nil)
return
}
token, err := utils.NewJWT([]byte(cfg.Data["password"])).GenerateToken(username, password)
if err != nil {
klog.Errorf("Failed to generate token: %v", err)
returnFormattedData(ctx, http.StatusInternalServerError, "Failed to generate token", nil)
return
}
returnFormattedData(ctx, http.StatusOK, "Login successfully", &loginResponse{Token: token})
}
43 changes: 43 additions & 0 deletions pkg/apiserver/router/router.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package router

import (
"net/http"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -32,10 +34,18 @@ func NewOpenAPPServerRouter(k8sClient kubernetes.Interface,
router.Use(corsHandler)
router.Use(NewGinContextWithClientLister(k8sClient, openappClient, openappHelper))

// middleware
cfg, err := openappHelper.ConfigMapLister.ConfigMaps(utils.SystemNamespace).Get(utils.SystemConfigMap)
if err != nil {
panic(err)
}
router.Use(jwtAuth([]byte(cfg.Data["password"])))

initAPPRouter(router, corsHandler)
initPublicServiceRouter(router, corsHandler)
initConfigRouter(router, corsHandler)
initVersionRouter(router, corsHandler)
initLoginRouter(router, corsHandler)

return router
}
Expand Down Expand Up @@ -78,3 +88,36 @@ func initVersionRouter(router *gin.Engine, corsHandler gin.HandlerFunc) {
versionGroup.GET("", handler.GetOpenAPPVersionHandler)
versionGroup.Use(corsHandler)
}

func initLoginRouter(router *gin.Engine, corsHandler gin.HandlerFunc) {
loginGroup := router.Group("/login")
loginGroup.POST("", handler.LoginHandler)
loginGroup.Use(corsHandler)
}

func jwtAuth(secret []byte) gin.HandlerFunc {
return func(ctx *gin.Context) {
token := ctx.GetHeader("Authorization")
if token == "" {
ctx.JSON(http.StatusUnauthorized, gin.H{
"code": http.StatusUnauthorized,
"message": "Authorization token is required",
})
ctx.Abort()
return
}

jwt := utils.NewJWT(secret)
_, err := jwt.ParseToken(token)
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{
"code": http.StatusUnauthorized,
"message": err.Error(),
})
ctx.Abort()
return
}

ctx.Next()
}
}
50 changes: 50 additions & 0 deletions pkg/utils/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package utils

import (
"time"

jwt "github.com/golang-jwt/jwt/v5"
)

type Claims struct {
Username string `json:"username"`
Password string `json:"password"`
jwt.RegisteredClaims
}

type JWT struct {
secret []byte
}

func NewJWT(secret []byte) *JWT {
return &JWT{secret: secret}
}

func (j *JWT) GenerateToken(username, password string) (string, error) {
claims := Claims{
Username: username,
Password: password,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "openapp",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.secret)
}

func (j *JWT) ParseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return j.secret, nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if ok && token.Valid {
return claims, nil
}
return nil, err
}

0 comments on commit eeb5471

Please sign in to comment.