mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
feat/login crud in users sync with sso
This commit is contained in:
+154
-22
@@ -2,36 +2,64 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type SSOClientConfig struct {
|
||||
PublicID string `json:"public_id"`
|
||||
RedirectURI string `json:"redirect_uri"`
|
||||
Scope string `json:"scope"`
|
||||
Prompt string `json:"prompt"`
|
||||
DefaultReturnURI string `json:"default_return_uri"`
|
||||
AllowedReturnOrigins []string `json:"allowed_return_origins"`
|
||||
SyncSecret string `json:"sync_secret"`
|
||||
}
|
||||
|
||||
var (
|
||||
IsProd bool
|
||||
AppHost string
|
||||
Version string
|
||||
LogLevel string
|
||||
AppPort int
|
||||
DBHost string
|
||||
DBUser string
|
||||
DBPassword string
|
||||
DBName string
|
||||
DBPort int
|
||||
JWTSecret string
|
||||
JWTAccessExp int
|
||||
JWTRefreshExp int
|
||||
JWTResetPasswordExp int
|
||||
JWTVerifyEmailExp int
|
||||
RedisURL string
|
||||
CORSAllowOrigins []string
|
||||
CORSAllowMethods []string
|
||||
CORSAllowHeaders []string
|
||||
CORSExposeHeaders []string
|
||||
CORSAllowCredentials bool
|
||||
CORSMaxAge int
|
||||
IsProd bool
|
||||
AppHost string
|
||||
Version string
|
||||
LogLevel string
|
||||
AppPort int
|
||||
DBHost string
|
||||
DBUser string
|
||||
DBPassword string
|
||||
DBName string
|
||||
DBPort int
|
||||
JWTSecret string
|
||||
JWTAccessExp int
|
||||
JWTRefreshExp int
|
||||
JWTResetPasswordExp int
|
||||
JWTVerifyEmailExp int
|
||||
RedisURL string
|
||||
CORSAllowOrigins []string
|
||||
CORSAllowMethods []string
|
||||
CORSAllowHeaders []string
|
||||
CORSExposeHeaders []string
|
||||
CORSAllowCredentials bool
|
||||
CORSMaxAge int
|
||||
SSOIssuer string
|
||||
SSOJWKSURL string
|
||||
SSOAllowedAudiences []string
|
||||
SSOAuthorizeURL string
|
||||
SSOTokenURL string
|
||||
SSOGetMeURL string
|
||||
SSOClients map[string]SSOClientConfig
|
||||
SSOAccessCookieName string
|
||||
SSORefreshCookieName string
|
||||
SSOCookieDomain string
|
||||
SSOCookieSecure bool
|
||||
SSOCookieSameSite string
|
||||
SSOPKCETTL time.Duration
|
||||
SSOUserSyncDrift time.Duration
|
||||
SSOUserSyncNonceTTL time.Duration
|
||||
SSOUserSyncMaxBodyBytes int
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -68,6 +96,43 @@ func init() {
|
||||
|
||||
// Redis
|
||||
RedisURL = viper.GetString("REDIS_URL")
|
||||
|
||||
// SSO integration
|
||||
SSOIssuer = viper.GetString("SSO_ISSUER")
|
||||
SSOJWKSURL = viper.GetString("SSO_JWKS_URL")
|
||||
SSOAllowedAudiences = parseList("SSO_ALLOWED_AUDIENCES")
|
||||
SSOAuthorizeURL = viper.GetString("SSO_AUTHORIZE_URL")
|
||||
SSOTokenURL = viper.GetString("SSO_TOKEN_URL")
|
||||
SSOGetMeURL = viper.GetString("SSO_GETME_URL")
|
||||
SSOAccessCookieName = defaultString(viper.GetString("SSO_ACCESS_COOKIE_NAME"), "sso_access")
|
||||
SSORefreshCookieName = defaultString(viper.GetString("SSO_REFRESH_COOKIE_NAME"), "sso_refresh")
|
||||
SSOCookieDomain = viper.GetString("SSO_COOKIE_DOMAIN")
|
||||
SSOCookieSecure = viper.GetBool("SSO_COOKIE_SECURE")
|
||||
SSOCookieSameSite = defaultString(viper.GetString("SSO_COOKIE_SAMESITE"), "Lax")
|
||||
if ttl := viper.GetInt("SSO_PKCE_TTL_SECONDS"); ttl > 0 {
|
||||
SSOPKCETTL = time.Duration(ttl) * time.Second
|
||||
} else {
|
||||
SSOPKCETTL = 5 * time.Minute
|
||||
}
|
||||
SSOClients = loadSSOClients("SSO_CLIENTS")
|
||||
if drift := viper.GetInt("SSO_USER_SYNC_SIGNATURE_DRIFT_SECONDS"); drift > 0 {
|
||||
SSOUserSyncDrift = time.Duration(drift) * time.Second
|
||||
} else {
|
||||
SSOUserSyncDrift = 2 * time.Minute
|
||||
}
|
||||
if ttl := viper.GetInt("SSO_USER_SYNC_NONCE_TTL_SECONDS"); ttl > 0 {
|
||||
SSOUserSyncNonceTTL = time.Duration(ttl) * time.Second
|
||||
} else {
|
||||
SSOUserSyncNonceTTL = 10 * time.Minute
|
||||
}
|
||||
SSOUserSyncMaxBodyBytes = viper.GetInt("SSO_USER_SYNC_MAX_BODY_BYTES")
|
||||
if SSOUserSyncMaxBodyBytes <= 0 {
|
||||
SSOUserSyncMaxBodyBytes = 32 * 1024
|
||||
}
|
||||
|
||||
if IsProd {
|
||||
ensureProdConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig() {
|
||||
@@ -117,3 +182,70 @@ func parseListWithDefault(key, def string) []string {
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func loadSSOClients(key string) map[string]SSOClientConfig {
|
||||
clients := make(map[string]SSOClientConfig)
|
||||
raw := strings.TrimSpace(viper.GetString(key))
|
||||
if raw == "" {
|
||||
return clients
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &clients); err != nil {
|
||||
utils.Log.Errorf("Failed to parse %s: %v", key, err)
|
||||
return make(map[string]SSOClientConfig)
|
||||
}
|
||||
result := make(map[string]SSOClientConfig, len(clients))
|
||||
for alias, cfg := range clients {
|
||||
alias = strings.ToLower(strings.TrimSpace(alias))
|
||||
for i, origin := range cfg.AllowedReturnOrigins {
|
||||
cfg.AllowedReturnOrigins[i] = strings.TrimSpace(origin)
|
||||
}
|
||||
cfg.SyncSecret = strings.TrimSpace(cfg.SyncSecret)
|
||||
result[alias] = cfg
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func defaultString(v, def string) string {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ensureProdConfig() {
|
||||
if SSOAuthorizeURL == "" || !strings.HasPrefix(SSOAuthorizeURL, "https://") {
|
||||
panic("SSO_AUTHORIZE_URL must be https in production")
|
||||
}
|
||||
if SSOTokenURL == "" || !strings.HasPrefix(SSOTokenURL, "https://") {
|
||||
panic("SSO_TOKEN_URL must be https in production")
|
||||
}
|
||||
if SSOGetMeURL == "" || !strings.HasPrefix(SSOGetMeURL, "https://") {
|
||||
panic("SSO_GETME_URL must be https in production")
|
||||
}
|
||||
if !SSOCookieSecure {
|
||||
panic("SSO_COOKIE_SECURE must be true in production")
|
||||
}
|
||||
if SSOCookieDomain == "" {
|
||||
panic("SSO_COOKIE_DOMAIN must be configured in production")
|
||||
}
|
||||
if len(SSOAllowedAudiences) == 0 {
|
||||
panic("SSO_ALLOWED_AUDIENCES must contain at least one audience in production")
|
||||
}
|
||||
for alias, cfg := range SSOClients {
|
||||
if strings.TrimSpace(cfg.SyncSecret) == "" {
|
||||
panic(fmt.Sprintf("SSO_CLIENTS[%s].sync_secret must be configured in production", alias))
|
||||
}
|
||||
if len(cfg.SyncSecret) < 16 {
|
||||
panic(fmt.Sprintf("SSO_CLIENTS[%s].sync_secret must be at least 16 characters", alias))
|
||||
}
|
||||
}
|
||||
if SSOUserSyncDrift <= 0 {
|
||||
panic("SSO_USER_SYNC_SIGNATURE_DRIFT_SECONDS must be greater than zero in production")
|
||||
}
|
||||
if SSOUserSyncNonceTTL <= 0 {
|
||||
panic("SSO_USER_SYNC_NONCE_TTL_SECONDS must be greater than zero in production")
|
||||
}
|
||||
if SSOUserSyncMaxBodyBytes <= 0 {
|
||||
panic("SSO_USER_SYNC_MAX_BODY_BYTES must be greater than zero in production")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user