2023-10-12 18:36:34 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
|
2023-10-14 16:06:02 +00:00
|
|
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
|
2024-10-06 20:11:58 +00:00
|
|
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/controller/auth"
|
2023-10-14 16:06:02 +00:00
|
|
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/controller/ui"
|
2024-08-15 16:25:15 +00:00
|
|
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/middlewares"
|
2024-10-06 20:11:58 +00:00
|
|
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage"
|
2024-10-16 19:42:39 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
2024-08-15 16:25:15 +00:00
|
|
|
"github.com/zitadel/oidc/v3/pkg/op"
|
|
|
|
"go.uber.org/zap"
|
2023-10-12 18:36:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Server struct {
|
2023-10-14 16:06:02 +00:00
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
httpSrv *http.Server
|
|
|
|
listener net.Listener
|
|
|
|
serverMode config.ListeningMode
|
|
|
|
address string
|
|
|
|
handler *http.ServeMux
|
|
|
|
controllers map[string]http.Handler
|
2024-08-15 16:25:15 +00:00
|
|
|
l *zap.SugaredLogger
|
2023-10-12 18:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newUnixListener(sockPath string) (net.Listener, error) {
|
|
|
|
if err := os.Remove(sockPath); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
|
|
return nil, fmt.Errorf("failed to cleanup previously existing socket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sock, err := net.Listen("unix", sockPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create unix socket: %w", err)
|
|
|
|
}
|
|
|
|
if err := os.Chmod(sockPath, 0o777); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to set permissions to unix socket: %w", err)
|
|
|
|
}
|
|
|
|
return sock, nil
|
|
|
|
}
|
|
|
|
|
2024-10-06 20:11:58 +00:00
|
|
|
func New(appConf *config.AppConfig, oidcHandler *op.Provider, st *storage.Storage, logger *zap.SugaredLogger) (*Server, error) {
|
2023-10-12 18:36:34 +00:00
|
|
|
var listener net.Listener
|
|
|
|
var addr string
|
|
|
|
var err error
|
|
|
|
switch appConf.ServerMode {
|
|
|
|
case config.ModeNet:
|
|
|
|
addr = fmt.Sprintf("%s:%d", appConf.Host, appConf.Port)
|
|
|
|
listener, err = net.Listen("tcp", addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to init server in net mode: %w", err)
|
|
|
|
}
|
|
|
|
case config.ModeUnix:
|
|
|
|
addr = appConf.SockPath
|
|
|
|
listener, err = newUnixListener(appConf.SockPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to init server in unix mode: %w", err)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unexpected listening mode %v", appConf.ServerMode))
|
|
|
|
}
|
|
|
|
|
2023-10-14 16:06:02 +00:00
|
|
|
controllers := map[string]http.Handler{
|
2024-10-16 19:42:39 +00:00
|
|
|
ui.StaticRoute: middlewares.WithLogger(ui.NewStaticController(appConf.StaticDir), logger),
|
|
|
|
"/": middlewares.WithLogger(ui.NewIndexController(logger, oidcHandler, appConf.StaticDir), logger),
|
2023-10-14 16:06:02 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:42:39 +00:00
|
|
|
userInfoHandler := auth.NewAuthCallbackController(logger, st)
|
|
|
|
loginHandlers := map[uuid.UUID]http.Handler{}
|
|
|
|
callbackHandlers := map[uuid.UUID]http.Handler{}
|
|
|
|
|
|
|
|
backends, err := st.LocalStorage.BackendStorage().GetAllBackends(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get list of backends from storage: %w", err)
|
|
|
|
}
|
|
|
|
for _, b := range backends {
|
2024-10-18 20:06:05 +00:00
|
|
|
provider, err := rp.NewRelyingPartyOIDC(context.Background(), b.Config.Issuer, b.Config.ClientID, b.Config.ClientSecret, b.Config.RedirectURI, b.Config.Scopes)
|
2024-10-16 19:42:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create connector for backend %s: %w", b.Name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
loginHandlers[b.ID] = middlewares.WithLogger(auth.NewAuthRedirectController(logger, provider, st), logger)
|
|
|
|
callbackHandlers[b.ID] = middlewares.WithLogger(rp.CodeExchangeHandler(rp.UserinfoCallback(userInfoHandler.HandleUserInfoCallback), provider), logger)
|
|
|
|
}
|
|
|
|
|
|
|
|
controllers[auth.AuthRedirectRoute] = middlewares.WithLogger(auth.NewAuthDispatchController(logger, st, loginHandlers), logger)
|
|
|
|
controllers[auth.AuthCallbackRoute] = middlewares.WithLogger(auth.NewCallbackDispatchController(logger, st, callbackHandlers), logger)
|
|
|
|
|
2023-10-12 18:36:34 +00:00
|
|
|
m := http.NewServeMux()
|
|
|
|
|
|
|
|
return &Server{
|
|
|
|
handler: m,
|
|
|
|
httpSrv: &http.Server{
|
|
|
|
Handler: m,
|
|
|
|
},
|
2023-10-14 16:06:02 +00:00
|
|
|
listener: listener,
|
|
|
|
l: logger,
|
|
|
|
serverMode: appConf.ServerMode,
|
|
|
|
address: addr,
|
|
|
|
controllers: controllers,
|
|
|
|
ctx: context.TODO(),
|
2023-10-12 18:36:34 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) initMux() {
|
2023-10-14 16:06:02 +00:00
|
|
|
for r, c := range s.controllers {
|
|
|
|
s.handler.Handle(r, c)
|
|
|
|
}
|
2023-10-12 18:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) Run(ctx context.Context) {
|
|
|
|
s.ctx, s.cancel = context.WithCancel(ctx)
|
|
|
|
s.initMux()
|
|
|
|
switch s.serverMode {
|
|
|
|
case config.ModeNet:
|
|
|
|
s.l.Infof("Server listening on host %q", s.address)
|
|
|
|
case config.ModeUnix:
|
|
|
|
s.l.Infof("Server listening on unix socket %q", s.address)
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
if err := s.httpSrv.Serve(s.listener); err != nil {
|
|
|
|
s.l.Errorf("failed to serve HTTP server: %s", err.Error())
|
|
|
|
}
|
|
|
|
s.cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) Done() <-chan struct{} {
|
|
|
|
return s.ctx.Done()
|
|
|
|
}
|