Completed
Push — main ( 28a13c...ee7c69 )
by Yume
24s queued 21s
created

v2.generateSecureNonce   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
package v2
2
3
import (
4
	"crypto/rand"
5
	"encoding/base64"
6
	"fmt"
7
	"net/http"
8
	"sync"
9
10
	"github.com/labstack/echo/v4"
11
	"github.com/labstack/echo/v4/middleware"
12
	"github.com/memnix/memnix-rest/cmd/v2/config"
13
	"github.com/memnix/memnix-rest/domain"
14
)
15
16
var (
17
	instance *InstanceSingleton //nolint:gochecknoglobals //Singleton
18
	once     sync.Once          //nolint:gochecknoglobals //Singleton
19
)
20
21
type InstanceSingleton struct {
22
	echoInstance *echo.Echo
23
	config       config.ServerConfig
24
}
25
26
// New returns a new Echo instance.
27
func GetEchoInstance() *echo.Echo {
28
	return instance.echoInstance
29
}
30
31
func GetEchoSingleton() *InstanceSingleton {
32
	once.Do(func() {
33
		instance = &InstanceSingleton{}
34
		instance.echoInstance = echo.New()
35
		instance.registerMiddlewares(instance.echoInstance)
36
37
		instance.registerStaticRoutes(instance.echoInstance)
38
39
		instance.registerRoutes(instance.echoInstance)
40
	})
41
	return instance
42
}
43
44
func CreateEchoInstance(config config.ServerConfig) *InstanceSingleton {
45
	return GetEchoSingleton().WithConfig(config)
46
}
47
48
func (i *InstanceSingleton) Start() error {
49
	if err := i.echoInstance.Start(":" + i.config.Port); err != nil {
50
		return err
51
	}
52
53
	return nil
54
}
55
56
func (i *InstanceSingleton) WithConfig(config config.ServerConfig) *InstanceSingleton {
57
	i.config = config
58
	return i
59
}
60
61
func (i *InstanceSingleton) registerMiddlewares(e *echo.Echo) {
62
	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
63
		AllowOrigins: []string{"http://localhost", i.config.FrontendURL, i.config.Host},
64
		AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
65
	}))
66
67
	e.Use(CSPMiddleware)
68
69
	// if debug
70
	if config.IsDevelopment() {
71
		e.Use(middleware.Logger())
72
	}
73
74
	// e.Use(middleware.Recover())
75
76
	e.Use(middleware.Secure())
77
78
	csrfConfig := middleware.CSRFWithConfig(middleware.CSRFConfig{
79
		TokenLookup:    "cookie:_csrf",
80
		CookiePath:     "/",
81
		CookieDomain:   i.config.Host,
82
		CookieSecure:   true,
83
		CookieHTTPOnly: true,
84
		CookieSameSite: http.SameSiteStrictMode,
85
	})
86
87
	e.Use(csrfConfig)
88
}
89
90
func generateSecureNonce() (string, error) {
91
	nonce := make([]byte, config.NonceLength)
92
	_, err := rand.Read(nonce)
93
	if err != nil {
94
		return "", err
95
	}
96
	return base64.URLEncoding.EncodeToString(nonce), err
97
}
98
99
func CSPMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
100
	return func(c echo.Context) error {
101
		htmxNonce, _ := generateSecureNonce()
102
		hyperscriptNonce, _ := generateSecureNonce()
103
		twNonce, _ := generateSecureNonce()
104
		preloadNonce, _ := generateSecureNonce()
105
106
		htmxCSSHash := "sha256-pgn1TCGZX6O77zDvy0oTODMOxemn0oj0LeCnQTRj7Kg="
107
108
		cspHeader := fmt.Sprintf(
109
			"default-src 'self'; script-src 'nonce-%s' 'nonce-%s' 'nonce-%s'; style-src 'self' 'nonce-%s' https://fonts.bunny.net '%s'; font-src https://fonts.bunny.net 'self'",
110
			htmxNonce, hyperscriptNonce, preloadNonce, twNonce, htmxCSSHash)
111
112
		c.Response().Header().Set("Content-Security-Policy", cspHeader)
113
114
		c.Set("nonce", domain.Nonce{
115
			HtmxNonce:        htmxNonce,
116
			HyperscriptNonce: hyperscriptNonce,
117
			TwNonce:          twNonce,
118
			PreloadNonce:     preloadNonce,
119
		})
120
121
		c.Set("preloadNonce", preloadNonce)
122
		c.Set("htmxNonce", htmxNonce)
123
		c.Set("twNonce", twNonce)
124
		c.Set("hyperscriptNonce", hyperscriptNonce)
125
126
		return next(c)
127
	}
128
}
129
130
func StaticAssetsCacheControlMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
131
	return func(c echo.Context) error {
132
		c.Response().Header().Set("Cache-Control", "public, max-age=31536000")
133
		return next(c)
134
	}
135
}
136
137
func StaticPageCacheControlMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
138
	return func(c echo.Context) error {
139
		// Set Cache-Control header
140
		c.Response().Header().Set("Cache-Control", "private, max-age=60")
141
142
		return next(c)
143
	}
144
}
145