Skip to content

Commit

Permalink
add security middleware (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
broneks authored Nov 25, 2024
1 parent 457c2da commit 27c9af5
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 17 deletions.
13 changes: 13 additions & 0 deletions api/middleware/cache_control.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package middleware

import "github.com/labstack/echo/v4"

func CacheControl() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set("Cache-Control", "private, max-age=3600") // 1 hour in seconds

return next(c)
}
}
}
27 changes: 27 additions & 0 deletions api/middleware/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package middleware

import (
"net/http"
"regexp"

"github.com/labstack/echo/v4"
echoMiddleware "github.com/labstack/echo/v4/middleware"
)

func allowOrigin(origin string) (bool, error) {
return regexp.MatchString(`^https:\/\/piccolo.pics$`, origin)
}

func Cors() echo.MiddlewareFunc {
return echoMiddleware.CORSWithConfig(echoMiddleware.CORSConfig{
Skipper: Skipper,
AllowOriginFunc: allowOrigin,
AllowMethods: []string{
http.MethodGet,
http.MethodPatch,
http.MethodPut,
http.MethodPost,
http.MethodDelete,
},
})
}
16 changes: 16 additions & 0 deletions api/middleware/csrf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package middleware

import (
"github.com/labstack/echo/v4"
echoMiddleware "github.com/labstack/echo/v4/middleware"
)

func Csrf() echo.MiddlewareFunc {
return echoMiddleware.CSRFWithConfig(echoMiddleware.CSRFConfig{
Skipper: Skipper,
TokenLookup: "form:csrf",
CookieName: "_csrf",
CookieSecure: true,
CookieHTTPOnly: true,
})
}
22 changes: 22 additions & 0 deletions api/middleware/https_redirect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package middleware

import (
"net/http"

"github.com/labstack/echo/v4"
echoMiddleware "github.com/labstack/echo/v4/middleware"
)

func HttpsRedirect() echo.MiddlewareFunc {
return echoMiddleware.HTTPSRedirectWithConfig(echoMiddleware.RedirectConfig{
Skipper: Skipper,
Code: http.StatusMovedPermanently,
})
}

func HttpsNonWWWRedirect() echo.MiddlewareFunc {
return echoMiddleware.HTTPSNonWWWRedirectWithConfig(echoMiddleware.RedirectConfig{
Skipper: Skipper,
Code: http.StatusMovedPermanently,
})
}
51 changes: 51 additions & 0 deletions api/middleware/secure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package middleware

import (
"crypto/rand"
"encoding/base64"
"fmt"

"github.com/labstack/echo/v4"
echoMiddleware "github.com/labstack/echo/v4/middleware"
)

func generateNonce() string {
nonce := make([]byte, 16)

_, err := rand.Read(nonce)
if err != nil {
panic("failed to generate nonce")
}

return base64.StdEncoding.EncodeToString(nonce)
}

func Secure() echo.MiddlewareFunc {
nonce := generateNonce()

secureMiddlewareFunc := echoMiddleware.SecureWithConfig(echoMiddleware.SecureConfig{
Skipper: echoMiddleware.DefaultSkipper,
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
ContentSecurityPolicy: fmt.Sprintf(`
default-src 'none';
script-src 'self' 'nonce-%s' https://cdn.tailwindcss.com;
connect-src 'self';
img-src 'self' https://f005.backblazeb2.com data:;
style-src 'self' 'unsafe-inline';
font-src 'self';
frame-src 'none';
base-uri 'self';
form-action 'self';`, nonce),
XFrameOptions: "SAMEORIGIN",
HSTSPreloadEnabled: false,
})

return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set("nonce", nonce)

return secureMiddlewareFunc(next)(c)
}
}
}
13 changes: 13 additions & 0 deletions api/middleware/skipper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package middleware

import (
"os"

"github.com/labstack/echo/v4"
)

func Skipper(c echo.Context) bool {
env := os.Getenv("ENV")

return env == "local"
}
12 changes: 6 additions & 6 deletions api/page/handle_reset_password_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func handleGetResetPasswordPage() echo.HandlerFunc {
return func(c echo.Context) error {
token := c.QueryParam("token")

pageInfo := NewPageInfo(c, "Reset Password")

return c.Render(http.StatusOK, "reset_password.html", &ResetPasswordPayload{
PageInfo: PageInfo{
Title: "Reset Password",
},
PageInfo: pageInfo,
Token: token,
Success: false,
Error: "",
Expand Down Expand Up @@ -62,10 +62,10 @@ func handlePostResetPasswordPage(authService *authservice.AuthService) echo.Hand
}
}

pageInfo := NewPageInfo(c, "Reset Password")

return c.Render(http.StatusOK, "reset_password.html", &ResetPasswordPayload{
PageInfo: PageInfo{
Title: "Reset Password",
},
PageInfo: pageInfo,
Token: token,
Success: success,
Error: error,
Expand Down
6 changes: 3 additions & 3 deletions api/page/handle_shared_album_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ func handleSharedAlbumPage(server *types.Server, sharedAlbumRepo *sharedalbumrep
}
}

pageInfo := NewPageInfo(c, album.Name.String)

return c.Render(http.StatusOK, "shared_album.html", &SharedAlbumPayload{
PageInfo: PageInfo{
Title: album.Name.String,
},
PageInfo: pageInfo,
Album: album,
CoverPhoto: coverPhoto,
Photos: photosWithUrl,
Expand Down
32 changes: 32 additions & 0 deletions api/page/page_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package page

import (
"os"

"github.com/labstack/echo/v4"
echoMiddleware "github.com/labstack/echo/v4/middleware"
)

type PageInfo struct {
Nonce string
CsrfToken string
Title string
}

func NewPageInfo(c echo.Context, title string) PageInfo {
env := os.Getenv("ENV")

nonce := c.Get("nonce").(string)

var csrfToken string

if env != "local" {
csrfToken = c.Get(echoMiddleware.DefaultCSRFConfig.ContextKey).(string)
}

return PageInfo{
Nonce: nonce,
CsrfToken: csrfToken,
Title: title,
}
}
5 changes: 0 additions & 5 deletions api/page/types.go

This file was deleted.

9 changes: 8 additions & 1 deletion api/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,17 @@ func Start() {

e := echo.New()

e.Pre(middleware.HttpsRedirect())
e.Pre(middleware.HttpsNonWWWRedirect())
e.Pre(echoMiddleware.RemoveTrailingSlash())

e.Use(middleware.Logger())
e.Use(middleware.CacheControl())
e.Use(middleware.Cors())
e.Use(middleware.Csrf())
e.Use(middleware.Secure())
e.Use(echoMiddleware.Recover())
e.Use(echoMiddleware.RequestID())
e.Use(echoMiddleware.Secure())

e.Static("/", "static")

Expand Down
2 changes: 1 addition & 1 deletion api/storage/backblaze/get_presigned_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"time"
)

const expirationDuration = time.Hour * 24
const expirationDuration = time.Hour
const fileNotFoundImageUrl = "/image-not-found.png"

func (bc *BackblazeClient) GetPresignedUrl(ctx context.Context, filename, userId string) (string, time.Duration) {
Expand Down
13 changes: 13 additions & 0 deletions templates/400.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>400 - Bad Request</title>
</head>
<body>
<section style="margin: 10% 0; text-align: center;">
<h1>400 - Bad Request</h1>
</section>
</body>
</html>
2 changes: 1 addition & 1 deletion templates/partials_head.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.PageInfo.Title}}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
<script nonce="{{ .PageInfo.Nonce }}">
tailwind.config = {
theme: {
extend: {
Expand Down
1 change: 1 addition & 0 deletions templates/reset_password.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<h2 class="text-2xl font-semibold text-center text-gray-700 mb-6">Reset Password</h2>

<form action="" method="POST" novalidate>
<input type="hidden" name="csrf" value="{{ .PageInfo.CsrfToken }}">
<input type="hidden" name="token" value="{{ .Token }}">

{{ if .Error }}
Expand Down

0 comments on commit 27c9af5

Please sign in to comment.