Update besar
This commit is contained in:
No files matched your search
+293
-60
@@ -13,16 +13,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Databases map[string]DatabaseConfig
|
||||
ReadReplicas map[string][]DatabaseConfig // For read replicas
|
||||
Auth AuthConfig
|
||||
Keycloak KeycloakConfig
|
||||
Bpjs BpjsConfig
|
||||
SatuSehat SatuSehatConfig
|
||||
Swagger SwaggerConfig
|
||||
Security SecurityConfig
|
||||
Validator *validator.Validate
|
||||
}
|
||||
|
||||
@@ -63,6 +67,25 @@ type DatabaseConfig struct {
|
||||
ConnMaxLifetime time.Duration // Connection max lifetime
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
Type string `yaml:"type" env:"AUTH_TYPE"` // "keycloak", "jwt", "static", "hybrid"
|
||||
StaticTokens []string `yaml:"static_tokens" env:"AUTH_STATIC_TOKENS"` // Support multiple static tokens
|
||||
FallbackTo string `yaml:"fallback_to" env:"AUTH_FALLBACK_TO"` // fallback auth type if primary fails
|
||||
}
|
||||
|
||||
// AuthYAMLConfig represents the auth section in config.yaml
|
||||
type AuthYAMLConfig struct {
|
||||
Type string `yaml:"type"`
|
||||
StaticTokens []string `yaml:"static_tokens"`
|
||||
FallbackTo string `yaml:"fallback_to"`
|
||||
}
|
||||
type KeycloakYAMLConfig struct {
|
||||
Issuer string `yaml:"issuer"`
|
||||
Audience string `yaml:"audience"`
|
||||
JwksURL string `yaml:"jwks_url"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
type KeycloakConfig struct {
|
||||
Issuer string
|
||||
Audience string
|
||||
@@ -90,27 +113,30 @@ type SatuSehatConfig struct {
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
}
|
||||
|
||||
// SetHeader generates required headers for BPJS VClaim API
|
||||
// func (cfg BpjsConfig) SetHeader() (string, string, string, string, string) {
|
||||
// timenow := time.Now().UTC()
|
||||
// t, err := time.Parse(time.RFC3339, "1970-01-01T00:00:00Z")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// SecurityConfig berisi semua pengaturan untuk middleware keamanan
|
||||
type SecurityConfig struct {
|
||||
// CORS
|
||||
TrustedOrigins []string `mapstructure:"trusted_origins"`
|
||||
// Rate Limiting
|
||||
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
||||
// Input Validation
|
||||
MaxInputLength int `mapstructure:"max_input_length"`
|
||||
}
|
||||
|
||||
// tstamp := timenow.Unix() - t.Unix()
|
||||
// secret := []byte(cfg.SecretKey)
|
||||
// message := []byte(cfg.ConsID + "&" + fmt.Sprint(tstamp))
|
||||
// hash := hmac.New(sha256.New, secret)
|
||||
// hash.Write(message)
|
||||
// RateLimitConfig berisi pengaturan untuk rate limiter
|
||||
type RateLimitConfig struct {
|
||||
RequestsPerMinute int `mapstructure:"requests_per_minute"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
}
|
||||
|
||||
// // to lowercase hexits
|
||||
// hex.EncodeToString(hash.Sum(nil))
|
||||
// // to base64
|
||||
// xSignature := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||
// RedisConfig berisi detail koneksi ke Redis
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// return cfg.ConsID, cfg.SecretKey, cfg.UserKey, fmt.Sprint(tstamp), xSignature
|
||||
// }
|
||||
func (cfg BpjsConfig) SetHeader() (string, string, string, string, string) {
|
||||
timenow := time.Now().UTC()
|
||||
t, err := time.Parse(time.RFC3339, "1970-01-01T00:00:00Z")
|
||||
@@ -149,6 +175,7 @@ func (cfg ConfigBpjs) SetHeader() (string, string, string, string, string) {
|
||||
}
|
||||
|
||||
func LoadConfig() *Config {
|
||||
log.Printf("DEBUG: Raw ENV for SECURITY_MAX_INPUT_LENGTH is: '%s'", os.Getenv("SECURITY_MAX_INPUT_LENGTH"))
|
||||
config := &Config{
|
||||
Server: ServerConfig{
|
||||
Port: getEnvAsInt("PORT", 8080),
|
||||
@@ -156,12 +183,8 @@ func LoadConfig() *Config {
|
||||
},
|
||||
Databases: make(map[string]DatabaseConfig),
|
||||
ReadReplicas: make(map[string][]DatabaseConfig),
|
||||
Keycloak: KeycloakConfig{
|
||||
Issuer: getEnv("KEYCLOAK_ISSUER", "https://keycloak.example.com/auth/realms/yourrealm"),
|
||||
Audience: getEnv("KEYCLOAK_AUDIENCE", "your-client-id"),
|
||||
JwksURL: getEnv("KEYCLOAK_JWKS_URL", "https://keycloak.example.com/auth/realms/yourrealm/protocol/openid-connect/certs"),
|
||||
Enabled: getEnvAsBool("KEYCLOAK_ENABLED", true),
|
||||
},
|
||||
Auth: loadAuthConfig(),
|
||||
Keycloak: loadKeycloakConfig(),
|
||||
Bpjs: BpjsConfig{
|
||||
BaseURL: getEnv("BPJS_BASEURL", "https://apijkn.bpjs-kesehatan.go.id"),
|
||||
ConsID: getEnv("BPJS_CONSID", ""),
|
||||
@@ -194,8 +217,21 @@ func LoadConfig() *Config {
|
||||
BasePath: getEnv("SWAGGER_BASE_PATH", "/api/v1"),
|
||||
Schemes: parseSchemes(getEnv("SWAGGER_SCHEMES", "http,https")),
|
||||
},
|
||||
Security: SecurityConfig{
|
||||
TrustedOrigins: parseOrigins(getEnv("SECURITY_TRUSTED_ORIGINS", "http://localhost:3000,http://localhost:8080")),
|
||||
MaxInputLength: getEnvAsInt("SECURITY_MAX_INPUT_LENGTH", 500),
|
||||
RateLimit: RateLimitConfig{
|
||||
RequestsPerMinute: getEnvAsInt("RATE_LIMIT_REQUESTS_PER_MINUTE", 60),
|
||||
Redis: RedisConfig{
|
||||
Host: getEnv("REDIS_HOST", "localhost"),
|
||||
Port: getEnvAsInt("REDIS_PORT", 6379),
|
||||
Password: getEnv("REDIS_PASSWORD", ""),
|
||||
DB: getEnvAsInt("REDIS_DB", 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
log.Printf("DEBUG: Final Config Object. MaxInputLength is: %d", config.Security.MaxInputLength)
|
||||
// Initialize validator
|
||||
config.Validator = validator.New()
|
||||
|
||||
@@ -205,28 +241,155 @@ func LoadConfig() *Config {
|
||||
// Load read replica configurations
|
||||
config.loadReadReplicaConfigs()
|
||||
|
||||
log.Printf("DEBUG [LoadConfig]: Config object created at address: %p", config)
|
||||
log.Printf("DEBUG [LoadConfig]: Security.MaxInputLength is: %d", config.Security.MaxInputLength)
|
||||
return config
|
||||
}
|
||||
|
||||
func loadAuthConfig() AuthConfig {
|
||||
// --- AWAL TAMBAHAN DEBUG ---
|
||||
// Cetak direktori kerja saat ini untuk debugging
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("Error getting working directory: %v", err)
|
||||
} else {
|
||||
log.Printf("DEBUG: Current working directory is: %s", wd)
|
||||
}
|
||||
// --- AKHIR TAMBAHAN DEBUG ---
|
||||
|
||||
authConfig := AuthConfig{
|
||||
Type: "jwt", // default to jwt for backward compatibility
|
||||
FallbackTo: "",
|
||||
StaticTokens: []string{},
|
||||
}
|
||||
|
||||
// Path file yang akan dibaca
|
||||
configPath := "internal/config/config.yaml"
|
||||
log.Printf("DEBUG: Attempting to read auth config from: %s", configPath)
|
||||
|
||||
// Load auth configuration from config.yaml first
|
||||
if data, err := os.ReadFile(configPath); err == nil {
|
||||
log.Printf("DEBUG: Successfully read config.yaml file. Parsing...") // Tambahkan log sukses
|
||||
|
||||
var yamlConfig struct {
|
||||
Auth AuthYAMLConfig `yaml:"auth"`
|
||||
}
|
||||
if err := yaml.Unmarshal(data, &yamlConfig); err == nil {
|
||||
// Log nilai yang berhasil dibaca
|
||||
log.Printf("DEBUG: Parsed YAML. Type: '%s', Tokens: %d", yamlConfig.Auth.Type, len(yamlConfig.Auth.StaticTokens))
|
||||
|
||||
authConfig.Type = yamlConfig.Auth.Type
|
||||
authConfig.FallbackTo = yamlConfig.Auth.FallbackTo
|
||||
authConfig.StaticTokens = yamlConfig.Auth.StaticTokens
|
||||
} else {
|
||||
log.Printf("ERROR: Failed to unmarshal YAML: %v", err)
|
||||
}
|
||||
} else {
|
||||
// --- AWAL TAMBAHAN DEBUG ---
|
||||
// Cetak error spesifik jika file tidak ditemukan
|
||||
log.Printf("ERROR: Could not read config file at '%s': %v", configPath, err)
|
||||
// --- AKHIR TAMBAHAN DEBUG ---
|
||||
}
|
||||
|
||||
// Then override with environment variables if set
|
||||
if envType := getEnv("AUTH_TYPE", ""); envType != "" {
|
||||
log.Printf("DEBUG: Overriding auth type with environment variable: %s", envType)
|
||||
authConfig.Type = envType
|
||||
}
|
||||
if envFallback := getEnv("AUTH_FALLBACK_TO", ""); envFallback != "" {
|
||||
authConfig.FallbackTo = envFallback
|
||||
}
|
||||
envTokens := parseStaticTokens(getEnv("AUTH_STATIC_TOKENS", ""))
|
||||
if len(envTokens) > 0 {
|
||||
authConfig.StaticTokens = envTokens
|
||||
}
|
||||
|
||||
// Log hasil akhir sebelum dikembalikan
|
||||
log.Printf("DEBUG: Final AuthConfig before returning: Type='%s', TokenCount=%d", authConfig.Type, len(authConfig.StaticTokens))
|
||||
|
||||
return authConfig
|
||||
}
|
||||
|
||||
// Lakukan hal yang sama untuk loadKeycloakConfig
|
||||
func loadKeycloakConfig() KeycloakConfig {
|
||||
// --- AWAL TAMBAHAN DEBUG ---
|
||||
// Cetak direktori kerja saat ini untuk debugging
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("Error getting working directory for keycloak config: %v", err)
|
||||
} else {
|
||||
log.Printf("DEBUG (Keycloak): Current working directory is: %s", wd)
|
||||
}
|
||||
// --- AKHIR TAMBAHAN DEBUG ---
|
||||
|
||||
v := viper.New()
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("./config")
|
||||
v.AddConfigPath("./internal/config")
|
||||
|
||||
// --- AWAL TAMBAHAN DEBUG ---
|
||||
log.Printf("DEBUG (Keycloak): Viper is set to search for config in: '.', './config', './internal/config'")
|
||||
// --- AKHIR TAMBAHAN DEBUG ---
|
||||
|
||||
if err := v.ReadInConfig(); err == nil {
|
||||
// Log jika file berhasil ditemukan dan dibaca
|
||||
log.Printf("DEBUG (Keycloak): Successfully read config file: %s", v.ConfigFileUsed())
|
||||
|
||||
keycloakConfig := KeycloakConfig{
|
||||
Issuer: v.GetString("keycloak.issuer"),
|
||||
Audience: v.GetString("keycloak.audience"),
|
||||
JwksURL: v.GetString("keycloak.jwks_url"),
|
||||
Enabled: v.GetBool("keycloak.enabled"),
|
||||
}
|
||||
|
||||
// Log nilai yang berhasil dibaca dari file
|
||||
log.Printf("DEBUG (Keycloak): Parsed values from file. Issuer: '%s', Enabled: %t", keycloakConfig.Issuer, keycloakConfig.Enabled)
|
||||
|
||||
log.Printf("Loaded keycloak config from file: enabled=%t", keycloakConfig.Enabled)
|
||||
return keycloakConfig
|
||||
} else {
|
||||
// --- AWAL TAMBAHAN DEBUG ---
|
||||
// Cetak error spesifik jika file tidak ditemukan
|
||||
log.Printf("ERROR (Keycloak): Could not read config file: %v", err)
|
||||
// --- AKHIR TAMBAHAN DEBUG ---
|
||||
}
|
||||
|
||||
// Fallback ke environment variable
|
||||
log.Printf("DEBUG (Keycloak): Falling back to environment variables.")
|
||||
fallbackConfig := KeycloakConfig{
|
||||
Issuer: getEnv("KEYCLOAK_ISSUER", ""),
|
||||
Audience: getEnv("KEYCLOAK_AUDIENCE", ""),
|
||||
JwksURL: getEnv("KEYCLOAK_JWKS_URL", ""),
|
||||
Enabled: getEnvAsBool("KEYCLOAK_ENABLED", false),
|
||||
}
|
||||
|
||||
// Log hasil akhir dari fallback
|
||||
log.Printf("DEBUG (Keycloak): Final fallback config. Issuer: '%s', Enabled: %t", fallbackConfig.Issuer, fallbackConfig.Enabled)
|
||||
|
||||
return fallbackConfig
|
||||
}
|
||||
|
||||
func (c *Config) loadDatabaseConfigs() {
|
||||
// Simplified approach: Directly load from environment variables
|
||||
// This ensures we get the exact values specified in .env
|
||||
|
||||
// Primary database configuration
|
||||
c.Databases["default"] = DatabaseConfig{
|
||||
Name: "default",
|
||||
Type: getEnv("DB_CONNECTION", "postgres"),
|
||||
Host: getEnv("DB_HOST", "localhost"),
|
||||
Port: getEnvAsInt("DB_PORT", 5432),
|
||||
Username: getEnv("DB_USERNAME", ""),
|
||||
Password: getEnv("DB_PASSWORD", ""),
|
||||
Database: getEnv("DB_DATABASE", "satu_db"),
|
||||
Schema: getEnv("DB_SCHEMA", "public"),
|
||||
SSLMode: getEnv("DB_SSLMODE", "disable"),
|
||||
MaxOpenConns: getEnvAsInt("DB_MAX_OPEN_CONNS", 25),
|
||||
MaxIdleConns: getEnvAsInt("DB_MAX_IDLE_CONNS", 25),
|
||||
ConnMaxLifetime: parseDuration(getEnv("DB_CONN_MAX_LIFETIME", "5m")),
|
||||
}
|
||||
// // Primary database configuration
|
||||
// c.Databases["default"] = DatabaseConfig{
|
||||
// Name: "default",
|
||||
// Type: getEnv("DB_CONNECTION", "postgres"),
|
||||
// Host: getEnv("DB_HOST", "localhost"),
|
||||
// Port: getEnvAsInt("DB_PORT", 5432),
|
||||
// Username: getEnv("DB_USERNAME", ""),
|
||||
// Password: getEnv("DB_PASSWORD", ""),
|
||||
// Database: getEnv("DB_DATABASE", "satu_db"),
|
||||
// Schema: getEnv("DB_SCHEMA", "public"),
|
||||
// SSLMode: getEnv("DB_SSLMODE", "disable"),
|
||||
// MaxOpenConns: getEnvAsInt("DB_MAX_OPEN_CONNS", 25),
|
||||
// MaxIdleConns: getEnvAsInt("DB_MAX_IDLE_CONNS", 25),
|
||||
// ConnMaxLifetime: parseDuration(getEnv("DB_CONN_MAX_LIFETIME", "5m")),
|
||||
// }
|
||||
|
||||
// SATUDATA database configuration
|
||||
c.addPostgreSQLConfigs()
|
||||
@@ -669,71 +832,141 @@ func parseSchemes(schemesStr string) []string {
|
||||
return schemes
|
||||
}
|
||||
|
||||
// parseStaticTokens parses comma-separated static tokens string into a slice
|
||||
func parseStaticTokens(tokensStr string) []string {
|
||||
if tokensStr == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
tokens := strings.Split(tokensStr, ",")
|
||||
for i, token := range tokens {
|
||||
tokens[i] = strings.TrimSpace(token)
|
||||
// Remove empty tokens
|
||||
if tokens[i] == "" {
|
||||
tokens = append(tokens[:i], tokens[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func parseOrigins(originsStr string) []string {
|
||||
if originsStr == "" {
|
||||
return []string{"http://localhost:8080"} // Default untuk pengembangan
|
||||
}
|
||||
origins := strings.Split(originsStr, ",")
|
||||
for i, origin := range origins {
|
||||
origins[i] = strings.TrimSpace(origin)
|
||||
}
|
||||
return origins
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
var errs []string
|
||||
|
||||
if len(c.Databases) == 0 {
|
||||
log.Fatal("At least one database configuration is required")
|
||||
errs = append(errs, "at least one database configuration is required")
|
||||
}
|
||||
|
||||
for name, db := range c.Databases {
|
||||
if db.Host == "" {
|
||||
log.Fatalf("Database host is required for %s", name)
|
||||
errs = append(errs, fmt.Sprintf("database host is required for %s", name))
|
||||
}
|
||||
if db.Username == "" {
|
||||
log.Fatalf("Database username is required for %s", name)
|
||||
errs = append(errs, fmt.Sprintf("database username is required for %s", name))
|
||||
}
|
||||
if db.Password == "" {
|
||||
log.Fatalf("Database password is required for %s", name)
|
||||
errs = append(errs, fmt.Sprintf("database password is required for %s", name))
|
||||
}
|
||||
if db.Database == "" {
|
||||
log.Fatalf("Database name is required for %s", name)
|
||||
errs = append(errs, fmt.Sprintf("database name is required for %s", name))
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bpjs.BaseURL == "" {
|
||||
log.Fatal("BPJS Base URL is required")
|
||||
errs = append(errs, "BPJS Base URL is required")
|
||||
}
|
||||
if c.Bpjs.ConsID == "" {
|
||||
log.Fatal("BPJS Consumer ID is required")
|
||||
errs = append(errs, "BPJS Consumer ID is required")
|
||||
}
|
||||
if c.Bpjs.UserKey == "" {
|
||||
log.Fatal("BPJS User Key is required")
|
||||
errs = append(errs, "BPJS User Key is required")
|
||||
}
|
||||
if c.Bpjs.SecretKey == "" {
|
||||
log.Fatal("BPJS Secret Key is required")
|
||||
errs = append(errs, "BPJS Secret Key is required")
|
||||
}
|
||||
|
||||
// Validate Keycloak configuration if enabled
|
||||
if c.Keycloak.Enabled {
|
||||
// Validate authentication configuration
|
||||
switch c.Auth.Type {
|
||||
case "keycloak":
|
||||
if !c.Keycloak.Enabled {
|
||||
errs = append(errs, "keycloak.enabled must be true when auth.type is 'keycloak'")
|
||||
}
|
||||
if c.Keycloak.Issuer == "" {
|
||||
log.Fatal("Keycloak issuer is required when Keycloak is enabled")
|
||||
errs = append(errs, "keycloak.issuer is required when auth.type is 'keycloak'")
|
||||
}
|
||||
if c.Keycloak.Audience == "" {
|
||||
log.Fatal("Keycloak audience is required when Keycloak is enabled")
|
||||
errs = append(errs, "keycloak.audience is required when auth.type is 'keycloak'")
|
||||
}
|
||||
if c.Keycloak.JwksURL == "" {
|
||||
log.Fatal("Keycloak JWKS URL is required when Keycloak is enabled")
|
||||
errs = append(errs, "keycloak.jwks_url is required when auth.type is 'keycloak'")
|
||||
}
|
||||
case "static":
|
||||
if len(c.Auth.StaticTokens) == 0 {
|
||||
errs = append(errs, "auth.static_tokens is required when auth.type is 'static'")
|
||||
}
|
||||
case "hybrid":
|
||||
if c.Auth.FallbackTo == "" {
|
||||
errs = append(errs, "auth.fallback_to is required when auth.type is 'hybrid'")
|
||||
}
|
||||
// Validate fallback configuration
|
||||
switch c.Auth.FallbackTo {
|
||||
case "keycloak":
|
||||
if !c.Keycloak.Enabled {
|
||||
errs = append(errs, "keycloak.enabled must be true when auth.fallback_to is 'keycloak'")
|
||||
}
|
||||
case "static":
|
||||
if len(c.Auth.StaticTokens) == 0 {
|
||||
errs = append(errs, "auth.static_tokens is required when auth.fallback_to is 'static'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy validation for backward compatibility
|
||||
if c.Auth.Type != "keycloak" && c.Keycloak.Enabled {
|
||||
if c.Keycloak.Issuer == "" {
|
||||
errs = append(errs, "Keycloak issuer is required when Keycloak is enabled")
|
||||
}
|
||||
if c.Keycloak.Audience == "" {
|
||||
errs = append(errs, "Keycloak audience is required when Keycloak is enabled")
|
||||
}
|
||||
if c.Keycloak.JwksURL == "" {
|
||||
errs = append(errs, "Keycloak JWKS URL is required when Keycloak is enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate SatuSehat configuration
|
||||
if c.SatuSehat.OrgID == "" {
|
||||
log.Fatal("SatuSehat Organization ID is required")
|
||||
errs = append(errs, "SatuSehat Organization ID is required")
|
||||
}
|
||||
if c.SatuSehat.FasyakesID == "" {
|
||||
log.Fatal("SatuSehat Fasyankes ID is required")
|
||||
errs = append(errs, "SatuSehat Fasyankes ID is required")
|
||||
}
|
||||
if c.SatuSehat.ClientID == "" {
|
||||
log.Fatal("SatuSehat Client ID is required")
|
||||
errs = append(errs, "SatuSehat Client ID is required")
|
||||
}
|
||||
if c.SatuSehat.ClientSecret == "" {
|
||||
log.Fatal("SatuSehat Client Secret is required")
|
||||
errs = append(errs, "SatuSehat Client Secret is required")
|
||||
}
|
||||
if c.SatuSehat.AuthURL == "" {
|
||||
log.Fatal("SatuSehat Auth URL is required")
|
||||
errs = append(errs, "SatuSehat Auth URL is required")
|
||||
}
|
||||
if c.SatuSehat.BaseURL == "" {
|
||||
log.Fatal("SatuSehat Base URL is required")
|
||||
errs = append(errs, "SatuSehat Base URL is required")
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("configuration validation failed: %s", strings.Join(errs, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
auth:
|
||||
type: static # Options: jwt, keycloak, static, hybrid (for hybrid mode keycloak is primary and jwt is fallback)
|
||||
static_tokens:
|
||||
- token1
|
||||
- token2
|
||||
- token3
|
||||
- token4
|
||||
fallback_to: jwt # Options: keycloak, static, jwt (for hybrid mode keycloak is primary and jwt is fallback)
|
||||
keycloak:
|
||||
enabled: true
|
||||
issuer: https://auth.rssa.top/realms/sandbox
|
||||
audience: nuxtsim-pendaftaran
|
||||
jwks_url: https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs
|
||||
|
||||
Reference in New Issue
Block a user