Files
copilot-app/internal/middleware/auth.go
2025-08-13 19:03:20 +08:00

207 lines
5.2 KiB
Go

package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"ripper/internal/app/github_auth"
"ripper/internal/response"
jwtpkg "ripper/pkg/jwt"
"strconv"
"strings"
"time"
)
type OAuthCheck struct {
ClientId string `json:"client_id" form:"client_id"`
DeviceCode string `json:"device_code" form:"device_code"`
GrantType string `json:"grant_type" form:"grant_type"`
}
func DeviceCodeCheckAuth(ctx *gin.Context) {
checkInfo := &OAuthCheck{}
if err := ctx.ShouldBind(&checkInfo); err != nil {
response.FailJson(ctx, response.FailStruct{
Code: -1,
Msg: "Invalid client id.",
}, false)
ctx.Abort()
return
}
info, _ := github_auth.GetClientAuthInfoByDeviceCode(checkInfo.DeviceCode)
if info.CardCode == "" {
ctx.JSON(http.StatusOK, gin.H{
"error": "authorization_pending",
"error_description": "The authorization request is still pending.",
"error_uri": "https://docs.github.com/developers/apps/authorizing-oauth-apps#error-codes-for-the-device-flow",
})
ctx.Abort()
return
}
ctx.Set("client_auth_info", info)
ctx.Next()
}
func AuthCodeFlowCheckAuth(ctx *gin.Context) {
checkInfoClient := &github_auth.ClientOAuthInfo{}
err := ctx.Bind(&checkInfoClient)
if err != nil {
response.FailJson(ctx, response.FailStruct{
Code: -1,
Msg: "Invalid client id.",
}, false)
ctx.Abort()
return
}
oauthCodeInfo, err := github_auth.GetOAuthCodeInfoByClientIdAndCode(checkInfoClient.ClientId, checkInfoClient.Code)
if err != nil {
response.FailJson(ctx, response.FailStruct{
Code: -1,
Msg: "Invalid client id.",
}, false)
ctx.Abort()
return
}
ctx.Set("client_auth_info", oauthCodeInfo)
ctx.Next()
}
func AccessTokenCheckAuth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Request.Header.Get("Authorization")
if token == "" {
response.FailJsonAndStatusCode(c, http.StatusForbidden, response.NoAccess, false)
c.Abort()
return
}
last := strings.Index(token, " ")
if len(token) < last || last == -1 {
response.FailJsonAndStatusCode(c, http.StatusForbidden, response.TokenWrongful, false)
c.Abort()
return
}
token = token[last+1:]
chk, jwter, err := jwtpkg.CheckToken(token, &UserLoad{}, "user")
if err != nil {
errmsg := response.TokenWrongful
errmsg.Msg = "令牌验证错误"
response.FailJsonAndStatusCode(c, http.StatusForbidden, errmsg, true, err.Error())
c.Abort()
return
}
if !chk {
response.FailJsonAndStatusCode(c, http.StatusForbidden, response.NoAccess, true, "破损令牌")
c.Abort()
return
}
chs := true
issuerStr := ""
issuerStr, err = jwter.GetIssuer()
if err != nil {
chs = false
c.Abort()
return
}
if "user" != issuerStr && issuerStr != "" {
chs = false
c.Abort()
return
}
if !chs {
errmsg := response.TokenWrongful
errmsg.Msg = "签名错误"
response.FailJsonAndStatusCode(c, http.StatusForbidden, errmsg, true, err.Error())
c.Abort()
return
}
c.Set("token", jwter)
c.Set("tokenStr", token)
c.Set("token.issuer", issuerStr)
c.Next()
}
}
func TokenCheckAuth() gin.HandlerFunc {
return func(c *gin.Context) {
clientType := os.Getenv("COPILOT_CLIENT_TYPE")
copilotProxyAll, err := strconv.ParseBool(os.Getenv("COPILOT_PROXY_ALL"))
if clientType == "github" && !copilotProxyAll {
c.Next()
return
}
token := c.Request.Header.Get("Authorization")
if token == "" {
response.FailJsonAndStatusCode(c, http.StatusUnauthorized, response.TokenWrongful, false)
c.Abort()
return
}
last := strings.Index(token, " ")
if len(token) < last || last == -1 {
response.FailJsonAndStatusCode(c, http.StatusUnauthorized, response.TokenWrongful, false)
c.Abort()
return
}
token = token[last+1:]
parsedToken := parseAuthorizationToken(token)
// 校验exp是否过期
expired, err := isExpired(parsedToken["exp"])
if err != nil {
response.FailJsonAndStatusCode(c, http.StatusUnauthorized, response.TokenWrongful, false)
c.Abort()
return
} else {
if expired {
response.FailJsonAndStatusCode(c, http.StatusUnauthorized, response.TokenOverdue, false)
c.Abort()
return
}
}
rawToken := github_auth.JsonMap2Token(map[string]interface{}{
"tid": parsedToken["tid"],
"exp": parsedToken["exp"],
"sku": parsedToken["sku"],
"st": parsedToken["st"],
"chat": parsedToken["chat"],
"u": parsedToken["u"],
})
sign := "1:" + github_auth.Token2Sign(rawToken)
if sign != parsedToken["8kp"] {
response.FailJsonAndStatusCode(c, http.StatusUnauthorized, response.TokenWrongful, false)
c.Abort()
return
}
c.Next()
}
}
func parseAuthorizationToken(token string) map[string]string {
result := make(map[string]string)
pairs := strings.Split(token, ";")
for _, pair := range pairs {
kv := strings.SplitN(pair, "=", 2)
if len(kv) == 2 {
key := kv[0]
value := kv[1]
if key == "tid" || key == "exp" || key == "sku" || key == "st" || key == "8kp" || key == "chat" || key == "u" {
result[key] = value
}
}
}
return result
}
func isExpired(expStr string) (bool, error) {
exp, err := strconv.ParseInt(expStr, 10, 64)
if err != nil {
return false, fmt.Errorf("invalid exp timestamp: %v", err)
}
now := time.Now().Unix()
return now > exp, nil
}