diff --git a/cmd/wg-portal/main.go b/cmd/wg-portal/main.go index edd4c48..18070e4 100644 --- a/cmd/wg-portal/main.go +++ b/cmd/wg-portal/main.go @@ -109,10 +109,10 @@ func main() { apiV0Session := handlersV0.NewSessionWrapper(cfg) apiV0Auth := handlersV0.NewAuthenticationHandler(authenticator, apiV0Session) - apiV0EndpointAuth := handlersV0.NewAuthEndpoint(backend, apiV0Auth, apiV0Session, validatorManager) - apiV0EndpointUsers := handlersV0.NewUserEndpoint(backend, apiV0Auth, validatorManager) - apiV0EndpointInterfaces := handlersV0.NewInterfaceEndpoint(backend, apiV0Auth, validatorManager) - apiV0EndpointPeers := handlersV0.NewPeerEndpoint(backend, apiV0Auth, validatorManager) + apiV0EndpointAuth := handlersV0.NewAuthEndpoint(cfg, apiV0Auth, apiV0Session, validatorManager, backend) + apiV0EndpointUsers := handlersV0.NewUserEndpoint(cfg, apiV0Auth, validatorManager, backend) + apiV0EndpointInterfaces := handlersV0.NewInterfaceEndpoint(cfg, apiV0Auth, validatorManager, backend) + apiV0EndpointPeers := handlersV0.NewPeerEndpoint(cfg, apiV0Auth, validatorManager, backend) apiV0EndpointConfig := handlersV0.NewConfigEndpoint(cfg, apiV0Auth) apiV0EndpointTest := handlersV0.NewTestEndpoint(apiV0Auth) diff --git a/internal/app/api/v0/handlers/base.go b/internal/app/api/v0/handlers/base.go index bb32b5e..82c8dfe 100644 --- a/internal/app/api/v0/handlers/base.go +++ b/internal/app/api/v0/handlers/base.go @@ -36,13 +36,6 @@ type Handler interface { RegisterRoutes(g *routegroup.Bundle) } -type Authenticator interface { - // LoggedIn checks if a user is logged in. If scopes are given, they are validated as well. - LoggedIn(scopes ...Scope) func(next http.Handler) http.Handler - // UserIdMatch checks if the user id in the session matches the user id in the request. If not, the request is aborted. - UserIdMatch(idParameter string) func(next http.Handler) http.Handler -} - // To compile the API documentation use the // api_build_tool // command that can be found in the $PROJECT_ROOT/cmd/api_build_tool directory. @@ -97,6 +90,27 @@ func handleCsrfGet() http.HandlerFunc { } } -// region session wrapper +// region handler-interfaces -// endregion session wrapper +type Authenticator interface { + // LoggedIn checks if a user is logged in. If scopes are given, they are validated as well. + LoggedIn(scopes ...Scope) func(next http.Handler) http.Handler + // UserIdMatch checks if the user id in the session matches the user id in the request. If not, the request is aborted. + UserIdMatch(idParameter string) func(next http.Handler) http.Handler +} + +type Session interface { + // SetData sets the session data for the given context. + SetData(ctx context.Context, val SessionData) + // GetData returns the session data for the given context. If no data is found, the default session data is returned. + GetData(ctx context.Context) SessionData + // DestroyData destroys the session data for the given context. + DestroyData(ctx context.Context) +} + +type Validator interface { + // Struct validates the given struct. + Struct(s interface{}) error +} + +// endregion handler-interfaces diff --git a/internal/app/api/v0/handlers/encoding.go b/internal/app/api/v0/handlers/encoding.go index 8681eaf..28e40b7 100644 --- a/internal/app/api/v0/handlers/encoding.go +++ b/internal/app/api/v0/handlers/encoding.go @@ -5,6 +5,9 @@ import ( "strings" ) +// Base64UrlDecode decodes a base64 url encoded string. +// In comparison to the standard base64 encoding, the url encoding uses - instead of + and _ instead of / +// as well as . instead of =. func Base64UrlDecode(in string) string { in = strings.ReplaceAll(in, "-", "=") in = strings.ReplaceAll(in, "_", "/") diff --git a/internal/app/api/v0/handlers/endpoint_authentication.go b/internal/app/api/v0/handlers/endpoint_authentication.go index 17fd3f8..16cef2e 100644 --- a/internal/app/api/v0/handlers/endpoint_authentication.go +++ b/internal/app/api/v0/handlers/endpoint_authentication.go @@ -10,36 +10,42 @@ import ( "github.com/go-pkgz/routegroup" - "github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app/api/core/request" "github.com/h44z/wg-portal/internal/app/api/core/respond" "github.com/h44z/wg-portal/internal/app/api/v0/model" + "github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/domain" ) -type Session interface { - // SetData sets the session data for the given context. - SetData(ctx context.Context, val SessionData) - // GetData returns the session data for the given context. If no data is found, the default session data is returned. - GetData(ctx context.Context) SessionData - // DestroyData destroys the session data for the given context. - DestroyData(ctx context.Context) -} - -type Validator interface { - Struct(s interface{}) error +type AuthenticationService interface { + // GetExternalLoginProviders returns a list of all available external login providers. + GetExternalLoginProviders(_ context.Context) []domain.LoginProviderInfo + // PlainLogin authenticates a user with a username and password. + PlainLogin(ctx context.Context, username, password string) (*domain.User, error) + // OauthLoginStep1 initiates the OAuth login flow. + OauthLoginStep1(_ context.Context, providerId string) (authCodeUrl, state, nonce string, err error) + // OauthLoginStep2 completes the OAuth login flow and logins the user in. + OauthLoginStep2(ctx context.Context, providerId, nonce, code string) (*domain.User, error) } type AuthEndpoint struct { - app *app.App + cfg *config.Config + authService AuthenticationService authenticator Authenticator session Session validate Validator } -func NewAuthEndpoint(app *app.App, authenticator Authenticator, session Session, validator Validator) AuthEndpoint { +func NewAuthEndpoint( + cfg *config.Config, + authenticator Authenticator, + session Session, + validator Validator, + authService AuthenticationService, +) AuthEndpoint { return AuthEndpoint{ - app: app, + cfg: cfg, + authService: authService, authenticator: authenticator, session: session, validate: validator, @@ -73,7 +79,7 @@ func (e AuthEndpoint) RegisterRoutes(g *routegroup.Bundle) { // @Router /auth/providers [get] func (e AuthEndpoint) handleExternalLoginProvidersGet() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - providers := e.app.Authenticator.GetExternalLoginProviders(r.Context()) + providers := e.authService.GetExternalLoginProviders(r.Context()) respond.JSON(w, http.StatusOK, model.NewLoginProviderInfos(providers)) } @@ -169,7 +175,7 @@ func (e AuthEndpoint) handleOauthInitiateGet() http.HandlerFunc { return } - authCodeUrl, state, nonce, err := e.app.Authenticator.OauthLoginStep1(context.Background(), provider) + authCodeUrl, state, nonce, err := e.authService.OauthLoginStep1(context.Background(), provider) if err != nil { if autoRedirect && e.isValidReturnUrl(returnTo) { redirectToReturn() @@ -262,7 +268,8 @@ func (e AuthEndpoint) handleOauthCallbackGet() http.HandlerFunc { } loginCtx, cancel := context.WithTimeout(context.Background(), 1000*time.Second) - user, err := e.app.Authenticator.OauthLoginStep2(loginCtx, provider, currentSession.OauthNonce, oauthCode) + user, err := e.authService.OauthLoginStep2(loginCtx, provider, currentSession.OauthNonce, + oauthCode) cancel() if err != nil { if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) { @@ -335,7 +342,8 @@ func (e AuthEndpoint) handleLoginPost() http.HandlerFunc { return } - user, err := e.app.Authenticator.PlainLogin(context.Background(), loginData.Username, loginData.Password) + user, err := e.authService.PlainLogin(context.Background(), loginData.Username, + loginData.Password) if err != nil { respond.JSON(w, http.StatusUnauthorized, model.Error{Code: http.StatusUnauthorized, Message: "login failed"}) @@ -372,7 +380,7 @@ func (e AuthEndpoint) handleLogoutPost() http.HandlerFunc { // isValidReturnUrl checks if the given return URL matches the configured external URL of the application. func (e AuthEndpoint) isValidReturnUrl(returnUrl string) bool { - if !strings.HasPrefix(returnUrl, e.app.Config.Web.ExternalUrl) { + if !strings.HasPrefix(returnUrl, e.cfg.Web.ExternalUrl) { return false } diff --git a/internal/app/api/v0/handlers/endpoint_interfaces.go b/internal/app/api/v0/handlers/endpoint_interfaces.go index fe89810..5478a3b 100644 --- a/internal/app/api/v0/handlers/endpoint_interfaces.go +++ b/internal/app/api/v0/handlers/endpoint_interfaces.go @@ -1,29 +1,58 @@ package handlers import ( + "context" "io" "net/http" "github.com/go-pkgz/routegroup" - "github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app/api/core/request" "github.com/h44z/wg-portal/internal/app/api/core/respond" "github.com/h44z/wg-portal/internal/app/api/v0/model" + "github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/domain" ) -type InterfaceEndpoint struct { - app *app.App - authenticator Authenticator - validator Validator +type InterfaceService interface { + // GetInterfaceAndPeers returns the interface with the given id and all peers associated with it. + GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) + // PrepareInterface returns a new interface with default values. + PrepareInterface(ctx context.Context) (*domain.Interface, error) + // CreateInterface creates a new interface. + CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) + // UpdateInterface updates the interface with the given id. + UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, []domain.Peer, error) + // DeleteInterface deletes the interface with the given id. + DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error + // GetAllInterfacesAndPeers returns all interfaces and all peers associated with them. + GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interface, [][]domain.Peer, error) + // GetInterfaceConfig returns the interface configuration as string. + GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) + // PersistInterfaceConfig persists the interface configuration to a file. + PersistInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) error + // ApplyPeerDefaults applies the peer defaults to all peers of the given interface. + ApplyPeerDefaults(ctx context.Context, in *domain.Interface) error } -func NewInterfaceEndpoint(app *app.App, authenticator Authenticator, validator Validator) InterfaceEndpoint { +type InterfaceEndpoint struct { + cfg *config.Config + interfaceService InterfaceService + authenticator Authenticator + validator Validator +} + +func NewInterfaceEndpoint( + cfg *config.Config, + authenticator Authenticator, + validator Validator, + interfaceService InterfaceService, +) InterfaceEndpoint { return InterfaceEndpoint{ - app: app, - authenticator: authenticator, - validator: validator, + cfg: cfg, + interfaceService: interfaceService, + authenticator: authenticator, + validator: validator, } } @@ -59,7 +88,7 @@ func (e InterfaceEndpoint) RegisterRoutes(g *routegroup.Bundle) { // @Router /interface/prepare [get] func (e InterfaceEndpoint) handlePrepareGet() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - in, err := e.app.PrepareInterface(r.Context()) + in, err := e.interfaceService.PrepareInterface(r.Context()) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -82,7 +111,7 @@ func (e InterfaceEndpoint) handlePrepareGet() http.HandlerFunc { // @Router /interface/all [get] func (e InterfaceEndpoint) handleAllGet() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - interfaces, peers, err := e.app.GetAllInterfacesAndPeers(r.Context()) + interfaces, peers, err := e.interfaceService.GetAllInterfacesAndPeers(r.Context()) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -114,7 +143,7 @@ func (e InterfaceEndpoint) handleSingleGet() http.HandlerFunc { return } - iface, peers, err := e.app.GetInterfaceAndPeers(r.Context(), domain.InterfaceIdentifier(id)) + iface, peers, err := e.interfaceService.GetInterfaceAndPeers(r.Context(), domain.InterfaceIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -146,7 +175,7 @@ func (e InterfaceEndpoint) handleConfigGet() http.HandlerFunc { return } - config, err := e.app.GetInterfaceConfig(r.Context(), domain.InterfaceIdentifier(id)) + config, err := e.interfaceService.GetInterfaceConfig(r.Context(), domain.InterfaceIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -203,7 +232,7 @@ func (e InterfaceEndpoint) handleUpdatePut() http.HandlerFunc { return } - updatedInterface, peers, err := e.app.UpdateInterface(r.Context(), model.NewDomainInterface(&in)) + updatedInterface, peers, err := e.interfaceService.UpdateInterface(r.Context(), model.NewDomainInterface(&in)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -238,7 +267,7 @@ func (e InterfaceEndpoint) handleCreatePost() http.HandlerFunc { return } - newInterface, err := e.app.CreateInterface(r.Context(), model.NewDomainInterface(&in)) + newInterface, err := e.interfaceService.CreateInterface(r.Context(), model.NewDomainInterface(&in)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -269,7 +298,7 @@ func (e InterfaceEndpoint) handlePeersGet() http.HandlerFunc { return } - _, peers, err := e.app.GetInterfaceAndPeers(r.Context(), domain.InterfaceIdentifier(id)) + _, peers, err := e.interfaceService.GetInterfaceAndPeers(r.Context(), domain.InterfaceIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -301,7 +330,7 @@ func (e InterfaceEndpoint) handleDelete() http.HandlerFunc { return } - err := e.app.DeleteInterface(r.Context(), domain.InterfaceIdentifier(id)) + err := e.interfaceService.DeleteInterface(r.Context(), domain.InterfaceIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -333,7 +362,7 @@ func (e InterfaceEndpoint) handleSaveConfigPost() http.HandlerFunc { return } - err := e.app.PersistInterfaceConfig(r.Context(), domain.InterfaceIdentifier(id)) + err := e.interfaceService.PersistInterfaceConfig(r.Context(), domain.InterfaceIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -382,7 +411,7 @@ func (e InterfaceEndpoint) handleApplyPeerDefaultsPost() http.HandlerFunc { return } - if err := e.app.ApplyPeerDefaults(r.Context(), model.NewDomainInterface(&in)); err != nil { + if err := e.interfaceService.ApplyPeerDefaults(r.Context(), model.NewDomainInterface(&in)); err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), }) diff --git a/internal/app/api/v0/handlers/endpoint_peers.go b/internal/app/api/v0/handlers/endpoint_peers.go index 64114fa..d9a0115 100644 --- a/internal/app/api/v0/handlers/endpoint_peers.go +++ b/internal/app/api/v0/handlers/endpoint_peers.go @@ -1,27 +1,64 @@ package handlers import ( + "context" "io" "net/http" "github.com/go-pkgz/routegroup" - "github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app/api/core/request" "github.com/h44z/wg-portal/internal/app/api/core/respond" "github.com/h44z/wg-portal/internal/app/api/v0/model" + "github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/domain" ) +type PeerService interface { + // GetInterfaceAndPeers returns the interface with the given id and all peers associated with it. + GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) + // PreparePeer returns a new peer with default values for the given interface. + PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) + // GetPeer returns the peer with the given id. + GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) + // CreatePeer creates a new peer. + CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) + // CreateMultiplePeers creates multiple new peers. + CreateMultiplePeers( + ctx context.Context, + interfaceId domain.InterfaceIdentifier, + r *domain.PeerCreationRequest, + ) ([]domain.Peer, error) + // UpdatePeer updates the peer with the given id. + UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) + // DeletePeer deletes the peer with the given id. + DeletePeer(ctx context.Context, id domain.PeerIdentifier) error + // GetPeerConfig returns the peer configuration for the given id. + GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) + // GetPeerConfigQrCode returns the peer configuration as qr code for the given id. + GetPeerConfigQrCode(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) + // SendPeerEmail sends the peer configuration via email. + SendPeerEmail(ctx context.Context, linkOnly bool, peers ...domain.PeerIdentifier) error + // GetPeerStats returns the peer stats for the given interface. + GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error) +} + type PeerEndpoint struct { - app *app.App + cfg *config.Config + peerService PeerService authenticator Authenticator validator Validator } -func NewPeerEndpoint(app *app.App, authenticator Authenticator, validator Validator) PeerEndpoint { +func NewPeerEndpoint( + cfg *config.Config, + authenticator Authenticator, + validator Validator, + peerService PeerService, +) PeerEndpoint { return PeerEndpoint{ - app: app, + cfg: cfg, + peerService: peerService, authenticator: authenticator, validator: validator, } @@ -69,7 +106,7 @@ func (e PeerEndpoint) handleAllGet() http.HandlerFunc { return } - _, peers, err := e.app.GetInterfaceAndPeers(r.Context(), domain.InterfaceIdentifier(interfaceId)) + _, peers, err := e.peerService.GetInterfaceAndPeers(r.Context(), domain.InterfaceIdentifier(interfaceId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -100,7 +137,7 @@ func (e PeerEndpoint) handleSingleGet() http.HandlerFunc { return } - peer, err := e.app.GetPeer(r.Context(), domain.PeerIdentifier(peerId)) + peer, err := e.peerService.GetPeer(r.Context(), domain.PeerIdentifier(peerId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -131,7 +168,7 @@ func (e PeerEndpoint) handlePrepareGet() http.HandlerFunc { return } - peer, err := e.app.PreparePeer(r.Context(), domain.InterfaceIdentifier(interfaceId)) + peer, err := e.peerService.PreparePeer(r.Context(), domain.InterfaceIdentifier(interfaceId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -179,7 +216,7 @@ func (e PeerEndpoint) handleCreatePost() http.HandlerFunc { return } - newPeer, err := e.app.CreatePeer(r.Context(), model.NewDomainPeer(&p)) + newPeer, err := e.peerService.CreatePeer(r.Context(), model.NewDomainPeer(&p)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -221,7 +258,7 @@ func (e PeerEndpoint) handleCreateMultiplePost() http.HandlerFunc { return } - newPeers, err := e.app.CreateMultiplePeers(r.Context(), domain.InterfaceIdentifier(interfaceId), + newPeers, err := e.peerService.CreateMultiplePeers(r.Context(), domain.InterfaceIdentifier(interfaceId), model.NewDomainPeerCreationRequest(&req)) if err != nil { respond.JSON(w, http.StatusInternalServerError, @@ -270,7 +307,7 @@ func (e PeerEndpoint) handleUpdatePut() http.HandlerFunc { return } - updatedPeer, err := e.app.UpdatePeer(r.Context(), model.NewDomainPeer(&p)) + updatedPeer, err := e.peerService.UpdatePeer(r.Context(), model.NewDomainPeer(&p)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -300,7 +337,7 @@ func (e PeerEndpoint) handleDelete() http.HandlerFunc { return } - err := e.app.DeletePeer(r.Context(), domain.PeerIdentifier(id)) + err := e.peerService.DeletePeer(r.Context(), domain.PeerIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -332,7 +369,7 @@ func (e PeerEndpoint) handleConfigGet() http.HandlerFunc { return } - config, err := e.app.GetPeerConfig(r.Context(), domain.PeerIdentifier(id)) + configTxt, err := e.peerService.GetPeerConfig(r.Context(), domain.PeerIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -340,7 +377,7 @@ func (e PeerEndpoint) handleConfigGet() http.HandlerFunc { return } - configString, err := io.ReadAll(config) + configTxtString, err := io.ReadAll(configTxt) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -348,7 +385,7 @@ func (e PeerEndpoint) handleConfigGet() http.HandlerFunc { return } - respond.JSON(w, http.StatusOK, string(configString)) + respond.JSON(w, http.StatusOK, string(configTxtString)) } } @@ -374,7 +411,7 @@ func (e PeerEndpoint) handleQrCodeGet() http.HandlerFunc { return } - config, err := e.app.GetPeerConfigQrCode(r.Context(), domain.PeerIdentifier(id)) + configQr, err := e.peerService.GetPeerConfigQrCode(r.Context(), domain.PeerIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -382,7 +419,7 @@ func (e PeerEndpoint) handleQrCodeGet() http.HandlerFunc { return } - configData, err := io.ReadAll(config) + configQrData, err := io.ReadAll(configQr) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{ Code: http.StatusInternalServerError, Message: err.Error(), @@ -390,7 +427,7 @@ func (e PeerEndpoint) handleQrCodeGet() http.HandlerFunc { return } - respond.Data(w, http.StatusOK, "image/png", configData) + respond.Data(w, http.StatusOK, "image/png", configQrData) } } @@ -427,7 +464,7 @@ func (e PeerEndpoint) handleEmailPost() http.HandlerFunc { for i := range req.Identifiers { peerIds[i] = domain.PeerIdentifier(req.Identifiers[i]) } - if err := e.app.SendPeerEmail(r.Context(), req.LinkOnly, peerIds...); err != nil { + if err := e.peerService.SendPeerEmail(r.Context(), req.LinkOnly, peerIds...); err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) return @@ -457,13 +494,13 @@ func (e PeerEndpoint) handleStatsGet() http.HandlerFunc { return } - stats, err := e.app.GetPeerStats(r.Context(), domain.InterfaceIdentifier(interfaceId)) + stats, err := e.peerService.GetPeerStats(r.Context(), domain.InterfaceIdentifier(interfaceId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) return } - respond.JSON(w, http.StatusOK, model.NewPeerStats(e.app.Config.Statistics.CollectPeerData, stats)) + respond.JSON(w, http.StatusOK, model.NewPeerStats(e.cfg.Statistics.CollectPeerData, stats)) } } diff --git a/internal/app/api/v0/handlers/endpoint_users.go b/internal/app/api/v0/handlers/endpoint_users.go index 7d65a9d..4cad7b2 100644 --- a/internal/app/api/v0/handlers/endpoint_users.go +++ b/internal/app/api/v0/handlers/endpoint_users.go @@ -1,26 +1,57 @@ package handlers import ( + "context" "net/http" "github.com/go-pkgz/routegroup" - "github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app/api/core/request" "github.com/h44z/wg-portal/internal/app/api/core/respond" "github.com/h44z/wg-portal/internal/app/api/v0/model" + "github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/domain" ) +type UserService interface { + // GetUser returns the user with the given id. + GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) + // GetAllUsers returns all users. + GetAllUsers(ctx context.Context) ([]domain.User, error) + // UpdateUser updates the user with the given id. + UpdateUser(ctx context.Context, user *domain.User) (*domain.User, error) + // CreateUser creates a new user. + CreateUser(ctx context.Context, user *domain.User) (*domain.User, error) + // DeleteUser deletes the user with the given id. + DeleteUser(ctx context.Context, id domain.UserIdentifier) error + // ActivateApi enables the API for the user with the given id. + ActivateApi(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) + // DeactivateApi disables the API for the user with the given id. + DeactivateApi(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) + // GetUserPeers returns all peers for the given user. + GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) + // GetUserPeerStats returns all peer stats for the given user. + GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error) + // GetUserInterfaces returns all interfaces for the given user. + GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error) +} + type UserEndpoint struct { - app *app.App + cfg *config.Config + userService UserService authenticator Authenticator validator Validator } -func NewUserEndpoint(app *app.App, authenticator Authenticator, validator Validator) UserEndpoint { +func NewUserEndpoint( + cfg *config.Config, + authenticator Authenticator, + validator Validator, + userService UserService, +) UserEndpoint { return UserEndpoint{ - app: app, + cfg: cfg, + userService: userService, authenticator: authenticator, validator: validator, } @@ -57,7 +88,7 @@ func (e UserEndpoint) RegisterRoutes(g *routegroup.Bundle) { // @Router /user/all [get] func (e UserEndpoint) handleAllGet() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - users, err := e.app.GetAllUsers(r.Context()) + users, err := e.userService.GetAllUsers(r.Context()) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -86,7 +117,7 @@ func (e UserEndpoint) handleSingleGet() http.HandlerFunc { return } - user, err := e.app.GetUser(r.Context(), domain.UserIdentifier(id)) + user, err := e.userService.GetUser(r.Context(), domain.UserIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -133,7 +164,7 @@ func (e UserEndpoint) handleUpdatePut() http.HandlerFunc { return } - updateUser, err := e.app.UpdateUser(r.Context(), model.NewDomainUser(&user)) + updateUser, err := e.userService.UpdateUser(r.Context(), model.NewDomainUser(&user)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -167,7 +198,7 @@ func (e UserEndpoint) handleCreatePost() http.HandlerFunc { return } - newUser, err := e.app.CreateUser(r.Context(), model.NewDomainUser(&user)) + newUser, err := e.userService.CreateUser(r.Context(), model.NewDomainUser(&user)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -198,7 +229,7 @@ func (e UserEndpoint) handlePeersGet() http.HandlerFunc { return } - peers, err := e.app.GetUserPeers(r.Context(), domain.UserIdentifier(userId)) + peers, err := e.userService.GetUserPeers(r.Context(), domain.UserIdentifier(userId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -229,14 +260,14 @@ func (e UserEndpoint) handleStatsGet() http.HandlerFunc { return } - stats, err := e.app.GetUserPeerStats(r.Context(), domain.UserIdentifier(userId)) + stats, err := e.userService.GetUserPeerStats(r.Context(), domain.UserIdentifier(userId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) return } - respond.JSON(w, http.StatusOK, model.NewPeerStats(e.app.Config.Statistics.CollectPeerData, stats)) + respond.JSON(w, http.StatusOK, model.NewPeerStats(e.cfg.Statistics.CollectPeerData, stats)) } } @@ -260,7 +291,7 @@ func (e UserEndpoint) handleInterfacesGet() http.HandlerFunc { return } - peers, err := e.app.GetUserInterfaces(r.Context(), domain.UserIdentifier(userId)) + peers, err := e.userService.GetUserInterfaces(r.Context(), domain.UserIdentifier(userId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -290,7 +321,7 @@ func (e UserEndpoint) handleDelete() http.HandlerFunc { return } - err := e.app.DeleteUser(r.Context(), domain.UserIdentifier(id)) + err := e.userService.DeleteUser(r.Context(), domain.UserIdentifier(id)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -320,7 +351,7 @@ func (e UserEndpoint) handleApiEnablePost() http.HandlerFunc { return } - user, err := e.app.ActivateApi(r.Context(), domain.UserIdentifier(userId)) + user, err := e.userService.ActivateApi(r.Context(), domain.UserIdentifier(userId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) @@ -350,7 +381,7 @@ func (e UserEndpoint) handleApiDisablePost() http.HandlerFunc { return } - user, err := e.app.DeactivateApi(r.Context(), domain.UserIdentifier(userId)) + user, err := e.userService.DeactivateApi(r.Context(), domain.UserIdentifier(userId)) if err != nil { respond.JSON(w, http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) diff --git a/internal/app/api/v1/handlers/base.go b/internal/app/api/v1/handlers/base.go index ccb4fb5..760e4b5 100644 --- a/internal/app/api/v1/handlers/base.go +++ b/internal/app/api/v1/handlers/base.go @@ -79,11 +79,16 @@ func ParseServiceError(err error) (int, models.Error) { } } +// region handler-interfaces + type Authenticator interface { // LoggedIn checks if a user is logged in. If scopes are given, they are validated as well. LoggedIn(scopes ...Scope) func(next http.Handler) http.Handler } type Validator interface { + // Struct validates the given struct. Struct(s interface{}) error } + +// endregion handler-interfaces