mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 09:51:39 +00:00
324 lines
9.5 KiB
Go
324 lines
9.5 KiB
Go
// Package server provides a spawnable HTTP server for Auth service.
|
|||
|
|
package server
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"os/signal"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/labstack/echo/v4"
|
||
|
|
"github.com/labstack/echo/v4/middleware"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Errors
|
||
|
|
var (
|
||
|
|
ErrAuthServerAlreadyRunning = errors.New("auth server already running")
|
||
|
|
ErrAuthServerNotRunning = errors.New("auth server not running")
|
||
|
|
ErrFailedToStartAuthServer = errors.New("failed to start auth server")
|
||
|
|
)
|
||
|
|
|
||
|
|
// AuthServer is a spawnable HTTP server for Auth service.
|
||
|
|
type AuthServer struct {
|
||
|
|
*echo.Echo
|
||
|
|
Port int
|
||
|
|
KillChan chan bool
|
||
|
|
ctx context.Context
|
||
|
|
cancel context.CancelFunc
|
||
|
|
sessionStore map[string]string // In-memory session store for WebAuthn challenges
|
||
|
|
registrationDone chan error // Channel to signal registration completion
|
||
|
|
credentialData chan *WebAuthnCredential // Channel to pass credential data to CLI
|
||
|
|
username string // Current username being registered
|
||
|
|
}
|
||
|
|
|
||
|
|
var authServer *AuthServer
|
||
|
|
|
||
|
|
// StartAuthServer starts the auth server
|
||
|
|
func StartAuthServer() error {
|
||
|
|
if authServer != nil {
|
||
|
|
return ErrAuthServerAlreadyRunning
|
||
|
|
}
|
||
|
|
setupAuthServer()
|
||
|
|
return authServer.Start()
|
||
|
|
}
|
||
|
|
|
||
|
|
// StartAuthServerWithWebAuthn starts the auth server with WebAuthn support
|
||
|
|
func StartAuthServerWithWebAuthn(port int, username string, done chan error) error {
|
||
|
|
if authServer != nil {
|
||
|
|
return ErrAuthServerAlreadyRunning
|
||
|
|
}
|
||
|
|
setupAuthServerWithWebAuthn(port, username, done)
|
||
|
|
return authServer.Start()
|
||
|
|
}
|
||
|
|
|
||
|
|
// StartAuthServerWithWebAuthnAndCredentialChannel starts auth server with WebAuthn and credential data channel
|
||
|
|
func StartAuthServerWithWebAuthnAndCredentialChannel(
|
||
|
|
port int,
|
||
|
|
username string,
|
||
|
|
done chan error,
|
||
|
|
credentialData chan *WebAuthnCredential,
|
||
|
|
) error {
|
||
|
|
if authServer != nil {
|
||
|
|
return ErrAuthServerAlreadyRunning
|
||
|
|
}
|
||
|
|
setupAuthServerWithWebAuthnAndCredentialChannel(port, username, done, credentialData)
|
||
|
|
return authServer.Start()
|
||
|
|
}
|
||
|
|
|
||
|
|
// StartAuthServerForLogin starts the auth server for WebAuthn login
|
||
|
|
func StartAuthServerForLogin(port int, username string, done chan error) error {
|
||
|
|
if authServer != nil {
|
||
|
|
return ErrAuthServerAlreadyRunning
|
||
|
|
}
|
||
|
|
setupAuthServerForLogin(port, username, done)
|
||
|
|
return authServer.Start()
|
||
|
|
}
|
||
|
|
|
||
|
|
// StopAuthServer stops the auth server
|
||
|
|
func StopAuthServer() error {
|
||
|
|
if authServer == nil {
|
||
|
|
return ErrAuthServerNotRunning
|
||
|
|
}
|
||
|
|
return authServer.Stop()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *AuthServer) Start() error {
|
||
|
|
// Setup signal context
|
||
|
|
s.ctx, s.cancel = signal.NotifyContext(context.Background(), os.Interrupt)
|
||
|
|
|
||
|
|
// Start server in goroutine
|
||
|
|
go func() {
|
||
|
|
if err := s.Echo.Start(fmt.Sprintf(":%d", s.Port)); err != nil &&
|
||
|
|
err != http.ErrServerClosed {
|
||
|
|
s.Logger.Fatal("shutting down the server")
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
// Start kill signal handler in another goroutine
|
||
|
|
go s.HandleKillSignal()
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *AuthServer) Stop() error {
|
||
|
|
// Cancel the signal context to trigger shutdown
|
||
|
|
if s.cancel != nil {
|
||
|
|
s.cancel()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create shutdown context with timeout
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
// Gracefully shutdown the server
|
||
|
|
if err := s.Shutdown(ctx); err != nil {
|
||
|
|
s.Logger.Fatal(err)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clean up
|
||
|
|
destroyAuthServer()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *AuthServer) HandleKillSignal() {
|
||
|
|
select {
|
||
|
|
case <-s.KillChan:
|
||
|
|
// Manual stop via KillChan
|
||
|
|
s.Stop()
|
||
|
|
case <-s.ctx.Done():
|
||
|
|
// OS interrupt signal received
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
if err := s.Shutdown(ctx); err != nil {
|
||
|
|
s.Logger.Fatal(err)
|
||
|
|
}
|
||
|
|
destroyAuthServer()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ╭───────────────────────────────────────────────────────────╮
|
||
|
|
// │ Server Config │
|
||
|
|
// ╰───────────────────────────────────────────────────────────╯
|
||
|
|
|
||
|
|
func setupRoutes(e *echo.Echo) {
|
||
|
|
// Basic routes
|
||
|
|
e.GET("/", HandleIndex)
|
||
|
|
e.GET("/health", HandleHealth)
|
||
|
|
e.POST("/login", HandleLogin)
|
||
|
|
|
||
|
|
// WebAuthn registration routes
|
||
|
|
e.GET("/register", HandleWebAuthnRegister)
|
||
|
|
e.GET("/begin-register", HandleBeginRegister) // GET for fetching options
|
||
|
|
e.POST("/begin-register", HandleBeginRegister) // POST also supported for client compatibility
|
||
|
|
e.POST("/finish-register", HandleFinishRegister)
|
||
|
|
}
|
||
|
|
|
||
|
|
// setupMiddleware configures server middleware
|
||
|
|
func setupMiddleware(e *echo.Echo) {
|
||
|
|
// CORS middleware for browser compatibility
|
||
|
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||
|
|
AllowOrigins: []string{"http://localhost:*", "https://localhost:*"},
|
||
|
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||
|
|
AllowHeaders: []string{"*"},
|
||
|
|
AllowCredentials: true,
|
||
|
|
}))
|
||
|
|
|
||
|
|
// Security middleware
|
||
|
|
e.Use(middleware.Secure())
|
||
|
|
e.Use(middleware.RequestID())
|
||
|
|
// Disable HTTP request logging for cleaner CLI output
|
||
|
|
// e.Use(middleware.Logger())
|
||
|
|
e.Use(middleware.Recover())
|
||
|
|
}
|
||
|
|
|
||
|
|
// destroyAuthServer destroys the auth server
|
||
|
|
func destroyAuthServer() {
|
||
|
|
authServer = nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// setupAuthServer sets up the auth server
|
||
|
|
func setupAuthServer() {
|
||
|
|
authServer = &AuthServer{
|
||
|
|
Echo: echo.New(),
|
||
|
|
Port: 8080,
|
||
|
|
KillChan: make(chan bool),
|
||
|
|
}
|
||
|
|
// Disable Echo framework logging for cleaner CLI output
|
||
|
|
authServer.HideBanner = true
|
||
|
|
authServer.HidePort = true
|
||
|
|
setupMiddleware(authServer.Echo)
|
||
|
|
setupRoutes(authServer.Echo)
|
||
|
|
}
|
||
|
|
|
||
|
|
// setupAuthServerWithWebAuthn sets up the auth server with WebAuthn context
|
||
|
|
func setupAuthServerWithWebAuthn(port int, username string, done chan error) {
|
||
|
|
// Initialize database for WebAuthn credential storage
|
||
|
|
_ = InitDB() // Errors handled gracefully in storeWebAuthnCredential
|
||
|
|
|
||
|
|
authServer = &AuthServer{
|
||
|
|
Echo: echo.New(),
|
||
|
|
Port: port,
|
||
|
|
KillChan: make(chan bool),
|
||
|
|
sessionStore: make(map[string]string),
|
||
|
|
registrationDone: done,
|
||
|
|
username: username,
|
||
|
|
}
|
||
|
|
// Disable Echo framework logging for cleaner CLI output
|
||
|
|
authServer.HideBanner = true
|
||
|
|
authServer.HidePort = true
|
||
|
|
setupMiddleware(authServer.Echo)
|
||
|
|
setupRoutes(authServer.Echo)
|
||
|
|
|
||
|
|
// Set up automatic server shutdown after 15 seconds as failsafe
|
||
|
|
go func() {
|
||
|
|
time.Sleep(15 * time.Second)
|
||
|
|
if authServer != nil {
|
||
|
|
logger := authServer.Logger
|
||
|
|
logger.Warn("Auto-shutting down auth server after 15 second timeout")
|
||
|
|
select {
|
||
|
|
case authServer.KillChan <- true:
|
||
|
|
logger.Info("Server shutdown signal sent via KillChan")
|
||
|
|
default:
|
||
|
|
logger.Warn("KillChan full, server may already be shutting down")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
// setupAuthServerWithWebAuthnAndCredentialChannel sets up auth server with WebAuthn and credential channel
|
||
|
|
func setupAuthServerWithWebAuthnAndCredentialChannel(
|
||
|
|
port int,
|
||
|
|
username string,
|
||
|
|
done chan error,
|
||
|
|
credentialData chan *WebAuthnCredential,
|
||
|
|
) {
|
||
|
|
// Initialize database for WebAuthn credential storage
|
||
|
|
_ = InitDB() // Errors handled gracefully in storeWebAuthnCredential
|
||
|
|
|
||
|
|
e := echo.New()
|
||
|
|
e.HideBanner = true
|
||
|
|
e.HidePort = true
|
||
|
|
authServer = &AuthServer{
|
||
|
|
Echo: e,
|
||
|
|
Port: port,
|
||
|
|
KillChan: make(chan bool),
|
||
|
|
sessionStore: make(map[string]string),
|
||
|
|
registrationDone: done,
|
||
|
|
credentialData: credentialData,
|
||
|
|
username: username,
|
||
|
|
}
|
||
|
|
// Disable Echo framework logging for cleaner CLI output
|
||
|
|
authServer.HideBanner = true
|
||
|
|
authServer.HidePort = true
|
||
|
|
setupMiddleware(authServer.Echo)
|
||
|
|
setupRoutes(authServer.Echo)
|
||
|
|
|
||
|
|
// Set up automatic server shutdown after 15 seconds as failsafe
|
||
|
|
go func() {
|
||
|
|
time.Sleep(15 * time.Second)
|
||
|
|
if authServer != nil {
|
||
|
|
logger := authServer.Logger
|
||
|
|
logger.Warn("Auto-shutting down auth server after 15 second timeout")
|
||
|
|
select {
|
||
|
|
case authServer.KillChan <- true:
|
||
|
|
logger.Info("Server shutdown signal sent via KillChan")
|
||
|
|
default:
|
||
|
|
logger.Warn("KillChan full, server may already be shutting down")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
// setupAuthServerForLogin sets up the auth server for WebAuthn login
|
||
|
|
func setupAuthServerForLogin(port int, username string, done chan error) {
|
||
|
|
// Initialize database for WebAuthn credential verification
|
||
|
|
_ = InitDB() // Errors handled gracefully in login handlers
|
||
|
|
|
||
|
|
authServer = &AuthServer{
|
||
|
|
Echo: echo.New(),
|
||
|
|
Port: port,
|
||
|
|
KillChan: make(chan bool),
|
||
|
|
sessionStore: make(map[string]string),
|
||
|
|
registrationDone: done,
|
||
|
|
username: username,
|
||
|
|
}
|
||
|
|
// Disable Echo framework logging for cleaner CLI output
|
||
|
|
authServer.HideBanner = true
|
||
|
|
authServer.HidePort = true
|
||
|
|
setupMiddleware(authServer.Echo)
|
||
|
|
setupLoginRoutes(authServer.Echo)
|
||
|
|
|
||
|
|
// Set up automatic server shutdown after 45 seconds as failsafe (longer for login)
|
||
|
|
go func() {
|
||
|
|
time.Sleep(45 * time.Second)
|
||
|
|
if authServer != nil {
|
||
|
|
logger := authServer.Logger
|
||
|
|
logger.Warn("Auto-shutting down login auth server after 45 second timeout")
|
||
|
|
select {
|
||
|
|
case authServer.KillChan <- true:
|
||
|
|
logger.Info("Login server shutdown signal sent via KillChan")
|
||
|
|
default:
|
||
|
|
logger.Warn("KillChan full, login server may already be shutting down")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
// setupLoginRoutes configures routes specifically for login flow
|
||
|
|
func setupLoginRoutes(e *echo.Echo) {
|
||
|
|
// Basic routes
|
||
|
|
e.GET("/", HandleIndex)
|
||
|
|
e.GET("/health", HandleHealth)
|
||
|
|
|
||
|
|
// WebAuthn login routes
|
||
|
|
e.GET("/login", HandleWebAuthnLogin)
|
||
|
|
e.GET("/begin-login", HandleBeginLogin)
|
||
|
|
e.POST("/begin-login", HandleBeginLogin) // POST also supported for client compatibility
|
||
|
|
e.POST("/finish-login", HandleFinishLogin)
|
||
|
|
e.POST("/login/verify", HandleFinishLogin) // Alternative endpoint for client compatibility
|
||
|
|
}
|