Browse Source

#1494

create qrcode4bind
tags/v1.22.2.2^2
Gitea 3 years ago
parent
commit
d72bf77a33
10 changed files with 318 additions and 2 deletions
  1. +44
    -0
      modules/auth/wechat/access_token.go
  2. +95
    -0
      modules/auth/wechat/client.go
  3. +1
    -0
      modules/auth/wechat/event.go
  4. +6
    -0
      modules/auth/wechat/qr_code.go
  5. +54
    -0
      modules/redis/redis_client/client.go
  6. +16
    -0
      modules/redis/redis_key/key_base.go
  7. +11
    -0
      modules/redis/redis_key/wechat_redis_key.go
  8. +16
    -2
      modules/setting/setting.go
  9. +67
    -0
      routers/authentication/wechat.go
  10. +8
    -0
      routers/routes/routes.go

+ 44
- 0
modules/auth/wechat/access_token.go View File

@@ -0,0 +1,44 @@
package wechat

import (
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"time"
)

const EMPTY_REDIS_VAL = "Nil"

func GetWechatAccessToken() string {
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" {
if token == EMPTY_REDIS_VAL {
return ""
}
live, _ := redis_client.TTL(token)
//refresh wechat access token when expire time less than 5 minutes
if live > 0 && live < 300 {
refreshAccessTokenCache()
}
return token
}
return refreshAccessTokenCache()
}

func refreshAccessTokenCache() string {
r := callAccessToken()

var token string
if r != nil {
token = r.Access_token
}

if token == "" {
redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second)
return ""
}

redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second)

return token

}

+ 95
- 0
modules/auth/wechat/client.go View File

@@ -0,0 +1,95 @@
package wechat

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"encoding/json"
"github.com/go-resty/resty/v2"
"time"
)

var (
client *resty.Client
)

const (
GRANT_TYPE = "client_credential"
ACCESS_TOKEN_PATH = "/cgi-bin/token"
QR_CODE_Path = "/cgi-bin/qrcode/create"
ACTION_QR_STR_SCENE = "QR_STR_SCENE"
)

type AccessTokenResponse struct {
Access_token string
Expires_in int
}

type QRCodeResponse struct {
Ticket string `json:"ticket"`
Expire_Seconds int `json:"expire_seconds"`
Url string `json:"url"`
}

type QRCodeRequest struct {
Action_name string `json:"action_name"`
Action_info ActionInfo `json:"action_info"`
Expire_seconds int `json:"expire_seconds"`
}

type ActionInfo struct {
Scene Scene
}

type Scene struct {
Scene_str string
}

func getWechatRestyClient() *resty.Client {
if client == nil {
client = resty.New()
client.SetTimeout(time.Duration(setting.WechatApiTimeoutSeconds) * time.Second)
}
return client
}

func callAccessToken() *AccessTokenResponse {
client := getWechatRestyClient()

var result AccessTokenResponse
_, err := client.R().
SetQueryParam("grant_type", GRANT_TYPE).
SetQueryParam("appid", setting.WechatAppId).
SetQueryParam("secret", setting.WechatAppSecret).
SetResult(&result).
Get(setting.WechatApiHost + ACCESS_TOKEN_PATH)
if err != nil {
log.Error("get wechat access token failed,e=%v", err)
return nil
}
return &result
}

func callQRCodeCreate(sceneStr string) *QRCodeResponse {
client := getWechatRestyClient()

body := &QRCodeRequest{
Action_name: ACTION_QR_STR_SCENE,
Action_info: ActionInfo{Scene: Scene{Scene_str: sceneStr}},
Expire_seconds: setting.WechatQRCodeExpireSeconds,
}
bodyJson, _ := json.Marshal(body)
var result QRCodeResponse
r, err := client.R().
SetHeader("Content-Type", "application/json").
SetQueryParam("access_token", GetWechatAccessToken()).
SetBody(bodyJson).
SetResult(&result).
Post(setting.WechatApiHost + QR_CODE_Path)
if err != nil || result.Url == "" {
log.Error("create QR code failed,e=%v", err)
return nil
}
//todo 识别token失效的错误码,重试机制
log.Info("%v", r)
return &result
}

+ 1
- 0
modules/auth/wechat/event.go View File

@@ -0,0 +1 @@
package wechat

+ 6
- 0
modules/auth/wechat/qr_code.go View File

@@ -0,0 +1,6 @@
package wechat

func GetWechatQRCode4Bind(sceneStr string) *QRCodeResponse {
r := callQRCodeCreate(sceneStr)
return r
}

+ 54
- 0
modules/redis/redis_client/client.go View File

@@ -0,0 +1,54 @@
package redis_client

import (
"code.gitea.io/gitea/modules/labelmsg"
"fmt"
"math"
"strconv"
"time"
)

//todo redis连接池
func Setex(key, value string, timeout time.Duration) (bool, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

seconds := int(math.Floor(timeout.Seconds()))
reply, err := redisClient.Do("SETEX", key, seconds, value)
if err != nil {
return false, err
}
if reply != "OK" {
return false, nil
}
return true, nil

}

func Get(key string) (string, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("GET", key)
if err != nil {
return "", err
}
if reply == nil {
return "", err
}
return fmt.Sprint(reply), nil

}

func TTL(key string) (int, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("TTL", key)
if err != nil {
return 0, err
}
n, _ := strconv.Atoi(fmt.Sprint(reply))
return n, nil

}

+ 16
- 0
modules/redis/redis_key/key_base.go View File

@@ -0,0 +1,16 @@
package redis_key

import "strings"

const KEY_SEPARATE = ":"

func KeyJoin(keys ...string) string {
var build strings.Builder
for _, v := range keys {
build.WriteString(v)
build.WriteString(KEY_SEPARATE)
}
s := build.String()
s = strings.TrimSuffix(s, KEY_SEPARATE)
return s
}

+ 11
- 0
modules/redis/redis_key/wechat_redis_key.go View File

@@ -0,0 +1,11 @@
package redis_key

const PREFIX = "wechat"

func WechatBindingUserIdKey(sceneStr string) string {
return KeyJoin(PREFIX, sceneStr, "scene_userId")
}

func WechatAccessTokenKey() string {
return KeyJoin(PREFIX, "access_token")
}

+ 16
- 2
modules/setting/setting.go View File

@@ -470,7 +470,7 @@ var (
BenchmarkTypes string
BenchmarkGpuTypes string
BenchmarkResourceSpecs string
BenchmarkMaxDuration int64
BenchmarkMaxDuration int64

//snn4imagenet config
IsSnn4imagenetEnabled bool
@@ -512,7 +512,7 @@ var (
ProfileID string
PoolInfos string
Flavor string
DebugHost string
DebugHost string
//train-job
ResourcePools string
Engines string
@@ -529,6 +529,13 @@ var (
ElkTimeFormat string
PROJECT_LIMIT_PAGES []string

//wechat config
WechatApiHost string
WechatApiTimeoutSeconds int
WechatAppId string
WechatAppSecret string
WechatQRCodeExpireSeconds int

//nginx proxy
PROXYURL string
RadarMap = struct {
@@ -1342,6 +1349,13 @@ func NewContext() {
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time")
PROJECT_LIMIT_PAGES = strings.Split(sec.Key("project_limit_pages").MustString(""), ",")

sec = Cfg.Section("wechat")
WechatApiHost = sec.Key("HOST").MustString("https://api.weixin.qq.com")
WechatApiTimeoutSeconds = sec.Key("TIMEOUT_SECONDS").MustInt(3)
WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d")
WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198")
WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120)

SetRadarMapConfig()

sec = Cfg.Section("warn_mail")


+ 67
- 0
routers/authentication/wechat.go View File

@@ -0,0 +1,67 @@
package authentication

import (
"code.gitea.io/gitea/modules/auth/wechat"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/setting"
"errors"
"fmt"
gouuid "github.com/satori/go.uuid"
"time"
)

type QRCodeResponse struct {
Url string
Ticket string
SceneStr string
ExpireSeconds int
}

// GetQRCode4Bind get QR code for wechat binding
func GetQRCode4Bind(ctx *context.Context) {
userId := ctx.User.ID

r, err := createQRCode4Bind(userId)
if err != nil {
ctx.JSON(200, map[string]interface{}{
"code": 9,
"msg": "Get QR code failed",
})
return
}

ctx.JSON(200, map[string]interface{}{
"code": 0,
"msg": "success",
"data": r,
})
}

func createQRCode4Bind(userId int64) (*QRCodeResponse, error) {
sceneStr := gouuid.NewV4().String()
r := wechat.GetWechatQRCode4Bind(sceneStr)
if r == nil {
return nil, errors.New("createQRCode4Bind failed")
}

isOk, err := redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), fmt.Sprint(userId), time.Duration(setting.WechatQRCodeExpireSeconds)*time.Second)
if err != nil {
log.Error("createQRCode4Bind failed.e=%v", err)
return nil, err
}
if !isOk {
log.Error("createQRCode4Bind failed.redis reply is not ok")
return nil, errors.New("reply is not ok when set WechatBindingUserIdKey")
}

result := &QRCodeResponse{
Url: r.Url,
Ticket: r.Ticket,
SceneStr: sceneStr,
ExpireSeconds: setting.WechatQRCodeExpireSeconds,
}
return result, nil
}

+ 8
- 0
routers/routes/routes.go View File

@@ -6,6 +6,7 @@ package routes

import (
"bytes"
"code.gitea.io/gitea/routers/authentication"
"encoding/gob"
"net/http"
"path"
@@ -391,6 +392,13 @@ func RegisterRoutes(m *macaron.Macaron) {
}, ignSignInAndCsrf, reqSignIn)
m.Post("/login/oauth/access_token", bindIgnErr(auth.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth)

m.Group("/authentication/wechat", func() {
m.Get("/qrCode4Bind", authentication.GetQRCode4Bind)
m.Post("/grant", bindIgnErr(auth.GrantApplicationForm{}), user.GrantApplicationOAuth)
// TODO manage redirection
m.Post("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth)
}, reqSignIn)

m.Group("/user/settings", func() {
m.Get("", userSetting.Profile)
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost)


Loading…
Cancel
Save