Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
223 lines
6.0 KiB
Go
223 lines
6.0 KiB
Go
package authser
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"videoweb/pub/common/log"
|
|
"videoweb/pub/common/useragent"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const guestDevIDCookie = "guest_dev_id"
|
|
const guestSkipAutoCookie = "guest_skip_auto"
|
|
|
|
// EnsureGuestOnEnter 页面进入时:无 token(未自己登录)则走游客登录并写 cookie。
|
|
// 对应上游 POST /api/app/auth/guest。成功后会把会话补进当前 Request,便于同一次 SSR 读到登录态。
|
|
// 手动退出后带 guest_skip_auto 时跳过本次;清掉标记后,下次进页再自动游客。
|
|
func EnsureGuestOnEnter(c *gin.Context) *LoginResp {
|
|
if c == nil || c.Request == nil {
|
|
return nil
|
|
}
|
|
// 已有任意会话(游客或正式账号)则跳过
|
|
if useragent.TokenFromRequest(c.Request) != "" {
|
|
return nil
|
|
}
|
|
// 手动退出:本次进页不自动游客,并清除标记,供下次进页恢复
|
|
if IsGuestSkipAuto(c) {
|
|
ClearGuestSkipAuto(c)
|
|
return nil
|
|
}
|
|
|
|
devID := ensureGuestDevID(c)
|
|
env, user, err := GuestLogin(c, GuestLoginReq{
|
|
DevID: devID,
|
|
DeviceID: devID,
|
|
Channel: guestChannel(c),
|
|
})
|
|
if err != nil {
|
|
log.Error("ensure guest on enter failed", log.E(err))
|
|
return nil
|
|
}
|
|
if env == nil || env.Code != 200 || user == nil || strings.TrimSpace(user.Token) == "" {
|
|
tip := ""
|
|
if env != nil {
|
|
tip = firstNonEmptyTip(env.Tip, env.Msg)
|
|
}
|
|
log.Error("ensure guest on enter rejected", log.E(fmt.Errorf("code=%v tip=%s", envCode(env), tip)))
|
|
return nil
|
|
}
|
|
|
|
SetAuthCookies(c, user)
|
|
ClearGuestSkipAuto(c)
|
|
patchAuthCookiesToRequest(c, user)
|
|
return user
|
|
}
|
|
|
|
// RefreshGuestSession 游客会话失效时(如上游 6001 用户不存在)重新游客登录。
|
|
// 正式注册账号不处理,避免把用户踢成游客。
|
|
func RefreshGuestSession(c *gin.Context) *LoginResp {
|
|
if c == nil || c.Request == nil {
|
|
return nil
|
|
}
|
|
if IsRegisteredFromCookies(c) {
|
|
return nil
|
|
}
|
|
dropRequestCookie(c, "token")
|
|
dropRequestCookie(c, "_token_")
|
|
return EnsureGuestOnEnter(c)
|
|
}
|
|
|
|
func dropRequestCookie(c *gin.Context, name string) {
|
|
if c == nil || c.Request == nil || name == "" {
|
|
return
|
|
}
|
|
var parts []string
|
|
for _, ck := range c.Request.Cookies() {
|
|
if ck == nil || ck.Name == name {
|
|
continue
|
|
}
|
|
parts = append(parts, ck.Name+"="+ck.Value)
|
|
}
|
|
c.Request.Header.Set("Cookie", strings.Join(parts, "; "))
|
|
}
|
|
|
|
// MarkGuestSkipAuto 手动退出后标记:跳过紧接着的一次自动游客登录。
|
|
func MarkGuestSkipAuto(c *gin.Context) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
secure := c.Request != nil && c.Request.TLS != nil
|
|
http.SetCookie(c.Writer, &http.Cookie{
|
|
Name: guestSkipAutoCookie,
|
|
Value: "1",
|
|
Path: "/",
|
|
MaxAge: 0, // session cookie
|
|
HttpOnly: false,
|
|
Secure: secure,
|
|
SameSite: http.SameSiteLaxMode,
|
|
})
|
|
}
|
|
|
|
// IsGuestSkipAuto 是否处于「手动退出后跳过自动游客」状态。
|
|
func IsGuestSkipAuto(c *gin.Context) bool {
|
|
return cookieValue(c, guestSkipAutoCookie) == "1"
|
|
}
|
|
|
|
// ClearGuestSkipAuto 清除手动退出跳过标记。
|
|
func ClearGuestSkipAuto(c *gin.Context) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
http.SetCookie(c.Writer, &http.Cookie{
|
|
Name: guestSkipAutoCookie,
|
|
Value: "",
|
|
Path: "/",
|
|
MaxAge: -1,
|
|
HttpOnly: false,
|
|
SameSite: http.SameSiteLaxMode,
|
|
})
|
|
}
|
|
|
|
func ensureGuestDevID(c *gin.Context) string {
|
|
if id := cookieValue(c, guestDevIDCookie); id != "" {
|
|
return id
|
|
}
|
|
id := newGuestDevID()
|
|
secure := c.Request != nil && c.Request.TLS != nil
|
|
setCookie(c, guestDevIDCookie, id, secure)
|
|
patchRequestCookie(c, guestDevIDCookie, id)
|
|
return id
|
|
}
|
|
|
|
func newGuestDevID() string {
|
|
b := make([]byte, 8)
|
|
if _, err := rand.Read(b); err != nil {
|
|
now := time.Now()
|
|
return fmt.Sprintf("%016X%d", uint64(now.UnixNano()), now.UnixMilli())
|
|
}
|
|
return strings.ToUpper(hex.EncodeToString(b)) + strconv.FormatInt(time.Now().UnixMilli(), 10)
|
|
}
|
|
|
|
// guestChannel 为首屏服务端自动游客登录补渠道;cookie 用于复用前端首次归因结果。
|
|
func guestChannel(c *gin.Context) string {
|
|
if c == nil || c.Request == nil {
|
|
return ""
|
|
}
|
|
if channel := cookieValue(c, "channel"); channel != "" {
|
|
return channel
|
|
}
|
|
for _, key := range []string{"dc", "ch", "pc"} {
|
|
if channel := strings.TrimSpace(c.Query(key)); channel != "" {
|
|
secure := c.Request.TLS != nil
|
|
setCookie(c, "channel", channel, secure)
|
|
patchRequestCookie(c, "channel", channel)
|
|
return channel
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func patchAuthCookiesToRequest(c *gin.Context, user *LoginResp) {
|
|
if c == nil || user == nil {
|
|
return
|
|
}
|
|
patchRequestCookie(c, "token", user.Token)
|
|
patchRequestCookie(c, "userId", strconv.FormatInt(user.UserID, 10))
|
|
patchRequestCookie(c, "accountNo", user.AccountNo)
|
|
patchRequestCookie(c, "nickName", user.NickName)
|
|
patchRequestCookie(c, "introduction", user.Introduction)
|
|
patchRequestCookie(c, "avatar", user.Avatar)
|
|
patchRequestCookie(c, "email", user.Email)
|
|
patchRequestCookie(c, "inviteCode", user.InviteCode)
|
|
patchRequestCookie(c, "recharge", strconv.FormatInt(user.Recharge, 10))
|
|
patchRequestCookie(c, "vipType", strconv.Itoa(user.VipType))
|
|
patchRequestCookie(c, "currentVipExpire", user.CurrentVipExpire)
|
|
patchRequestCookie(c, "showCardId", strconv.FormatInt(user.ShowCardID, 10))
|
|
patchRequestCookie(c, "showCardName", user.ShowCardName)
|
|
patchRequestCookie(c, "userType", strconv.Itoa(user.Type))
|
|
isReg := "0"
|
|
if user.IsRegister || user.Type == 1 {
|
|
isReg = "1"
|
|
}
|
|
patchRequestCookie(c, "isRegister", isReg)
|
|
}
|
|
|
|
func patchRequestCookie(c *gin.Context, name, value string) {
|
|
if c == nil || c.Request == nil || name == "" {
|
|
return
|
|
}
|
|
escaped := url.PathEscape(value)
|
|
var parts []string
|
|
for _, ck := range c.Request.Cookies() {
|
|
if ck == nil || ck.Name == name {
|
|
continue
|
|
}
|
|
parts = append(parts, ck.Name+"="+ck.Value)
|
|
}
|
|
parts = append(parts, name+"="+escaped)
|
|
c.Request.Header.Set("Cookie", strings.Join(parts, "; "))
|
|
}
|
|
|
|
func envCode(env *APIEnvelope) int {
|
|
if env == nil {
|
|
return 0
|
|
}
|
|
return env.Code
|
|
}
|
|
|
|
func firstNonEmptyTip(values ...string) string {
|
|
for _, v := range values {
|
|
if strings.TrimSpace(v) != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|