Move config struct to model, avoid dependancy to storage
This commit is contained in:
parent
34f1404adb
commit
ee2f033b0d
33 changed files with 377 additions and 228 deletions
|
@ -67,7 +67,7 @@ func main() {
|
||||||
color.NoColor = true
|
color.NoColor = true
|
||||||
|
|
||||||
// Load and parse options
|
// Load and parse options
|
||||||
var opts *config.Options
|
var opts *happydns.Options
|
||||||
if opts, err = config.ConsolidateConfig(); err != nil {
|
if opts, err = config.ConsolidateConfig(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,18 +29,17 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
"git.happydns.org/happyDomain/internal/usecase"
|
"git.happydns.org/happyDomain/internal/usecase"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackupController struct {
|
type BackupController struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
store storage.Storage
|
store storage.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBackupController(cfg *config.Options, store storage.Storage) *BackupController {
|
func NewBackupController(cfg *happydns.Options, store storage.Storage) *BackupController {
|
||||||
return &BackupController{
|
return &BackupController{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
store: store,
|
store: store,
|
||||||
|
|
|
@ -26,17 +26,16 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/usecase/session"
|
"git.happydns.org/happyDomain/internal/usecase/session"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionController struct {
|
type SessionController struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
store session.SessionStorage
|
store session.SessionStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionController(cfg *config.Options, store session.SessionStorage) *SessionController {
|
func NewSessionController(cfg *happydns.Options, store session.SessionStorage) *SessionController {
|
||||||
return &SessionController{
|
return &SessionController{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
store: store,
|
store: store,
|
||||||
|
|
|
@ -25,11 +25,11 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/api-admin/controller"
|
"git.happydns.org/happyDomain/internal/api-admin/controller"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func declareBackupRoutes(cfg *config.Options, router *gin.RouterGroup, store storage.Storage) {
|
func declareBackupRoutes(cfg *happydns.Options, router *gin.RouterGroup, store storage.Storage) {
|
||||||
bc := controller.NewBackupController(cfg, store)
|
bc := controller.NewBackupController(cfg, store)
|
||||||
|
|
||||||
router.POST("/backup.json", bc.BackupJSON)
|
router.POST("/backup.json", bc.BackupJSON)
|
||||||
|
|
|
@ -25,12 +25,11 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
api "git.happydns.org/happyDomain/internal/api/route"
|
api "git.happydns.org/happyDomain/internal/api/route"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DeclareRoutes(cfg *config.Options, router *gin.Engine, s storage.Storage, dependancies happydns.UsecaseDependancies) {
|
func DeclareRoutes(cfg *happydns.Options, router *gin.Engine, s storage.Storage, dependancies happydns.UsecaseDependancies) {
|
||||||
apiRoutes := router.Group("/api")
|
apiRoutes := router.Group("/api")
|
||||||
|
|
||||||
declareBackupRoutes(cfg, apiRoutes, s)
|
declareBackupRoutes(cfg, apiRoutes, s)
|
||||||
|
|
|
@ -25,11 +25,11 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/api-admin/controller"
|
"git.happydns.org/happyDomain/internal/api-admin/controller"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func declareSessionsRoutes(cfg *config.Options, router *gin.RouterGroup, store storage.Storage) {
|
func declareSessionsRoutes(cfg *happydns.Options, router *gin.RouterGroup, store storage.Storage) {
|
||||||
sc := controller.NewSessionController(cfg, store)
|
sc := controller.NewSessionController(cfg, store)
|
||||||
|
|
||||||
router.DELETE("/sessions", sc.DeleteSessions)
|
router.DELETE("/sessions", sc.DeleteSessions)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
@ -39,7 +40,6 @@ import (
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/api/middleware"
|
"git.happydns.org/happyDomain/internal/api/middleware"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,23 +48,39 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type OIDCProvider struct {
|
type OIDCProvider struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
authService happydns.AuthenticationUsecase
|
authService happydns.AuthenticationUsecase
|
||||||
oauth2config *oauth2.Config
|
oauth2config *oauth2.Config
|
||||||
oidcVerifier *oidc.IDTokenVerifier
|
oidcVerifier *oidc.IDTokenVerifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOIDCProvider(cfg *config.Options, authService happydns.AuthenticationUsecase) *OIDCProvider {
|
func GetOIDCProvider(o *happydns.Options, ctx context.Context) (*oidc.Provider, error) {
|
||||||
|
return oidc.NewProvider(ctx, strings.TrimSuffix(o.OIDCClients[0].ProviderURL.String(), "/.well-known/openid-configuration"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOAuth2Config(o *happydns.Options, provider *oidc.Provider) *oauth2.Config {
|
||||||
|
oauth2Config := oauth2.Config{
|
||||||
|
ClientID: o.OIDCClients[0].ClientID,
|
||||||
|
ClientSecret: o.OIDCClients[0].ClientSecret,
|
||||||
|
RedirectURL: o.GetAuthURL().String(),
|
||||||
|
Endpoint: provider.Endpoint(),
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oauth2Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOIDCProvider(cfg *happydns.Options, authService happydns.AuthenticationUsecase) *OIDCProvider {
|
||||||
// Initialize OIDC
|
// Initialize OIDC
|
||||||
provider, err := cfg.GetOIDCProvider(context.Background())
|
provider, err := GetOIDCProvider(cfg, context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to instantiate OIDC Provider:", err)
|
log.Fatal("Unable to instantiate OIDC Provider:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2Config := cfg.GetOAuth2Config(provider)
|
oauth2Config := GetOAuth2Config(cfg, provider)
|
||||||
|
|
||||||
oidcVerifier := provider.Verifier(&oidc.Config{
|
oidcVerifier := provider.Verifier(&oidc.Config{
|
||||||
ClientID: config.OIDCClientID,
|
ClientID: cfg.OIDCClients[0].ClientID,
|
||||||
})
|
})
|
||||||
|
|
||||||
return &OIDCProvider{
|
return &OIDCProvider{
|
||||||
|
|
|
@ -29,22 +29,21 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/api/controller"
|
"git.happydns.org/happyDomain/internal/api/controller"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DeclareAuthenticationRoutes(cfg *config.Options, baserouter, apirouter *gin.RouterGroup, dependancies happydns.UsecaseDependancies) *controller.LoginController {
|
func DeclareAuthenticationRoutes(cfg *happydns.Options, baserouter, apirouter *gin.RouterGroup, dependancies happydns.UsecaseDependancies) *controller.LoginController {
|
||||||
lc := controller.NewLoginController(dependancies.AuthenticationUsecase())
|
lc := controller.NewLoginController(dependancies.AuthenticationUsecase())
|
||||||
|
|
||||||
apirouter.POST("/auth", lc.Login)
|
apirouter.POST("/auth", lc.Login)
|
||||||
apirouter.POST("/auth/logout", lc.Logout)
|
apirouter.POST("/auth/logout", lc.Logout)
|
||||||
|
|
||||||
if cfg.GetOIDCProviderURL() != "" {
|
if len(cfg.OIDCClients) > 0 {
|
||||||
oidcp := controller.NewOIDCProvider(cfg, dependancies.AuthenticationUsecase())
|
oidcp := controller.NewOIDCProvider(cfg, dependancies.AuthenticationUsecase())
|
||||||
|
|
||||||
authRoutes := baserouter.Group("/auth")
|
authRoutes := baserouter.Group("/auth")
|
||||||
|
|
||||||
providerurl, _ := url.Parse(cfg.GetOIDCProviderURL())
|
providerurl, _ := url.Parse(cfg.OIDCClients[0].ProviderURL.String())
|
||||||
authRoutes.GET("has_oidc", func(c *gin.Context) {
|
authRoutes.GET("has_oidc", func(c *gin.Context) {
|
||||||
parts := strings.Split(strings.TrimSuffix(providerurl.Host, "."), ".")
|
parts := strings.Split(strings.TrimSuffix(providerurl.Host, "."), ".")
|
||||||
if len(parts) > 2 {
|
if len(parts) > 2 {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/api/middleware"
|
"git.happydns.org/happyDomain/internal/api/middleware"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,7 +48,7 @@ import (
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @description Description for what is this security definition being used
|
// @description Description for what is this security definition being used
|
||||||
|
|
||||||
func DeclareRoutes(cfg *config.Options, router *gin.Engine, dependancies happydns.UsecaseDependancies) {
|
func DeclareRoutes(cfg *happydns.Options, router *gin.Engine, dependancies happydns.UsecaseDependancies) {
|
||||||
// Declare routes
|
// Declare routes
|
||||||
baseRoutes := router.Group("")
|
baseRoutes := router.Group("")
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,13 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
admin "git.happydns.org/happyDomain/internal/api-admin/route"
|
admin "git.happydns.org/happyDomain/internal/api-admin/route"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/usecase"
|
"git.happydns.org/happyDomain/internal/usecase"
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Admin struct {
|
type Admin struct {
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
cfg *config.Options
|
cfg *happydns.Options
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
api "git.happydns.org/happyDomain/internal/api/route"
|
api "git.happydns.org/happyDomain/internal/api/route"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/mailer"
|
"git.happydns.org/happyDomain/internal/mailer"
|
||||||
"git.happydns.org/happyDomain/internal/newsletter"
|
"git.happydns.org/happyDomain/internal/newsletter"
|
||||||
"git.happydns.org/happyDomain/internal/session"
|
"git.happydns.org/happyDomain/internal/session"
|
||||||
|
@ -59,7 +58,7 @@ type Usecases struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
cfg *config.Options
|
cfg *happydns.Options
|
||||||
mailer *mailer.Mailer
|
mailer *mailer.Mailer
|
||||||
newsletter happydns.NewsletterSubscriptor
|
newsletter happydns.NewsletterSubscriptor
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
|
@ -125,7 +124,7 @@ func (a *App) ZoneUsecase() happydns.ZoneUsecase {
|
||||||
return a.usecases.zone
|
return a.usecases.zone
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(cfg *config.Options) *App {
|
func NewApp(cfg *happydns.Options) *App {
|
||||||
app := &App{
|
app := &App{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
@ -140,7 +139,7 @@ func NewApp(cfg *config.Options) *App {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppWithStorage(cfg *config.Options, store storage.Storage) *App {
|
func NewAppWithStorage(cfg *happydns.Options, store storage.Storage) *App {
|
||||||
app := &App{
|
app := &App{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
store: store,
|
store: store,
|
||||||
|
@ -191,9 +190,9 @@ func (app *App) initStorageEngine() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) initNewsletter() {
|
func (app *App) initNewsletter() {
|
||||||
if app.cfg.ListmonkURL.URL != nil {
|
if app.cfg.ListmonkURL.String() != "" {
|
||||||
app.newsletter = &newsletter.ListmonkNewsletterSubscription{
|
app.newsletter = &newsletter.ListmonkNewsletterSubscription{
|
||||||
ListmonkURL: app.cfg.ListmonkURL.URL,
|
ListmonkURL: &app.cfg.ListmonkURL,
|
||||||
ListmonkId: app.cfg.ListmonkId,
|
ListmonkId: app.cfg.ListmonkId,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/api/controller"
|
"git.happydns.org/happyDomain/internal/api/controller"
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
@ -45,7 +44,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type insightsCollector struct {
|
type insightsCollector struct {
|
||||||
cfg *config.Options
|
cfg *happydns.Options
|
||||||
store storage.Storage
|
store storage.Storage
|
||||||
stop chan bool
|
stop chan bool
|
||||||
}
|
}
|
||||||
|
@ -117,11 +116,12 @@ func (c *insightsCollector) collect() (*happydns.Insights, error) {
|
||||||
data.Config.DisableEmbeddedLogin = c.cfg.DisableEmbeddedLogin
|
data.Config.DisableEmbeddedLogin = c.cfg.DisableEmbeddedLogin
|
||||||
data.Config.DisableProviders = c.cfg.DisableProviders
|
data.Config.DisableProviders = c.cfg.DisableProviders
|
||||||
data.Config.DisableRegistration = c.cfg.DisableRegistration
|
data.Config.DisableRegistration = c.cfg.DisableRegistration
|
||||||
data.Config.HasBaseURL = c.cfg.GetBasePath() != ""
|
data.Config.HasBaseURL = c.cfg.BasePath != ""
|
||||||
data.Config.HasDevProxy = c.cfg.DevProxy != ""
|
data.Config.HasDevProxy = c.cfg.DevProxy != ""
|
||||||
data.Config.HasExternalAuth = c.cfg.ExternalAuth.String() != ""
|
data.Config.HasExternalAuth = c.cfg.ExternalAuth.String() != ""
|
||||||
data.Config.HasListmonkURL = c.cfg.ListmonkURL.String() != ""
|
data.Config.HasListmonkURL = c.cfg.ListmonkURL.String() != ""
|
||||||
data.Config.LocalBind = strings.HasPrefix(c.cfg.Bind, "127.0.0.1:") || strings.HasPrefix(c.cfg.Bind, "[::1]:")
|
data.Config.LocalBind = strings.HasPrefix(c.cfg.Bind, "127.0.0.1:") || strings.HasPrefix(c.cfg.Bind, "[::1]:")
|
||||||
|
data.Config.NbOidcProviders = len(c.cfg.OIDCClients)
|
||||||
data.Config.NoAuthActive = c.cfg.NoAuth
|
data.Config.NoAuthActive = c.cfg.NoAuth
|
||||||
data.Config.NoMail = c.cfg.NoMail
|
data.Config.NoMail = c.cfg.NoMail
|
||||||
data.Config.NonUnixAdminBind = strings.Contains(c.cfg.AdminBind, ":")
|
data.Config.NonUnixAdminBind = strings.Contains(c.cfg.AdminBind, ":")
|
||||||
|
|
|
@ -26,26 +26,27 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// declareFlags registers flags for the structure Options.
|
// declareFlags registers flags for the structure Options.
|
||||||
func (o *Options) declareFlags() {
|
func declareFlags(o *happydns.Options) {
|
||||||
flag.StringVar(&o.DevProxy, "dev", o.DevProxy, "Proxify traffic to this host for static assets")
|
flag.StringVar(&o.DevProxy, "dev", o.DevProxy, "Proxify traffic to this host for static assets")
|
||||||
flag.StringVar(&o.AdminBind, "admin-bind", o.AdminBind, "Bind port/socket for administration interface")
|
flag.StringVar(&o.AdminBind, "admin-bind", o.AdminBind, "Bind port/socket for administration interface")
|
||||||
flag.StringVar(&o.Bind, "bind", ":8081", "Bind port/socket")
|
flag.StringVar(&o.Bind, "bind", ":8081", "Bind port/socket")
|
||||||
flag.BoolVar(&o.DisableProviders, "disable-providers-edit", o.DisableProviders, "Disallow all actions on provider (add/edit/delete)")
|
flag.BoolVar(&o.DisableProviders, "disable-providers-edit", o.DisableProviders, "Disallow all actions on provider (add/edit/delete)")
|
||||||
flag.BoolVar(&o.DisableRegistration, "disable-registration", o.DisableRegistration, "Forbids new account creation through public form/API (still allow registration from external services)")
|
flag.BoolVar(&o.DisableRegistration, "disable-registration", o.DisableRegistration, "Forbids new account creation through public form/API (still allow registration from external services)")
|
||||||
flag.BoolVar(&o.DisableEmbeddedLogin, "disable-embedded-login", o.DisableEmbeddedLogin, "Disables the internal user/password login in favor of external-auth or OIDC")
|
flag.BoolVar(&o.DisableEmbeddedLogin, "disable-embedded-login", o.DisableEmbeddedLogin, "Disables the internal user/password login in favor of external-auth or OIDC")
|
||||||
flag.Var(&o.ExternalURL, "externalurl", "Begining of the URL, before the base, that should be used eg. in mails")
|
flag.Var(&URL{&o.ExternalURL}, "externalurl", "Begining of the URL, before the base, that should be used eg. in mails")
|
||||||
flag.StringVar(&o.baseURL, "baseurl", o.baseURL, "URL prepended to each URL")
|
flag.StringVar(&o.BasePath, "baseurl", o.BasePath, "URL prepended to each URL")
|
||||||
flag.StringVar(&o.DefaultNameServer, "default-ns", o.DefaultNameServer, "Adress to the default name server")
|
flag.StringVar(&o.DefaultNameServer, "default-ns", o.DefaultNameServer, "Adress to the default name server")
|
||||||
flag.Var(&o.StorageEngine, "storage-engine", fmt.Sprintf("Select the storage engine between %v", storage.GetStorageEngines()))
|
flag.StringVar(&o.StorageEngine, "storage-engine", o.StorageEngine, fmt.Sprintf("Select the storage engine between %v", storage.GetStorageEngines()))
|
||||||
flag.BoolVar(&o.NoAuth, "no-auth", false, "Disable user access control, use default account")
|
flag.BoolVar(&o.NoAuth, "no-auth", false, "Disable user access control, use default account")
|
||||||
flag.Var(&o.JWTSecretKey, "jwt-secret-key", "Secret key used to verify JWT authentication tokens (a random secret is used if undefined)")
|
flag.Var(&JWTSecretKey{&o.JWTSecretKey}, "jwt-secret-key", "Secret key used to verify JWT authentication tokens (a random secret is used if undefined)")
|
||||||
flag.Var(&o.ExternalAuth, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
|
flag.Var(&URL{&o.ExternalAuth}, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
|
||||||
flag.BoolVar(&o.OptOutInsights, "opt-out-insights", false, "Disable the anonymous usage statistics report. If you care about this project and don't participate in discussions, don't opt-out.")
|
flag.BoolVar(&o.OptOutInsights, "opt-out-insights", false, "Disable the anonymous usage statistics report. If you care about this project and don't participate in discussions, don't opt-out.")
|
||||||
|
|
||||||
flag.Var(&o.ListmonkURL, "newsletter-server-url", "Base URL of the listmonk newsletter server")
|
flag.Var(&URL{&o.ListmonkURL}, "newsletter-server-url", "Base URL of the listmonk newsletter server")
|
||||||
flag.IntVar(&o.ListmonkId, "newsletter-id", 1, "Listmonk identifier of the list receiving the new user")
|
flag.IntVar(&o.ListmonkId, "newsletter-id", 1, "Listmonk identifier of the list receiving the new user")
|
||||||
|
|
||||||
flag.BoolVar(&o.NoMail, "no-mail", o.NoMail, "Disable all automatic mails, skip email verification at registration")
|
flag.BoolVar(&o.NoMail, "no-mail", o.NoMail, "Disable all automatic mails, skip email verification at registration")
|
||||||
|
@ -60,11 +61,11 @@ func (o *Options) declareFlags() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseCLI parse the flags and treats extra args as configuration filename.
|
// parseCLI parse the flags and treats extra args as configuration filename.
|
||||||
func (o *Options) parseCLI() error {
|
func parseCLI(o *happydns.Options) error {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
for _, conf := range flag.Args() {
|
for _, conf := range flag.Args() {
|
||||||
err := o.parseFile(conf)
|
err := parseFile(o, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,104 +32,30 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options stores the configuration of the software.
|
|
||||||
type Options struct {
|
|
||||||
// AdminBind is the address:port or unix socket used to serve the admin
|
|
||||||
// API.
|
|
||||||
AdminBind string
|
|
||||||
|
|
||||||
// Bind is the address:port used to bind the main interface with API.
|
|
||||||
Bind string
|
|
||||||
|
|
||||||
// BaseURL is the relative path where begins the root of the app.
|
|
||||||
baseURL string
|
|
||||||
|
|
||||||
// DevProxy is the URL that override static assets.
|
|
||||||
DevProxy string
|
|
||||||
|
|
||||||
// DefaultNameServer is the NS server suggested by default.
|
|
||||||
DefaultNameServer string
|
|
||||||
|
|
||||||
// DisableProviders should disallow all actions on provider (add/edit/delete) through public API.
|
|
||||||
DisableProviders bool
|
|
||||||
|
|
||||||
// DisableRegistration forbids all new registration using the public form/API.
|
|
||||||
DisableRegistration bool
|
|
||||||
|
|
||||||
// DisableEmbeddedLogin disables the internal user/password login in favor of ExternalAuth or OIDC.
|
|
||||||
DisableEmbeddedLogin bool
|
|
||||||
|
|
||||||
// ExternalAuth is the URL of the login form to use instead of the embedded one.
|
|
||||||
ExternalAuth URL
|
|
||||||
|
|
||||||
// ExternalURL keeps the URL used in communications (such as email,
|
|
||||||
// ...), when it needs to use complete URL, not only relative parts.
|
|
||||||
ExternalURL URL
|
|
||||||
|
|
||||||
// JWTSecretKey stores the private key to sign and verify JWT tokens.
|
|
||||||
JWTSecretKey JWTSecretKey
|
|
||||||
|
|
||||||
// JWTSigningMethod is the signing method to check token signature.
|
|
||||||
JWTSigningMethod string
|
|
||||||
|
|
||||||
// NoAuth controls if there is user access control or not.
|
|
||||||
NoAuth bool
|
|
||||||
|
|
||||||
// OptOutInsights disable the anonymous usage statistics report.
|
|
||||||
OptOutInsights bool
|
|
||||||
|
|
||||||
// StorageEngine points to the storage engine used.
|
|
||||||
StorageEngine storage.StorageEngine
|
|
||||||
|
|
||||||
ListmonkURL URL
|
|
||||||
ListmonkId int
|
|
||||||
|
|
||||||
// MailFrom holds the content of the From field for all e-mails that
|
|
||||||
// will be send.
|
|
||||||
MailFrom mail.Address
|
|
||||||
|
|
||||||
NoMail bool
|
|
||||||
MailSMTPHost string
|
|
||||||
MailSMTPPort uint
|
|
||||||
MailSMTPUsername string
|
|
||||||
MailSMTPPassword string
|
|
||||||
MailSMTPTLSSNoVerify bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBaseURL returns the full url to the absolute ExternalURL, including BaseURL.
|
|
||||||
func (o *Options) GetBaseURL() string {
|
|
||||||
return fmt.Sprintf("%s%s", o.ExternalURL.URL.String(), o.baseURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBasePath returns the baseURL.
|
|
||||||
func (o *Options) GetBasePath() string {
|
|
||||||
return o.baseURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsolidateConfig fills an Options struct by reading configuration from
|
// ConsolidateConfig fills an Options struct by reading configuration from
|
||||||
// config files, environment, then command line.
|
// config files, environment, then command line.
|
||||||
//
|
//
|
||||||
// Should be called only one time.
|
// Should be called only one time.
|
||||||
func ConsolidateConfig() (opts *Options, err error) {
|
func ConsolidateConfig() (opts *happydns.Options, err error) {
|
||||||
u, _ := url.Parse("http://localhost:8081")
|
u, _ := url.Parse("http://localhost:8081")
|
||||||
|
|
||||||
// Define defaults options
|
// Define defaults options
|
||||||
opts = &Options{
|
opts = &happydns.Options{
|
||||||
AdminBind: "./happydomain.sock",
|
AdminBind: "./happydomain.sock",
|
||||||
baseURL: "/",
|
BasePath: "/",
|
||||||
Bind: ":8081",
|
Bind: ":8081",
|
||||||
DefaultNameServer: "127.0.0.1:53",
|
DefaultNameServer: "127.0.0.1:53",
|
||||||
ExternalURL: URL{URL: u},
|
ExternalURL: *u,
|
||||||
JWTSigningMethod: "HS512",
|
JWTSigningMethod: "HS512",
|
||||||
MailFrom: mail.Address{Name: "happyDomain", Address: "happydomain@localhost"},
|
MailFrom: mail.Address{Name: "happyDomain", Address: "happydomain@localhost"},
|
||||||
MailSMTPPort: 587,
|
MailSMTPPort: 587,
|
||||||
StorageEngine: storage.StorageEngine("leveldb"),
|
StorageEngine: "leveldb",
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.declareFlags()
|
declareFlags(opts)
|
||||||
|
|
||||||
// Establish a list of possible configuration file locations
|
// Establish a list of possible configuration file locations
|
||||||
configLocations := []string{
|
configLocations := []string{
|
||||||
|
@ -146,7 +72,7 @@ func ConsolidateConfig() (opts *Options, err error) {
|
||||||
for _, filename := range configLocations {
|
for _, filename := range configLocations {
|
||||||
if _, e := os.Stat(filename); !os.IsNotExist(e) {
|
if _, e := os.Stat(filename); !os.IsNotExist(e) {
|
||||||
log.Printf("Loading configuration from %s\n", filename)
|
log.Printf("Loading configuration from %s\n", filename)
|
||||||
err = opts.parseFile(filename)
|
err = parseFile(opts, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -155,22 +81,22 @@ func ConsolidateConfig() (opts *Options, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, overwrite that by what is present in the environment
|
// Then, overwrite that by what is present in the environment
|
||||||
err = opts.parseEnvironmentVariables()
|
err = parseEnvironmentVariables(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finaly, command line takes precedence
|
// Finaly, command line takes precedence
|
||||||
err = opts.parseCLI()
|
err = parseCLI(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize options
|
// Sanitize options
|
||||||
if opts.baseURL != "/" {
|
if opts.BasePath != "/" {
|
||||||
opts.baseURL = path.Clean(opts.baseURL)
|
opts.BasePath = path.Clean(opts.BasePath)
|
||||||
} else {
|
} else {
|
||||||
opts.baseURL = ""
|
opts.BasePath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.NoMail && opts.MailSMTPHost != "" {
|
if opts.NoMail && opts.MailSMTPHost != "" {
|
||||||
|
@ -178,26 +104,26 @@ func ConsolidateConfig() (opts *Options, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ExternalURL.URL.Host == "" || opts.ExternalURL.URL.Scheme == "" {
|
if opts.ExternalURL.Host == "" || opts.ExternalURL.Scheme == "" {
|
||||||
u, err2 := url.Parse("http://" + opts.ExternalURL.URL.String())
|
u, err2 := url.Parse("http://" + opts.ExternalURL.String())
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
opts.ExternalURL.URL = u
|
opts.ExternalURL = *u
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("You defined an external URL without a scheme. The expected value is eg. http://localhost:8081")
|
err = fmt.Errorf("You defined an external URL without a scheme. The expected value is eg. http://localhost:8081")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(opts.ExternalURL.URL.Path) > 1 {
|
if len(opts.ExternalURL.Path) > 1 {
|
||||||
if opts.baseURL != "" && opts.baseURL != opts.ExternalURL.URL.Path {
|
if opts.BasePath != "" && opts.BasePath != opts.ExternalURL.Path {
|
||||||
err = fmt.Errorf("You defined both baseurl and a path to externalurl that are different. Define only one of those.")
|
err = fmt.Errorf("You defined both baseurl and a path to externalurl that are different. Define only one of those.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.baseURL = path.Clean(opts.ExternalURL.URL.Path)
|
opts.BasePath = path.Clean(opts.ExternalURL.Path)
|
||||||
}
|
}
|
||||||
opts.ExternalURL.URL.Path = ""
|
opts.ExternalURL.Path = ""
|
||||||
opts.ExternalURL.URL.Fragment = ""
|
opts.ExternalURL.Fragment = ""
|
||||||
opts.ExternalURL.URL.RawQuery = ""
|
opts.ExternalURL.RawQuery = ""
|
||||||
|
|
||||||
if len(opts.JWTSecretKey) == 0 {
|
if len(opts.JWTSecretKey) == 0 {
|
||||||
opts.JWTSecretKey = make([]byte, 32)
|
opts.JWTSecretKey = make([]byte, 32)
|
||||||
|
@ -207,12 +133,17 @@ func ConsolidateConfig() (opts *Options, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = ExtendsConfigWithOIDC(opts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLine treats a config line and place the read value in the variable
|
// parseLine treats a config line and place the read value in the variable
|
||||||
// declared to the corresponding flag.
|
// declared to the corresponding flag.
|
||||||
func (o *Options) parseLine(line string) (err error) {
|
func parseLine(o *happydns.Options, line string) (err error) {
|
||||||
fields := strings.SplitN(line, "=", 2)
|
fields := strings.SplitN(line, "=", 2)
|
||||||
orig_key := strings.TrimSpace(fields[0])
|
orig_key := strings.TrimSpace(fields[0])
|
||||||
value := strings.TrimSpace(fields[1])
|
value := strings.TrimSpace(fields[1])
|
||||||
|
|
|
@ -19,18 +19,20 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package config // import "git.happydns.org/happyDomain/config"
|
package config // import "git.happydns.org/happyDomain/internal/config"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseLine(t *testing.T) {
|
func TestParseLine(t *testing.T) {
|
||||||
cfg := Options{}
|
cfg := &happydns.Options{}
|
||||||
cfg.declareFlags()
|
declareFlags(cfg)
|
||||||
|
|
||||||
err := cfg.parseLine("HAPPYDOMAIN_BIND=:8080")
|
err := parseLine(cfg, "HAPPYDOMAIN_BIND=:8080")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`parseLine("BIND=:8080") => %v`, err.Error())
|
t.Fatalf(`parseLine("BIND=:8080") => %v`, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -38,30 +40,30 @@ func TestParseLine(t *testing.T) {
|
||||||
t.Fatalf(`parseLine("BIND=:8080") = %q, want ":8080"`, cfg.Bind)
|
t.Fatalf(`parseLine("BIND=:8080") = %q, want ":8080"`, cfg.Bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cfg.parseLine("BASEURL=/base")
|
err = parseLine(cfg, "BASEURL=/base")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`parseLine("BASEURL=/base") => %v`, err.Error())
|
t.Fatalf(`parseLine("BASEURL=/base") => %v`, err.Error())
|
||||||
}
|
}
|
||||||
if cfg.baseURL != "/base" {
|
if cfg.BasePath != "/base" {
|
||||||
t.Fatalf(`parseLine("BASEURL=/base") = %q, want "/base"`, cfg.baseURL)
|
t.Fatalf(`parseLine("BASEURL=/base") = %q, want "/base"`, cfg.BasePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.parseLine("EXTERNALURL=https://happydomain.org")
|
parseLine(cfg, "EXTERNALURL=https://happydomain.org")
|
||||||
if cfg.ExternalURL.String() != "https://happydomain.org" {
|
if cfg.ExternalURL.String() != "https://happydomain.org" {
|
||||||
t.Fatalf(`parseLine("EXTERNAL_URL=https://happydomain.org") = %q, want "https://happydomain.org"`, cfg.ExternalURL)
|
t.Fatalf(`parseLine("EXTERNAL_URL=https://happydomain.org") = %q, want "https://happydomain.org"`, cfg.ExternalURL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.parseLine("DEFAULT-NS=42.42.42.42:5353")
|
parseLine(cfg, "DEFAULT-NS=42.42.42.42:5353")
|
||||||
if cfg.DefaultNameServer != "42.42.42.42:5353" {
|
if cfg.DefaultNameServer != "42.42.42.42:5353" {
|
||||||
t.Fatalf(`parseLine("DEFAULT-NS=42.42.42.42:5353") = %q, want "42.42.42.42:5353"`, cfg.DefaultNameServer)
|
t.Fatalf(`parseLine("DEFAULT-NS=42.42.42.42:5353") = %q, want "42.42.42.42:5353"`, cfg.DefaultNameServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.parseLine("DEFAULT_NS=42.42.42.42:3535")
|
parseLine(cfg, "DEFAULT_NS=42.42.42.42:3535")
|
||||||
if cfg.DefaultNameServer != "42.42.42.42:3535" {
|
if cfg.DefaultNameServer != "42.42.42.42:3535" {
|
||||||
t.Fatalf(`parseLine("DEFAULT_NS=42.42.42.42:3535") = %q, want "42.42.42.42:3535"`, cfg.DefaultNameServer)
|
t.Fatalf(`parseLine("DEFAULT_NS=42.42.42.42:3535") = %q, want "42.42.42.42:3535"`, cfg.DefaultNameServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cfg.parseLine("NO_AUTH=true")
|
err = parseLine(cfg, "NO_AUTH=true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`parseLine("NO_AUTH=true") => %v`, err.Error())
|
t.Fatalf(`parseLine("NO_AUTH=true") => %v`, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -73,8 +75,8 @@ func TestParseLine(t *testing.T) {
|
||||||
func TestGetBaseURL(t *testing.T) {
|
func TestGetBaseURL(t *testing.T) {
|
||||||
u, _ := url.Parse("http://localhost:8081")
|
u, _ := url.Parse("http://localhost:8081")
|
||||||
|
|
||||||
cfg := Options{
|
cfg := &happydns.Options{
|
||||||
ExternalURL: URL{URL: u},
|
ExternalURL: *u,
|
||||||
}
|
}
|
||||||
|
|
||||||
builded_url := cfg.GetBaseURL()
|
builded_url := cfg.GetBaseURL()
|
||||||
|
@ -82,7 +84,7 @@ func TestGetBaseURL(t *testing.T) {
|
||||||
t.Fatalf(`GetBaseURL() = %q, want "http://localhost:8081"`, builded_url)
|
t.Fatalf(`GetBaseURL() = %q, want "http://localhost:8081"`, builded_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.baseURL = "/base"
|
cfg.BasePath = "/base"
|
||||||
|
|
||||||
builded_url = cfg.GetBaseURL()
|
builded_url = cfg.GetBaseURL()
|
||||||
if builded_url != "http://localhost:8081/base" {
|
if builded_url != "http://localhost:8081/base" {
|
||||||
|
|
|
@ -27,10 +27,12 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JWTSecretKey []byte
|
type JWTSecretKey struct {
|
||||||
|
Secret *[]byte
|
||||||
|
}
|
||||||
|
|
||||||
func (i *JWTSecretKey) String() string {
|
func (i *JWTSecretKey) String() string {
|
||||||
return base64.StdEncoding.EncodeToString(*i)
|
return base64.StdEncoding.EncodeToString(*i.Secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *JWTSecretKey) Set(value string) error {
|
func (i *JWTSecretKey) Set(value string) error {
|
||||||
|
@ -39,7 +41,7 @@ func (i *JWTSecretKey) Set(value string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*i = z
|
*i.Secret = z
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +85,6 @@ func (i *URL) Set(value string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.URL = u
|
*i.URL = *u
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseEnvironmentVariables analyzes all the environment variables to find
|
// parseEnvironmentVariables analyzes all the environment variables to find
|
||||||
// each one starting by HAPPYDOMAIN_
|
// each one starting by HAPPYDOMAIN_
|
||||||
func (o *Options) parseEnvironmentVariables() (err error) {
|
func parseEnvironmentVariables(o *happydns.Options) (err error) {
|
||||||
for _, line := range os.Environ() {
|
for _, line := range os.Environ() {
|
||||||
if strings.HasPrefix(line, "HAPPYDOMAIN_") || strings.HasPrefix(line, "HAPPYDNS_") {
|
if strings.HasPrefix(line, "HAPPYDOMAIN_") || strings.HasPrefix(line, "HAPPYDNS_") {
|
||||||
err := o.parseLine(line)
|
err := parseLine(o, line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error in environment (%q): %w", line, err)
|
return fmt.Errorf("error in environment (%q): %w", line, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseFile opens the file at the given filename path, then treat each line
|
// parseFile opens the file at the given filename path, then treat each line
|
||||||
// not starting with '#' as a configuration statement.
|
// not starting with '#' as a configuration statement.
|
||||||
func (o *Options) parseFile(filename string) error {
|
func parseFile(o *happydns.Options, filename string) error {
|
||||||
fp, err := os.Open(filename)
|
fp, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -43,7 +45,7 @@ func (o *Options) parseFile(filename string) error {
|
||||||
n += 1
|
n += 1
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
|
if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Index(line, "=") > 0 {
|
||||||
err := o.parseLine(line)
|
err := parseLine(o, line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%v:%d: error in configuration: %w", filename, n, err)
|
return fmt.Errorf("%v:%d: error in configuration: %w", filename, n, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
func (o *Options) GetOIDCProviderURL() string {
|
import (
|
||||||
return ""
|
"git.happydns.org/happyDomain/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExtendsConfigWithOIDC(o *happydns.Options) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,50 +24,32 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"flag"
|
"flag"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"git.happydns.org/happyDomain/model"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
OIDCClientID string
|
oidcClientID string
|
||||||
oidcClientSecret string
|
oidcClientSecret string
|
||||||
OIDCProviderURL string
|
oidcProviderURL url.URL
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&OIDCClientID, "oidc-client-id", OIDCClientID, "ClientID for OIDC")
|
flag.StringVar(&oidcClientID, "oidc-client-id", oidcClientID, "ClientID for OIDC")
|
||||||
flag.StringVar(&oidcClientSecret, "oidc-client-secret", oidcClientSecret, "Secret for OIDC")
|
flag.StringVar(&oidcClientSecret, "oidc-client-secret", oidcClientSecret, "Secret for OIDC")
|
||||||
flag.StringVar(&OIDCProviderURL, "oidc-provider-url", OIDCProviderURL, "Base URL of the OpenId Connect service")
|
flag.Var(&URL{&oidcProviderURL}, "oidc-provider-url", "Base URL of the OpenId Connect service")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) GetAuthURL() *url.URL {
|
func ExtendsConfigWithOIDC(o *happydns.Options) error {
|
||||||
redirecturl := *o.ExternalURL.URL
|
if oidcProviderURL.String() != "" {
|
||||||
redirecturl.Path = path.Join(redirecturl.Path, o.baseURL, "auth", "callback")
|
o.OIDCClients = append(o.OIDCClients, happydns.OIDCSettings{
|
||||||
return &redirecturl
|
ClientID: oidcClientID,
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Options) GetOIDCProvider(ctx context.Context) (*oidc.Provider, error) {
|
|
||||||
return oidc.NewProvider(ctx, strings.TrimSuffix(OIDCProviderURL, "/.well-known/openid-configuration"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Options) GetOIDCProviderURL() string {
|
|
||||||
return OIDCProviderURL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Options) GetOAuth2Config(provider *oidc.Provider) *oauth2.Config {
|
|
||||||
oauth2Config := oauth2.Config{
|
|
||||||
ClientID: OIDCClientID,
|
|
||||||
ClientSecret: oidcClientSecret,
|
ClientSecret: oidcClientSecret,
|
||||||
RedirectURL: o.GetAuthURL().String(),
|
ProviderURL: oidcProviderURL,
|
||||||
Endpoint: provider.Endpoint(),
|
})
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &oauth2Config
|
return nil
|
||||||
}
|
}
|
||||||
|
|
82
internal/config/oidc_test.go
Normal file
82
internal/config/oidc_test.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// This file is part of the happyDomain (R) project.
|
||||||
|
// Copyright (c) 2020-2025 happyDomain
|
||||||
|
// Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
//
|
||||||
|
// This program is offered under a commercial and under the AGPL license.
|
||||||
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
//
|
||||||
|
// For AGPL licensing:
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build !nooidc
|
||||||
|
|
||||||
|
package config // import "git.happydns.org/happyDomain/internal/config"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.happydns.org/happyDomain/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOIDCConfig(t *testing.T) {
|
||||||
|
cfg := &happydns.Options{}
|
||||||
|
|
||||||
|
err := parseLine(cfg, "HAPPYDOMAIN_OIDC_CLIENT_ID=test-oidc-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_ID=test-oidc-1") => %v`, err.Error())
|
||||||
|
}
|
||||||
|
if oidcClientID != "test-oidc-1" {
|
||||||
|
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_ID=test-oidc-1") = %q, want "test-oidc-1"`, oidcClientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parseLine(cfg, "HAPPYDOMAIN_OIDC_CLIENT_SECRET=s3cret$")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_SECRET=s3cret$") => %v`, err.Error())
|
||||||
|
}
|
||||||
|
if oidcClientSecret != "s3cret$" {
|
||||||
|
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_CLIENT_SECRET=s3cret$") = %q, want "s3cret$"`, oidcClientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oidcProviderURL.String() != "" {
|
||||||
|
t.Fatalf(`before parseLine("HAPPYDOMAIN_OIDC_PROVIDER_URL") = %q, want ""`, oidcProviderURL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = parseLine(cfg, "HAPPYDOMAIN_OIDC_PROVIDER_URL=https://localhost:12345/secret")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_PROVIDER_URL=https://localhost:12345/secret") => %v`, err.Error())
|
||||||
|
}
|
||||||
|
if oidcProviderURL.String() != "https://localhost:12345/secret" {
|
||||||
|
t.Fatalf(`parseLine("HAPPYDOMAIN_OIDC_PROVIDER_URL=https://localhost:12345/secret") = %q, want "https://localhost:12345/secret"`, cfg.Bind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test extended config
|
||||||
|
err = ExtendsConfigWithOIDC(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`ExtendsConfigWithOIDC(cfg) => %v`, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.OIDCClients) != 1 {
|
||||||
|
t.Fatalf(`len(cfg.OIDCClients) == %d, should be 1`, len(cfg.OIDCClients))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.OIDCClients[0].ClientID != "test-oidc-1" {
|
||||||
|
t.Fatalf(`cfg.OIDCClients[0].ClientID == %q, should be test-oidc-1`, cfg.OIDCClients[0].ClientID)
|
||||||
|
}
|
||||||
|
if cfg.OIDCClients[0].ClientSecret != "s3cret$" {
|
||||||
|
t.Fatalf(`cfg.OIDCClients[0].ClientSecret == %q, should be test-oidc-1`, cfg.OIDCClients[0].ClientSecret)
|
||||||
|
}
|
||||||
|
if cfg.OIDCClients[0].ProviderURL.String() != "https://localhost:12345/secret" {
|
||||||
|
t.Fatalf(`cfg.OIDCClients[0].ProviderURL == %q, should be https://localhost:12345/secret`, cfg.OIDCClients[0].ProviderURL.String())
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/mileusna/useragent"
|
"github.com/mileusna/useragent"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
@ -47,13 +46,13 @@ type SessionStore struct {
|
||||||
storage storage.Storage
|
storage storage.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionStore(opts *config.Options, storage storage.Storage, keyPairs ...[]byte) *SessionStore {
|
func NewSessionStore(opts *happydns.Options, storage storage.Storage, keyPairs ...[]byte) *SessionStore {
|
||||||
store := &SessionStore{
|
store := &SessionStore{
|
||||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||||
options: &sessions.Options{
|
options: &sessions.Options{
|
||||||
Path: opts.GetBasePath() + "/",
|
Path: opts.BasePath + "/",
|
||||||
MaxAge: 86400 * 30,
|
MaxAge: 86400 * 30,
|
||||||
Secure: opts.DevProxy == "" && opts.ExternalURL.URL.Scheme != "http",
|
Secure: opts.DevProxy == "" && opts.ExternalURL.Scheme != "http",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,11 +34,10 @@ func (i *StorageEngine) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *StorageEngine) Set(value string) (err error) {
|
func (i *StorageEngine) Set(value string) (err error) {
|
||||||
se := StorageEngine(value)
|
if _, ok := StorageEngines[value]; !ok {
|
||||||
if _, ok := StorageEngines[se]; !ok {
|
|
||||||
return fmt.Errorf("Unexistant storage engine: please select one between: %v", GetStorageEngines())
|
return fmt.Errorf("Unexistant storage engine: please select one between: %v", GetStorageEngines())
|
||||||
}
|
}
|
||||||
*i = se
|
*i = StorageEngine(value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +47,7 @@ type StorageInstanciation func() (Storage, error)
|
||||||
|
|
||||||
// StorageEngines lists all Storage implementations declared, with a
|
// StorageEngines lists all Storage implementations declared, with a
|
||||||
// way to instanciate automatically each.
|
// way to instanciate automatically each.
|
||||||
var StorageEngines = map[StorageEngine]StorageInstanciation{}
|
var StorageEngines = map[string]StorageInstanciation{}
|
||||||
|
|
||||||
// GetStorageEngines returns all declared Storage implementation.
|
// GetStorageEngines returns all declared Storage implementation.
|
||||||
func GetStorageEngines() (se []string) {
|
func GetStorageEngines() (se []string) {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/helpers"
|
"git.happydns.org/happyDomain/internal/helpers"
|
||||||
"git.happydns.org/happyDomain/internal/mailer"
|
"git.happydns.org/happyDomain/internal/mailer"
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
|
@ -35,12 +34,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type authUserUsecase struct {
|
type authUserUsecase struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
mailer *mailer.Mailer
|
mailer *mailer.Mailer
|
||||||
store storage.AuthUserAndSessionStorage
|
store storage.AuthUserAndSessionStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthUserUsecase(cfg *config.Options, m *mailer.Mailer, store storage.AuthUserAndSessionStorage) happydns.AuthUserUsecase {
|
func NewAuthUserUsecase(cfg *happydns.Options, m *mailer.Mailer, store storage.AuthUserAndSessionStorage) happydns.AuthUserUsecase {
|
||||||
return &authUserUsecase{
|
return &authUserUsecase{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
mailer: m,
|
mailer: m,
|
||||||
|
|
|
@ -25,18 +25,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginUsecase struct {
|
type loginUsecase struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
store storage.AuthenticationStorage
|
store storage.AuthenticationStorage
|
||||||
userService happydns.UserUsecase
|
userService happydns.UserUsecase
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthenticationUsecase(cfg *config.Options, store storage.AuthenticationStorage, userService happydns.UserUsecase) happydns.AuthenticationUsecase {
|
func NewAuthenticationUsecase(cfg *happydns.Options, store storage.AuthenticationStorage, userService happydns.UserUsecase) happydns.AuthenticationUsecase {
|
||||||
return &loginUsecase{
|
return &loginUsecase{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
store: store,
|
store: store,
|
||||||
|
|
|
@ -22,15 +22,14 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type formUsecase struct {
|
type formUsecase struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFormUsecase(cfg *config.Options) happydns.FormUsecase {
|
func NewFormUsecase(cfg *happydns.Options) happydns.FormUsecase {
|
||||||
return &formUsecase{
|
return &formUsecase{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,19 +24,18 @@ package usecase
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/forms"
|
"git.happydns.org/happyDomain/internal/forms"
|
||||||
"git.happydns.org/happyDomain/internal/usecase/provider"
|
"git.happydns.org/happyDomain/internal/usecase/provider"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type providerSettingsUsecase struct {
|
type providerSettingsUsecase struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
providerService happydns.ProviderUsecase
|
providerService happydns.ProviderUsecase
|
||||||
store provider.ProviderStorage
|
store provider.ProviderStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviderSettingsUsecase(cfg *config.Options, ps happydns.ProviderUsecase, store provider.ProviderStorage) happydns.ProviderSettingsUsecase {
|
func NewProviderSettingsUsecase(cfg *happydns.Options, ps happydns.ProviderUsecase, store provider.ProviderStorage) happydns.ProviderSettingsUsecase {
|
||||||
return &providerSettingsUsecase{
|
return &providerSettingsUsecase{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
providerService: ps,
|
providerService: ps,
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/internal/storage"
|
"git.happydns.org/happyDomain/internal/storage"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
"git.happydns.org/happyDomain/providers"
|
"git.happydns.org/happyDomain/providers"
|
||||||
|
@ -33,10 +32,10 @@ import (
|
||||||
|
|
||||||
type providerUsecase struct {
|
type providerUsecase struct {
|
||||||
happydns.ProviderUsecase
|
happydns.ProviderUsecase
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviderUsecase(cfg *config.Options, store storage.ProviderAndDomainStorage) happydns.ProviderUsecase {
|
func NewProviderUsecase(cfg *happydns.Options, store storage.ProviderAndDomainStorage) happydns.ProviderUsecase {
|
||||||
return &providerUsecase{
|
return &providerUsecase{
|
||||||
ProviderUsecase: NewAdminProviderUsecase(store),
|
ProviderUsecase: NewAdminProviderUsecase(store),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
|
|
@ -30,7 +30,6 @@ import (
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,10 +38,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type resolverUsecase struct {
|
type resolverUsecase struct {
|
||||||
config *config.Options
|
config *happydns.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolverUsecase(cfg *config.Options) happydns.ResolverUsecase {
|
func NewResolverUsecase(cfg *happydns.Options) happydns.ResolverUsecase {
|
||||||
return &resolverUsecase{
|
return &resolverUsecase{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
}
|
}
|
||||||
|
|
106
model/config.go
Normal file
106
model/config.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// This file is part of the happyDomain (R) project.
|
||||||
|
// Copyright (c) 2020-2025 happyDomain
|
||||||
|
// Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
//
|
||||||
|
// This program is offered under a commercial and under the AGPL license.
|
||||||
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
//
|
||||||
|
// For AGPL licensing:
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package happydns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options stores the configuration of the software.
|
||||||
|
type Options struct {
|
||||||
|
// AdminBind is the address:port or unix socket used to serve the admin
|
||||||
|
// API.
|
||||||
|
AdminBind string
|
||||||
|
|
||||||
|
// Bind is the address:port used to bind the main interface with API.
|
||||||
|
Bind string
|
||||||
|
|
||||||
|
// BasePath is the relative path where begins the root of the app.
|
||||||
|
BasePath string
|
||||||
|
|
||||||
|
// DevProxy is the URL that override static assets.
|
||||||
|
DevProxy string
|
||||||
|
|
||||||
|
// DefaultNameServer is the NS server suggested by default.
|
||||||
|
DefaultNameServer string
|
||||||
|
|
||||||
|
// DisableProviders should disallow all actions on provider (add/edit/delete) through public API.
|
||||||
|
DisableProviders bool
|
||||||
|
|
||||||
|
// DisableRegistration forbids all new registration using the public form/API.
|
||||||
|
DisableRegistration bool
|
||||||
|
|
||||||
|
// DisableEmbeddedLogin disables the internal user/password login in favor of ExternalAuth or OIDC.
|
||||||
|
DisableEmbeddedLogin bool
|
||||||
|
|
||||||
|
// ExternalAuth is the URL of the login form to use instead of the embedded one.
|
||||||
|
ExternalAuth url.URL
|
||||||
|
|
||||||
|
// ExternalURL keeps the URL used in communications (such as email,
|
||||||
|
// ...), when it needs to use complete URL, not only relative parts.
|
||||||
|
ExternalURL url.URL
|
||||||
|
|
||||||
|
// JWTSecretKey stores the private key to sign and verify JWT tokens.
|
||||||
|
JWTSecretKey []byte
|
||||||
|
|
||||||
|
// JWTSigningMethod is the signing method to check token signature.
|
||||||
|
JWTSigningMethod string
|
||||||
|
|
||||||
|
// NoAuth controls if there is user access control or not.
|
||||||
|
NoAuth bool
|
||||||
|
|
||||||
|
// OptOutInsights disable the anonymous usage statistics report.
|
||||||
|
OptOutInsights bool
|
||||||
|
|
||||||
|
// StorageEngine points to the storage engine used.
|
||||||
|
StorageEngine string
|
||||||
|
|
||||||
|
ListmonkURL url.URL
|
||||||
|
ListmonkId int
|
||||||
|
|
||||||
|
// MailFrom holds the content of the From field for all e-mails that
|
||||||
|
// will be send.
|
||||||
|
MailFrom mail.Address
|
||||||
|
|
||||||
|
NoMail bool
|
||||||
|
MailSMTPHost string
|
||||||
|
MailSMTPPort uint
|
||||||
|
MailSMTPUsername string
|
||||||
|
MailSMTPPassword string
|
||||||
|
MailSMTPTLSSNoVerify bool
|
||||||
|
|
||||||
|
OIDCClients []OIDCSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBaseURL returns the full url to the absolute ExternalURL, including BaseURL.
|
||||||
|
func (o *Options) GetBaseURL() string {
|
||||||
|
return fmt.Sprintf("%s%s", o.ExternalURL.String(), o.BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) GetAuthURL() *url.URL {
|
||||||
|
redirecturl := o.ExternalURL
|
||||||
|
redirecturl.Path = path.Join(redirecturl.Path, o.BasePath, "auth", "callback")
|
||||||
|
return &redirecturl
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ type Insights struct {
|
||||||
HasExternalAuth bool `json:"hasExternalAuth,omitempty"`
|
HasExternalAuth bool `json:"hasExternalAuth,omitempty"`
|
||||||
HasListmonkURL bool `json:"hasListmonkURL,omitempty"`
|
HasListmonkURL bool `json:"hasListmonkURL,omitempty"`
|
||||||
LocalBind bool `json:"localBind,omitempty"`
|
LocalBind bool `json:"localBind,omitempty"`
|
||||||
|
NbOidcProviders int `json:"nbOidcProviders,omitempty"`
|
||||||
NoAuthActive bool `json:"noAuthActive,omitempty"`
|
NoAuthActive bool `json:"noAuthActive,omitempty"`
|
||||||
NoMail bool `json:"noMail,omitempty"`
|
NoMail bool `json:"noMail,omitempty"`
|
||||||
NonUnixAdminBind bool `json:"nonUnixAdminBind,omitempty"`
|
NonUnixAdminBind bool `json:"nonUnixAdminBind,omitempty"`
|
||||||
|
|
32
model/oidc.go
Normal file
32
model/oidc.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// This file is part of the happyDomain (R) project.
|
||||||
|
// Copyright (c) 2020-2024 happyDomain
|
||||||
|
// Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
//
|
||||||
|
// This program is offered under a commercial and under the AGPL license.
|
||||||
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
//
|
||||||
|
// For AGPL licensing:
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package happydns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OIDCSettings struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
ProviderURL url.URL
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/internal/config"
|
"git.happydns.org/happyDomain/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -55,7 +55,7 @@ func init() {
|
||||||
flag.StringVar(&MsgHeaderColor, "msg-header-color", MsgHeaderColor, "Background color of the banner added at the top of the app")
|
flag.StringVar(&MsgHeaderColor, "msg-header-color", MsgHeaderColor, "Background color of the banner added at the top of the app")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeclareRoutes(cfg *config.Options, router *gin.Engine) {
|
func DeclareRoutes(cfg *happydns.Options, router *gin.Engine) {
|
||||||
if cfg.DisableProviders {
|
if cfg.DisableProviders {
|
||||||
CustomHeadHTML += `<script type="text/javascript">window.disable_providers = true;</script>`
|
CustomHeadHTML += `<script type="text/javascript">window.disable_providers = true;</script>`
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine) {
|
||||||
CustomHeadHTML += `<script type="text/javascript">window.disable_embedded_login = true;</script>`
|
CustomHeadHTML += `<script type="text/javascript">window.disable_embedded_login = true;</script>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.OIDCProviderURL != "" {
|
if len(cfg.OIDCClients) > 0 {
|
||||||
CustomHeadHTML += `<script type="text/javascript">window.oidc_configured = true;</script>`
|
CustomHeadHTML += `<script type="text/javascript">window.oidc_configured = true;</script>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveOrReverse(forced_url string, cfg *config.Options) gin.HandlerFunc {
|
func serveOrReverse(forced_url string, cfg *happydns.Options) gin.HandlerFunc {
|
||||||
if cfg.DevProxy != "" {
|
if cfg.DevProxy != "" {
|
||||||
// Forward to the Vue dev proxy
|
// Forward to the Vue dev proxy
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue