aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/auth/provider.go6
-rw-r--r--pkg/config/site.go5
-rw-r--r--pkg/html/password.go47
-rw-r--r--pkg/server/serve.go79
-rw-r--r--pkg/site/fs.go6
-rw-r--r--pkg/site/site.go11
6 files changed, 141 insertions, 13 deletions
diff --git a/pkg/auth/provider.go b/pkg/auth/provider.go
index 0d515ab..e521c8c 100644
--- a/pkg/auth/provider.go
+++ b/pkg/auth/provider.go
@@ -1,6 +1,7 @@
package auth
import (
+ "crypto/rand"
"time"
"github.com/golang-jwt/jwt/v5"
@@ -11,9 +12,12 @@ type Authenticator struct {
parser *jwt.Parser
}
-func NewAuthenticator(secretKey []byte) *Authenticator {
+func NewAuthenticator() *Authenticator {
parser := jwt.NewParser(jwt.WithIssuer("scrapbook"), jwt.WithExpirationRequired())
+ secretKey := make([]byte, 32)
+ rand.Read(secretKey)
+
a := &Authenticator{
secretKey: secretKey,
parser: parser,
diff --git a/pkg/config/site.go b/pkg/config/site.go
index 4b36f1a..bcdbe86 100644
--- a/pkg/config/site.go
+++ b/pkg/config/site.go
@@ -17,9 +17,10 @@ const (
)
type SiteConfig struct {
- Host string
+ Flags SiteFlag `scfg:"flags"`
- Flags SiteFlag
+ Host string `scfg:"host"`
+ Password string `scfg:"password"`
}
func ReadSiteConfig(filePath string, dst *SiteConfig) error {
diff --git a/pkg/html/password.go b/pkg/html/password.go
new file mode 100644
index 0000000..ba23a06
--- /dev/null
+++ b/pkg/html/password.go
@@ -0,0 +1,47 @@
+package html
+
+import (
+ "net/url"
+
+ . "github.com/LMBishop/scrapbook/web/skeleton"
+ . "maragu.dev/gomponents"
+ . "maragu.dev/gomponents/html"
+)
+
+func AuthenticateSitePage(err, redirect, siteName string) Node {
+ return Page("Authenticate",
+ H1(Text("A password is required to visit this site")),
+
+ If(err != "", AlertError(err)),
+
+ Form(
+ Action("/authenticate?redirect="+url.QueryEscape(redirect)),
+ Method("post"),
+
+ FieldSet(
+ Legend(Text("Authentication")),
+ Label(
+ For("password"),
+ Text("Password"),
+ ),
+ Input(
+ ID("password"),
+ Name("password"),
+ Type("password"),
+ ),
+ Span(
+ Class("form-help"),
+ Text("Enter the password to continue."),
+ ),
+ ),
+
+ Div(
+ Class("control-group group-right"),
+ Input(
+ Type("submit"),
+ Value("Submit"),
+ ),
+ ),
+ ),
+ )
+}
diff --git a/pkg/server/serve.go b/pkg/server/serve.go
index c938870..84d0a21 100644
--- a/pkg/server/serve.go
+++ b/pkg/server/serve.go
@@ -1,10 +1,16 @@
package server
import (
+ "crypto/subtle"
+ "fmt"
"net/http"
+ "net/url"
+ "strings"
+ "github.com/LMBishop/scrapbook/pkg/config"
"github.com/LMBishop/scrapbook/pkg/html"
"github.com/LMBishop/scrapbook/pkg/index"
+ "github.com/LMBishop/scrapbook/pkg/site"
)
func ServeSite(siteIndex *index.SiteIndex) func(w http.ResponseWriter, r *http.Request) {
@@ -16,6 +22,79 @@ func ServeSite(siteIndex *index.SiteIndex) func(w http.ResponseWriter, r *http.R
return
}
+ if site.SiteConfig.Flags&config.FlagDisable != 0 {
+ w.WriteHeader(http.StatusForbidden)
+ html.ForbiddenDisabledPage(site.SiteConfig.Host).Render(w)
+ return
+ }
+
+ if site.SiteConfig.Flags&config.FlagPassword != 0 {
+ jwt, err := r.Cookie("session")
+ if err != nil {
+ goto deny
+ }
+
+ err = site.Authenticator.VerifyJwt(jwt.Value)
+ if err != nil {
+ goto deny
+ }
+
+ goto permit
+
+ deny:
+ if strings.HasPrefix(r.URL.Path, "/authenticate") {
+ goto ask
+ }
+ http.Redirect(w, r, "/authenticate?redirect="+url.QueryEscape(r.URL.Path), 302)
+ return
+
+ ask:
+ handleAsk(w, r, site)
+ return
+
+ permit:
+ }
+
site.Handler.ServeHTTP(w, r)
}
}
+
+func handleAsk(w http.ResponseWriter, r *http.Request, site *site.Site) {
+ redirect := r.URL.Query().Get("redirect")
+
+ switch r.Method {
+ case "GET":
+ html.AuthenticateSitePage("", redirect, site.Name).Render(w)
+ case "POST":
+ err := r.ParseForm()
+ if err != nil {
+ html.AuthenticateSitePage(err.Error(), redirect, site.Name).Render(w)
+ return
+ }
+
+ password := r.Form.Get("password")
+
+ if len(site.SiteConfig.Password) == 0 || subtle.ConstantTimeCompare([]byte(password), []byte(site.SiteConfig.Password)) != 1 {
+ html.AuthenticateSitePage("The password is incorrect", redirect, site.Name).Render(w)
+ return
+ }
+
+ jwt, err := site.Authenticator.NewJwt()
+ if err != nil {
+ html.AuthenticateSitePage(fmt.Errorf("Failed to create jwt: %w", err).Error(), redirect, site.Name).Render(w)
+ return
+ }
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "session",
+ Value: jwt,
+
+ Secure: true,
+ SameSite: http.SameSiteStrictMode,
+ HttpOnly: true,
+ })
+ http.Redirect(w, r, redirect, 302)
+ default:
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ }
+}
diff --git a/pkg/site/fs.go b/pkg/site/fs.go
index ec57318..4542344 100644
--- a/pkg/site/fs.go
+++ b/pkg/site/fs.go
@@ -21,12 +21,6 @@ func NewSiteFileServer(root http.FileSystem, siteConfig *config.SiteConfig) *Sit
}
func (fs *SiteFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if fs.siteConfig.Flags&config.FlagDisable != 0 {
- w.WriteHeader(http.StatusForbidden)
- html.ForbiddenDisabledPage(fs.siteConfig.Host).Render(w)
- return
- }
-
path := filepath.Clean(r.URL.Path)
var info os.FileInfo
diff --git a/pkg/site/site.go b/pkg/site/site.go
index 8b7af41..e15480f 100644
--- a/pkg/site/site.go
+++ b/pkg/site/site.go
@@ -12,6 +12,7 @@ import (
"strings"
"time"
+ "github.com/LMBishop/scrapbook/pkg/auth"
"github.com/LMBishop/scrapbook/pkg/config"
)
@@ -19,10 +20,11 @@ const versionRegex = "[0-9]{4}_[0-9]{2}_[0-9]{2}_[0-9]{2}_[0-9]{2}_[0-9]{2}"
const timeFormat = "2006_01_02_15_04_05"
type Site struct {
- Name string
- Path string
- Handler http.Handler
- SiteConfig *config.SiteConfig
+ Name string
+ Path string
+ Handler http.Handler
+ Authenticator *auth.Authenticator
+ SiteConfig *config.SiteConfig
}
func NewSite(name string, dir string, config *config.SiteConfig) *Site {
@@ -30,6 +32,7 @@ func NewSite(name string, dir string, config *config.SiteConfig) *Site {
site.Name = name
site.Path = dir
site.SiteConfig = config
+ site.Authenticator = auth.NewAuthenticator()
site.Handler = NewSiteFileServer(http.Dir(path.Join(dir, "default")), config)
return &site
}