chore: use interfaces for all other services

This commit is contained in:
Christoph Haas
2025-03-23 23:09:47 +01:00
parent 02ed7b19df
commit 7d0da4e7ad
40 changed files with 1337 additions and 406 deletions

View File

@@ -14,25 +14,78 @@ import (
"time"
"github.com/coreos/go-oidc/v3/oidc"
evbus "github.com/vardius/message-bus"
"golang.org/x/oauth2"
"github.com/h44z/wg-portal/internal/app"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
)
// region dependencies
type UserManager interface {
// GetUser returns a user by its identifier.
GetUser(context.Context, domain.UserIdentifier) (*domain.User, error)
// RegisterUser creates a new user in the database.
RegisterUser(ctx context.Context, user *domain.User) error
// UpdateUser updates an existing user in the database.
UpdateUser(ctx context.Context, user *domain.User) (*domain.User, error)
}
type EventBus interface {
// Publish sends a message to the message bus.
Publish(topic string, args ...any)
}
// endregion dependencies
type AuthenticatorType string
const (
AuthenticatorTypeOAuth AuthenticatorType = "oauth"
AuthenticatorTypeOidc AuthenticatorType = "oidc"
)
// AuthenticatorOauth is the interface for all OAuth authenticators.
type AuthenticatorOauth interface {
// GetName returns the name of the authenticator.
GetName() string
// GetType returns the type of the authenticator. It can be either AuthenticatorTypeOAuth or AuthenticatorTypeOidc.
GetType() AuthenticatorType
// AuthCodeURL returns the URL for the authentication flow.
AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string
// Exchange exchanges the OAuth code for an access token.
Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
// GetUserInfo fetches the user information from the OAuth or OIDC provider.
GetUserInfo(ctx context.Context, token *oauth2.Token, nonce string) (map[string]any, error)
// ParseUserInfo parses the raw user information into a domain.AuthenticatorUserInfo struct.
ParseUserInfo(raw map[string]any) (*domain.AuthenticatorUserInfo, error)
// RegistrationEnabled returns whether registration is enabled for the OAuth authenticator.
RegistrationEnabled() bool
}
// AuthenticatorLdap is the interface for all LDAP authenticators.
type AuthenticatorLdap interface {
// GetName returns the name of the authenticator.
GetName() string
// PlaintextAuthentication performs a plaintext authentication against the LDAP server.
PlaintextAuthentication(userId domain.UserIdentifier, plainPassword string) error
// GetUserInfo fetches the user information from the LDAP server.
GetUserInfo(ctx context.Context, username domain.UserIdentifier) (map[string]any, error)
// ParseUserInfo parses the raw user information into a domain.AuthenticatorUserInfo struct.
ParseUserInfo(raw map[string]any) (*domain.AuthenticatorUserInfo, error)
// RegistrationEnabled returns whether registration is enabled for the LDAP authenticator.
RegistrationEnabled() bool
}
// Authenticator is the main entry point for all authentication related tasks.
// This includes password authentication and external authentication providers (OIDC, OAuth, LDAP).
type Authenticator struct {
cfg *config.Auth
bus evbus.MessageBus
bus EventBus
oauthAuthenticators map[string]domain.OauthAuthenticator
ldapAuthenticators map[string]domain.LdapAuthenticator
oauthAuthenticators map[string]AuthenticatorOauth
ldapAuthenticators map[string]AuthenticatorLdap
// URL prefix for the callback endpoints, this is a combination of the external URL and the API prefix
callbackUrlPrefix string
@@ -40,7 +93,8 @@ type Authenticator struct {
users UserManager
}
func NewAuthenticator(cfg *config.Auth, extUrl string, bus evbus.MessageBus, users UserManager) (
// NewAuthenticator creates a new Authenticator instance.
func NewAuthenticator(cfg *config.Auth, extUrl string, bus EventBus, users UserManager) (
*Authenticator,
error,
) {
@@ -68,8 +122,8 @@ func (a *Authenticator) setupExternalAuthProviders(ctx context.Context) error {
return fmt.Errorf("failed to parse external url: %w", err)
}
a.oauthAuthenticators = make(map[string]domain.OauthAuthenticator, len(a.cfg.OpenIDConnect)+len(a.cfg.OAuth))
a.ldapAuthenticators = make(map[string]domain.LdapAuthenticator, len(a.cfg.Ldap))
a.oauthAuthenticators = make(map[string]AuthenticatorOauth, len(a.cfg.OpenIDConnect)+len(a.cfg.OAuth))
a.ldapAuthenticators = make(map[string]AuthenticatorLdap, len(a.cfg.Ldap))
for i := range a.cfg.OpenIDConnect { // OIDC
providerCfg := &a.cfg.OpenIDConnect[i]
@@ -123,6 +177,7 @@ func (a *Authenticator) setupExternalAuthProviders(ctx context.Context) error {
return nil
}
// GetExternalLoginProviders returns a list of all available external login providers.
func (a *Authenticator) GetExternalLoginProviders(_ context.Context) []domain.LoginProviderInfo {
authProviders := make([]domain.LoginProviderInfo, 0, len(a.cfg.OAuth)+len(a.cfg.OpenIDConnect))
@@ -157,6 +212,7 @@ func (a *Authenticator) GetExternalLoginProviders(_ context.Context) []domain.Lo
return authProviders
}
// IsUserValid checks if a user is valid and not locked or disabled.
func (a *Authenticator) IsUserValid(ctx context.Context, id domain.UserIdentifier) bool {
ctx = domain.SetUserInfo(ctx, domain.SystemAdminContextUserInfo()) // switch to admin user context
user, err := a.users.GetUser(ctx, id)
@@ -177,6 +233,8 @@ func (a *Authenticator) IsUserValid(ctx context.Context, id domain.UserIdentifie
// region password authentication
// PlainLogin performs a password authentication for a user. The username and password are trimmed before usage.
// If the login is successful, the user is returned, otherwise an error.
func (a *Authenticator) PlainLogin(ctx context.Context, username, password string) (*domain.User, error) {
// Validate form input
username = strings.TrimSpace(username)
@@ -204,7 +262,7 @@ func (a *Authenticator) passwordAuthentication(
domain.SystemAdminContextUserInfo()) // switch to admin user context to check if user exists
var ldapUserInfo *domain.AuthenticatorUserInfo
var ldapProvider domain.LdapAuthenticator
var ldapProvider AuthenticatorLdap
var userInDatabase = false
var userSource domain.UserSource
@@ -280,6 +338,7 @@ func (a *Authenticator) passwordAuthentication(
// region oauth authentication
// OauthLoginStep1 starts the oauth authentication flow by returning the authentication URL, state and nonce.
func (a *Authenticator) OauthLoginStep1(_ context.Context, providerId string) (
authCodeUrl, state, nonce string,
err error,
@@ -296,9 +355,9 @@ func (a *Authenticator) OauthLoginStep1(_ context.Context, providerId string) (
}
switch oauthProvider.GetType() {
case domain.AuthenticatorTypeOAuth:
case AuthenticatorTypeOAuth:
authCodeUrl = oauthProvider.AuthCodeURL(state)
case domain.AuthenticatorTypeOidc:
case AuthenticatorTypeOidc:
nonce, err = a.randString(16)
if err != nil {
return "", "", "", fmt.Errorf("failed to generate nonce: %w", err)
@@ -318,6 +377,8 @@ func (a *Authenticator) randString(nByte int) (string, error) {
return base64.RawURLEncoding.EncodeToString(b), nil
}
// OauthLoginStep2 finishes the oauth authentication flow by exchanging the code for an access token and
// fetching the user information.
func (a *Authenticator) OauthLoginStep2(ctx context.Context, providerId, nonce, code string) (*domain.User, error) {
oauthProvider, ok := a.oauthAuthenticators[providerId]
if !ok {