package searchaccessser import ( "errors" "fmt" "time" "91porn-server/app/appg" "91porn-server/common/crypt" ) const ( tokenScope = "video_search_access" tokenTTL = 10 * time.Minute ) // Issue 为搜索结果签发短时效的视频访问凭证。 func Issue(uid uint64, videoID string, now time.Time) (string, error) { return issue(appg.Conf.Base.JwtKey, uid, videoID, now) } // Validate 校验搜索结果访问凭证,凭证与用户和视频一一绑定。 func Validate(token string, uid uint64, videoID string, now time.Time) bool { return validate(appg.Conf.Base.JwtKey, token, uid, videoID, now) } func issue(secret string, uid uint64, videoID string, now time.Time) (string, error) { if videoID == "" { return "", errors.New("video id is empty") } return crypt.CreateToken(searchTokenSecret(secret), map[string]interface{}{ "scope": tokenScope, "uid": uid, "vid": videoID, "iat": now.Unix(), "exp": now.Add(tokenTTL).Unix(), }) } func validate(secret, token string, uid uint64, videoID string, now time.Time) bool { claims, err := crypt.ParseToken(searchTokenSecret(secret), token) if err != nil { return false } scope, _ := claims["scope"].(string) vid, _ := claims["vid"].(string) tokenUID, uidOK := numberClaim(claims["uid"]) expiresAt, expOK := numberClaim(claims["exp"]) return scope == tokenScope && vid == videoID && uidOK && tokenUID == int64(uid) && expOK && expiresAt >= now.Unix() } func searchTokenSecret(secret string) string { if secret == "" { return "" } return fmt.Sprintf("%s:%s", secret, tokenScope) } func numberClaim(value interface{}) (int64, bool) { switch number := value.(type) { case float64: return int64(number), true case int64: return number, true case int: return int64(number), true default: return 0, false } }