This commit is contained in:
2026-03-26 13:02:43 +00:00
parent a689f8b9da
commit eb7b519e49
64 changed files with 7081 additions and 5572 deletions

View File

@@ -0,0 +1,187 @@
package common
import (
"context"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"gorm.io/gorm"
"stream.api/internal/database/model"
"stream.api/internal/middleware"
videodomain "stream.api/internal/video"
"stream.api/pkg/cache"
"stream.api/pkg/logger"
"stream.api/pkg/storage"
"stream.api/pkg/token"
)
const (
AdTemplateUpgradeRequiredMessage = "Upgrade required to manage Ads & VAST"
DefaultGoogleUserInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo"
PlayerConfigFreePlanLimitMessage = "Free plan supports only 1 player config"
PlayerConfigFreePlanReconciliationMessage = "Delete extra player configs to continue managing player configs on the free plan"
WalletTransactionTypeTopup = "topup"
WalletTransactionTypeSubscriptionDebit = "subscription_debit"
WalletTransactionTypeReferralReward = "referral_reward"
PaymentMethodWallet = "wallet"
PaymentMethodTopup = "topup"
PaymentKindSubscription = "subscription"
PaymentKindWalletTopup = "wallet_topup"
DefaultReferralRewardBps = int32(500)
)
var AllowedTermMonths = map[int32]struct{}{
1: {},
3: {},
6: {},
12: {},
}
type RuntimeOptions struct {
DB *gorm.DB
Logger logger.Logger
Authenticator *middleware.Authenticator
TokenProvider token.Provider
Cache cache.Cache
GoogleOauth *oauth2.Config
GoogleStateTTL time.Duration
GoogleUserInfoURL string
FrontendBaseURL string
StorageProvider func() storage.Provider
VideoService func() *videodomain.Service
AgentRuntime func() videodomain.AgentRuntime
}
type Runtime struct {
db *gorm.DB
logger logger.Logger
authenticator *middleware.Authenticator
tokenProvider token.Provider
cache cache.Cache
googleOauth *oauth2.Config
googleStateTTL time.Duration
googleUserInfoURL string
frontendBaseURL string
storageProvider func() storage.Provider
videoService func() *videodomain.Service
agentRuntime func() videodomain.AgentRuntime
}
func NewRuntime(opts RuntimeOptions) *Runtime {
googleStateTTL := opts.GoogleStateTTL
if googleStateTTL <= 0 {
googleStateTTL = 10 * time.Minute
}
googleUserInfoURL := strings.TrimSpace(opts.GoogleUserInfoURL)
if googleUserInfoURL == "" {
googleUserInfoURL = DefaultGoogleUserInfoURL
}
return &Runtime{
db: opts.DB,
logger: opts.Logger,
authenticator: opts.Authenticator,
tokenProvider: opts.TokenProvider,
cache: opts.Cache,
googleOauth: opts.GoogleOauth,
googleStateTTL: googleStateTTL,
googleUserInfoURL: googleUserInfoURL,
frontendBaseURL: strings.TrimSpace(opts.FrontendBaseURL),
storageProvider: opts.StorageProvider,
videoService: opts.VideoService,
agentRuntime: opts.AgentRuntime,
}
}
func (r *Runtime) DB() *gorm.DB { return r.db }
func (r *Runtime) Logger() logger.Logger { return r.logger }
func (r *Runtime) Authenticator() *middleware.Authenticator { return r.authenticator }
func (r *Runtime) TokenProvider() token.Provider { return r.tokenProvider }
func (r *Runtime) Cache() cache.Cache { return r.cache }
func (r *Runtime) GoogleOauth() *oauth2.Config { return r.googleOauth }
func (r *Runtime) GoogleStateTTL() time.Duration { return r.googleStateTTL }
func (r *Runtime) GoogleUserInfoURL() string { return r.googleUserInfoURL }
func (r *Runtime) FrontendBaseURL() string { return r.frontendBaseURL }
func (r *Runtime) StorageProvider() storage.Provider {
if r == nil || r.storageProvider == nil {
return nil
}
return r.storageProvider()
}
func (r *Runtime) VideoService() *videodomain.Service {
if r == nil || r.videoService == nil {
return nil
}
return r.videoService()
}
func (r *Runtime) AgentRuntime() videodomain.AgentRuntime {
if r == nil || r.agentRuntime == nil {
return nil
}
return r.agentRuntime()
}
func (r *Runtime) Authenticate(ctx context.Context) (*middleware.AuthResult, error) {
if r == nil || r.authenticator == nil {
return nil, status.Error(codes.Unauthenticated, "Unauthorized")
}
return r.authenticator.Authenticate(ctx)
}
func (r *Runtime) RequireAdmin(ctx context.Context) (*middleware.AuthResult, error) {
result, err := r.Authenticate(ctx)
if err != nil {
return nil, err
}
if result.User == nil || result.User.Role == nil || strings.ToUpper(strings.TrimSpace(*result.User.Role)) != "ADMIN" {
return nil, status.Error(codes.PermissionDenied, "Admin access required")
}
return result, nil
}
func (r *Runtime) IssueSessionCookies(ctx context.Context, user *model.User) error {
if user == nil {
return status.Error(codes.Unauthenticated, "Unauthorized")
}
if r == nil || r.tokenProvider == nil || r.cache == nil {
return status.Error(codes.Internal, "Error storing session")
}
tokenPair, err := r.tokenProvider.GenerateTokenPair(user.ID, user.Email, SafeRole(user.Role))
if err != nil {
if r.logger != nil {
r.logger.Error("Token generation failed", "error", err)
}
return status.Error(codes.Internal, "Error generating tokens")
}
if err := r.cache.Set(ctx, "refresh_uuid:"+tokenPair.RefreshUUID, user.ID, time.Until(time.Unix(tokenPair.RtExpires, 0))); err != nil {
if r.logger != nil {
r.logger.Error("Session storage failed", "error", err)
}
return status.Error(codes.Internal, "Error storing session")
}
if err := grpc.SetHeader(ctx, metadata.Pairs(
"set-cookie", BuildTokenCookie("access_token", tokenPair.AccessToken, int(tokenPair.AtExpires-time.Now().Unix())),
"set-cookie", BuildTokenCookie("refresh_token", tokenPair.RefreshToken, int(tokenPair.RtExpires-time.Now().Unix())),
)); err != nil && r.logger != nil {
r.logger.Error("Failed to set gRPC auth headers", "error", err)
}
return nil
}
func BuildTokenCookie(name string, value string, maxAge int) string {
return (&http.Cookie{
Name: name,
Value: value,
Path: "/",
MaxAge: maxAge,
HttpOnly: true,
}).String()
}