From a07f747eb186a655254c6e980e75046f8e638ef2 Mon Sep 17 00:00:00 2001 From: James Mills Date: Wed, 5 Oct 2022 11:11:40 +1000 Subject: [PATCH] Remove a bunch of unused cruft --- cmd/spyda/main.go | 6 - go.mod | 4 +- go.sum | 22 --- internal/api.go | 158 ----------------- internal/auth/manager.go | 70 -------- internal/auth_handlers.go | 99 ----------- internal/bitcask_store.go | 234 +------------------------ internal/config.go | 62 +------ internal/context.go | 31 ---- internal/crawler.go | 23 +-- internal/email.go | 63 +------ internal/handlers.go | 4 +- internal/manage_handlers.go | 190 -------------------- internal/options.go | 53 +----- internal/page_handlers.go | 7 +- internal/passwd_handlers.go | 194 -------------------- internal/passwords/passwords.go | 9 - internal/passwords/scrypt_passwords.go | 72 -------- internal/scraper.go | 3 +- internal/server.go | 96 +--------- internal/session_store.go | 90 ---------- internal/store.go | 30 +--- internal/templates.go | 1 - internal/templates/login.html | 44 ----- internal/utils.go | 8 - 25 files changed, 31 insertions(+), 1542 deletions(-) delete mode 100644 internal/api.go delete mode 100644 internal/auth/manager.go delete mode 100644 internal/auth_handlers.go delete mode 100644 internal/manage_handlers.go delete mode 100644 internal/passwd_handlers.go delete mode 100644 internal/passwords/passwords.go delete mode 100644 internal/passwords/scrypt_passwords.go delete mode 100644 internal/session_store.go delete mode 100644 internal/templates/login.html diff --git a/cmd/spyda/main.go b/cmd/spyda/main.go index 1cb0033..6533a31 100644 --- a/cmd/spyda/main.go +++ b/cmd/spyda/main.go @@ -180,8 +180,6 @@ func main() { internal.WithBaseURL(baseURL), // Administration - internal.WithAdminUser(adminUser), - internal.WithAdminPass(adminPass), internal.WithAdminName(adminName), internal.WithAdminEmail(adminEmail), @@ -189,9 +187,7 @@ func main() { internal.WithResultsPerPage(resultsPerPage), // Secrets - internal.WithAPISigningKey(apiSigningKey), internal.WithCookieSecret(cookieSecret), - internal.WithMagicLinkSecret(magiclinkSecret), // Email Setitngs internal.WithSMTPHost(smtpHost), @@ -202,8 +198,6 @@ func main() { // Timeouts internal.WithSessionExpiry(sessionExpiry), - internal.WithSessionCacheTTL(sessionCacheTTL), - internal.WithAPISessionTime(apiSessionTime), ) if err != nil { log.WithError(err).Fatal("error creating server") diff --git a/go.mod b/go.mod index c0950b0..0ee631b 100644 --- a/go.mod +++ b/go.mod @@ -17,14 +17,11 @@ require ( github.com/apex/log v1.9.0 github.com/blevesearch/bleve/v2 v2.0.1 github.com/creasty/defaults v1.5.1 - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v1.0.0 - github.com/elithrar/simple-scrypt v1.3.0 github.com/gabstv/merger v1.0.1 github.com/glycerine/go-unsnap-stream v0.0.0-20210130063903-47dfef350d96 // indirect github.com/go-mail/mail v2.3.1+incompatible github.com/go-shiori/go-readability v0.0.0-20201011032228-bdc871772408 - github.com/goccy/go-yaml v1.8.8 github.com/gocolly/colly/v2 v2.1.0 github.com/golang/snappy v0.0.2 // indirect github.com/gomarkdown/markdown v0.0.0-20201113031856-722100d81a8e @@ -57,6 +54,7 @@ require ( golang.org/x/exp v0.0.0-20210201131500-d352d2db2ceb // indirect golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect + golang.org/x/text v0.3.5 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 // indirect ) diff --git a/go.sum b/go.sum index f4723cf..733a3a2 100644 --- a/go.sum +++ b/go.sum @@ -181,7 +181,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -191,8 +190,6 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elithrar/simple-scrypt v1.3.0 h1:KIlOlxdoQf9JWKl5lMAJ28SY2URB0XTRDn2TckyzAZg= -github.com/elithrar/simple-scrypt v1.3.0/go.mod h1:U2XQRI95XHY0St410VE3UjT7vuKb1qPwrl/EJwEqnZo= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -203,8 +200,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -228,14 +223,6 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-shiori/dom v0.0.0-20201011032054-d6b74a54fe52 h1:wEe9mu6BOmGYT5yQ9ag5E38LHUMUv7/AFx0J8YNR8HI= github.com/go-shiori/dom v0.0.0-20201011032054-d6b74a54fe52/go.mod h1:aLEd5DGjh1qYKnJJ/tC5OL0f3CV4CMcreDOn4RpCmUc= github.com/go-shiori/go-readability v0.0.0-20201011032228-bdc871772408 h1:xq7Sck0bwvgp/WWw6tHFDn3dUTCQwWRWLudr+inH/gs= @@ -245,8 +232,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-yaml v1.8.8 h1:MGfRB1GeSn/hWXYWS2Pt67iC2GJNnebdIro01ddyucA= -github.com/goccy/go-yaml v1.8.8/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI= github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs= @@ -430,8 +415,6 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= @@ -441,14 +424,10 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= @@ -876,7 +855,6 @@ golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/api.go b/internal/api.go deleted file mode 100644 index 214b48a..0000000 --- a/internal/api.go +++ /dev/null @@ -1,158 +0,0 @@ -package internal - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - jwt "github.com/dgrijalva/jwt-go" - "github.com/julienschmidt/httprouter" - log "github.com/sirupsen/logrus" - - "git.mills.io/prologic/spyda/internal/passwords" -) - -// ContextKey ... -type ContextKey int - -const ( - TokenContextKey ContextKey = iota - UserContextKey -) - -var ( - // ErrInvalidCredentials is returned for invalid credentials against /auth - ErrInvalidCredentials = errors.New("error: invalid credentials") - - // ErrInvalidToken is returned for expired or invalid tokens used in Authorizeation headers - ErrInvalidToken = errors.New("error: invalid token") -) - -// API ... -type API struct { - router *Router - config *Config - db Store - pm passwords.Passwords -} - -// NewAPI ... -func NewAPI(router *Router, config *Config, db Store, pm passwords.Passwords) *API { - api := &API{router, config, db, pm} - - api.initRoutes() - - return api -} - -func (a *API) initRoutes() { - router := a.router.Group("/api/v1") - - router.GET("/ping", a.PingEndpoint()) -} - -// CreateToken ... -func (a *API) CreateToken(user *User, r *http.Request) (*Token, error) { - claims := jwt.MapClaims{} - claims["username"] = user.Username - createdAt := time.Now() - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString([]byte(a.config.APISigningKey)) - if err != nil { - log.WithError(err).Error("error creating signed token") - return nil, err - } - - signedToken, err := jwt.Parse(tokenString, a.jwtKeyFunc) - if err != nil { - log.WithError(err).Error("error creating signed token") - return nil, err - } - - tkn := &Token{ - Signature: signedToken.Signature, - Value: tokenString, - UserAgent: r.UserAgent(), - CreatedAt: createdAt, - } - - return tkn, nil -} - -func (a *API) jwtKeyFunc(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("There was an error") - } - return []byte(a.config.APISigningKey), nil -} - -func (a *API) getLoggedInUser(r *http.Request) *User { - token, err := jwt.Parse(r.Header.Get("Token"), a.jwtKeyFunc) - if err != nil { - return nil - } - - if !token.Valid { - return nil - } - - claims := token.Claims.(jwt.MapClaims) - - username := claims["username"].(string) - - user, err := a.db.GetUser(username) - if err != nil { - log.WithError(err).Error("error loading user object") - return nil - } - - return user - -} - -func (a *API) isAuthorized(endpoint httprouter.Handle) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - if r.Header.Get("Token") == "" { - http.Error(w, "No Token Provided", http.StatusUnauthorized) - return - } - - token, err := jwt.Parse(r.Header.Get("Token"), a.jwtKeyFunc) - if err != nil { - log.WithError(err).Error("error parsing token") - http.Error(w, "Bad Request", http.StatusBadRequest) - return - } - - if token.Valid { - claims := token.Claims.(jwt.MapClaims) - - username := claims["username"].(string) - - user, err := a.db.GetUser(username) - if err != nil { - log.WithError(err).Error("error loading user object") - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - ctx := context.WithValue(r.Context(), TokenContextKey, token) - ctx = context.WithValue(ctx, UserContextKey, user) - - endpoint(w, r.WithContext(ctx), p) - } else { - http.Error(w, "Invalid Token", http.StatusUnauthorized) - return - } - } -} - -// PingEndpoint ... -func (a *API) PingEndpoint() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{}`)) - } -} diff --git a/internal/auth/manager.go b/internal/auth/manager.go deleted file mode 100644 index aef2c6e..0000000 --- a/internal/auth/manager.go +++ /dev/null @@ -1,70 +0,0 @@ -package auth - -import ( - "net/http" - - "github.com/julienschmidt/httprouter" - - "git.mills.io/prologic/spyda/internal/session" -) - -// Options ... -type Options struct { - login string - register string -} - -// NewOptions ... -func NewOptions(login, register string) *Options { - return &Options{login, register} -} - -// Manager ... -type Manager struct { - options *Options -} - -// NewManager ... -func NewManager(options *Options) *Manager { - return &Manager{options} -} - -// MustAuth ... -func (m *Manager) MustAuth(next httprouter.Handle) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - if sess := r.Context().Value(session.SessionKey); sess != nil { - if _, ok := sess.(*session.Session).Get("username"); ok { - next(w, r, p) - return - } - } - - http.Redirect(w, r, m.options.login, http.StatusFound) - } -} - -// ShouldAuth ... -func (m *Manager) ShouldAuth(next httprouter.Handle) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - if sess := r.Context().Value(session.SessionKey); sess != nil { - if _, ok := sess.(*session.Session).Get("username"); ok { - next(w, r, p) - return - } - } - - http.Redirect(w, r, m.options.login, http.StatusFound) - } -} - -func (m *Manager) HasAuth(next httprouter.Handle) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - if sess := r.Context().Value(session.SessionKey); sess != nil { - if _, ok := sess.(*session.Session).Get("username"); ok { - http.Redirect(w, r, "/", http.StatusFound) - return - } - } - next(w, r, p) - } -} diff --git a/internal/auth_handlers.go b/internal/auth_handlers.go deleted file mode 100644 index d4e8b79..0000000 --- a/internal/auth_handlers.go +++ /dev/null @@ -1,99 +0,0 @@ -package internal - -import ( - "net/http" - "time" - - "github.com/julienschmidt/httprouter" - log "github.com/sirupsen/logrus" - - "git.mills.io/prologic/spyda/internal/session" -) - -// LoginHandler ... -func (s *Server) LoginHandler() httprouter.Handle { - // #239: Throttle failed login attempts and lock user account. - failures := NewTTLCache(5 * time.Minute) - - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if r.Method == "GET" { - s.render("login", w, ctx) - return - } - - username := NormalizeUsername(r.FormValue("username")) - password := r.FormValue("password") - rememberme := r.FormValue("rememberme") == "on" - - // Error: no username or password provided - if username == "" || password == "" { - log.Warn("no username or password provided") - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - // Lookup user - user, err := s.db.GetUser(username) - if err != nil { - ctx.Error = true - ctx.Message = "Invalid username! Hint: Register an account?" - s.render("error", w, ctx) - return - } - - // #239: Throttle failed login attempts and lock user account. - if failures.Get(user.Username) > MaxFailedLogins { - ctx.Error = true - ctx.Message = "Too many failed login attempts. Account temporarily locked! Please try again later." - s.render("error", w, ctx) - return - } - - // Validate cleartext password against KDF hash - err = s.pm.CheckPassword(user.Password, password) - if err != nil { - // #239: Throttle failed login attempts and lock user account. - failed := failures.Inc(user.Username) - time.Sleep(time.Duration(IntPow(2, failed)) * time.Second) - - ctx.Error = true - ctx.Message = "Invalid password! Hint: Reset your password?" - s.render("error", w, ctx) - return - } - - // #239: Throttle failed login attempts and lock user account. - failures.Reset(user.Username) - - // Login successful - log.Infof("login successful: %s", username) - - // Lookup session - sess := r.Context().Value(session.SessionKey) - if sess == nil { - log.Warn("no session found") - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - // Authorize session - _ = sess.(*session.Session).Set("username", username) - - // Persist session? - if rememberme { - _ = sess.(*session.Session).Set("persist", "1") - } - - http.Redirect(w, r, RedirectRefererURL(r, s.config, "/"), http.StatusFound) - } -} - -// LogoutHandler ... -func (s *Server) LogoutHandler() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - s.sm.Delete(w, r) - http.Redirect(w, r, "/", http.StatusFound) - } -} diff --git a/internal/bitcask_store.go b/internal/bitcask_store.go index 1284ff3..31d2bd8 100644 --- a/internal/bitcask_store.go +++ b/internal/bitcask_store.go @@ -2,20 +2,13 @@ package internal import ( "fmt" - "strings" "git.mills.io/prologic/bitcask" log "github.com/sirupsen/logrus" - - "git.mills.io/prologic/spyda/internal/session" ) const ( - feedsKeyPrefix = "/feeds" - sessionsKeyPrefix = "/sessions" - usersKeyPrefix = "/users" - urlsKeyPrefix = "/urls" - tokensKeyPrefix = "/tokens" + urlsKeyPrefix = "/urls" ) // BitcaskStore ... @@ -68,89 +61,6 @@ func (bs *BitcaskStore) Merge() error { return nil } -func (bs *BitcaskStore) HasUser(username string) bool { - key := []byte(fmt.Sprintf("%s/%s", usersKeyPrefix, username)) - return bs.db.Has(key) -} - -func (bs *BitcaskStore) DelUser(username string) error { - key := []byte(fmt.Sprintf("%s/%s", usersKeyPrefix, username)) - return bs.db.Delete(key) -} - -func (bs *BitcaskStore) GetUser(username string) (*User, error) { - key := []byte(fmt.Sprintf("%s/%s", usersKeyPrefix, username)) - data, err := bs.db.Get(key) - if err == bitcask.ErrKeyNotFound { - return nil, ErrUserNotFound - } - return LoadUser(data) -} - -func (bs *BitcaskStore) SetUser(username string, user *User) error { - data, err := user.Bytes() - if err != nil { - return err - } - - key := []byte(fmt.Sprintf("%s/%s", usersKeyPrefix, username)) - if err := bs.db.Put(key, data); err != nil { - return err - } - return nil -} - -func (bs *BitcaskStore) LenUsers() int64 { - var count int64 - - if err := bs.db.Scan([]byte(usersKeyPrefix), func(_ []byte) error { - count++ - return nil - }); err != nil { - log.WithError(err).Error("error scanning") - } - - return count -} - -func (bs *BitcaskStore) SearchUsers(prefix string) []string { - var keys []string - - if err := bs.db.Scan([]byte(usersKeyPrefix), func(key []byte) error { - if strings.HasPrefix(strings.ToLower(string(key)), prefix) { - keys = append(keys, strings.TrimPrefix(string(key), "/users/")) - } - return nil - }); err != nil { - log.WithError(err).Error("error scanning") - } - - return keys -} - -func (bs *BitcaskStore) GetAllUsers() ([]*User, error) { - var users []*User - - err := bs.db.Scan([]byte(usersKeyPrefix), func(key []byte) error { - data, err := bs.db.Get(key) - if err != nil { - return err - } - - user, err := LoadUser(data) - if err != nil { - return err - } - users = append(users, user) - return nil - }) - if err != nil { - return nil, err - } - - return users, nil -} - func (bs *BitcaskStore) HasURL(hash string) bool { key := []byte(fmt.Sprintf("%s/%s", urlsKeyPrefix, hash)) return bs.db.Has(key) @@ -216,145 +126,3 @@ func (bs *BitcaskStore) ForEachURL(f func(url *URL) error) error { return nil } - -func (bs *BitcaskStore) GetSession(sid string) (*session.Session, error) { - key := []byte(fmt.Sprintf("%s/%s", sessionsKeyPrefix, sid)) - data, err := bs.db.Get(key) - if err != nil { - if err == bitcask.ErrKeyNotFound { - return nil, session.ErrSessionNotFound - } - return nil, err - } - sess := session.NewSession(bs) - if err := session.LoadSession(data, sess); err != nil { - return nil, err - } - return sess, nil -} - -func (bs *BitcaskStore) SetSession(sid string, sess *session.Session) error { - key := []byte(fmt.Sprintf("%s/%s", sessionsKeyPrefix, sid)) - - data, err := sess.Bytes() - if err != nil { - return err - } - - return bs.db.Put(key, data) -} - -func (bs *BitcaskStore) HasSession(sid string) bool { - key := []byte(fmt.Sprintf("%s/%s", sessionsKeyPrefix, sid)) - return bs.db.Has(key) -} - -func (bs *BitcaskStore) DelSession(sid string) error { - key := []byte(fmt.Sprintf("%s/%s", sessionsKeyPrefix, sid)) - return bs.db.Delete(key) -} - -func (bs *BitcaskStore) SyncSession(sess *session.Session) error { - // Only persist sessions with a logged in user associated with an account - // This saves resources as we don't need to keep session keys around for - // sessions we may never load from the store again. - if sess.Has("username") { - return bs.SetSession(sess.ID, sess) - } - return nil -} - -func (bs *BitcaskStore) LenSessions() int64 { - var count int64 - - if err := bs.db.Scan([]byte(sessionsKeyPrefix), func(_ []byte) error { - count++ - return nil - }); err != nil { - log.WithError(err).Error("error scanning") - } - - return count -} - -func (bs *BitcaskStore) GetAllSessions() ([]*session.Session, error) { - var sessions []*session.Session - - err := bs.db.Scan([]byte(sessionsKeyPrefix), func(key []byte) error { - data, err := bs.db.Get(key) - if err != nil { - return err - } - - sess := session.NewSession(bs) - if err := session.LoadSession(data, sess); err != nil { - return err - } - sessions = append(sessions, sess) - return nil - }) - if err != nil { - return nil, err - } - - return sessions, nil -} - -func (bs *BitcaskStore) GetUserTokens(user *User) ([]*Token, error) { - tokens := []*Token{} - for _, signature := range user.Tokens { - tkn, err := bs.GetToken(signature) - if err != nil { - return tokens, err - } - - tokens = append(tokens, tkn) - } - - return tokens, nil -} - -func (bs *BitcaskStore) GetToken(signature string) (*Token, error) { - key := []byte(fmt.Sprintf("%s/%s", tokensKeyPrefix, signature)) - data, err := bs.db.Get(key) - if err == bitcask.ErrKeyNotFound { - return nil, ErrTokenNotFound - } - tkn, err := LoadToken(data) - if err != nil { - return nil, err - } - - return tkn, nil -} - -func (bs *BitcaskStore) SetToken(signature string, tkn *Token) error { - data, err := tkn.Bytes() - if err != nil { - return err - } - - key := []byte(fmt.Sprintf("%s/%s", tokensKeyPrefix, signature)) - if err := bs.db.Put(key, data); err != nil { - return err - } - return nil -} - -func (bs *BitcaskStore) DelToken(signature string) error { - key := []byte(fmt.Sprintf("%s/%s", tokensKeyPrefix, signature)) - return bs.db.Delete(key) -} - -func (bs *BitcaskStore) LenTokens() int64 { - var count int64 - - if err := bs.db.Scan([]byte(tokensKeyPrefix), func(_ []byte) error { - count++ - return nil - }); err != nil { - log.WithError(err).Error("error scanning") - } - - return count -} diff --git a/internal/config.go b/internal/config.go index 3bfd901..6946f68 100644 --- a/internal/config.go +++ b/internal/config.go @@ -3,15 +3,12 @@ package internal import ( "errors" "fmt" - "io/ioutil" "math/rand" "net/url" - "os" "strings" "time" "github.com/gabstv/merger" - "github.com/goccy/go-yaml" log "github.com/sirupsen/logrus" ) @@ -35,21 +32,15 @@ type Config struct { Store string Theme string BaseURL string - AdminUser string - AdminPass string AdminName string AdminEmail string SearchPrompts []string ResultsPerPage int - APISessionTime time.Duration - SessionExpiry time.Duration - SessionCacheTTL time.Duration + SessionExpiry time.Duration - APISigningKey string - CookieSecret string - MagicLinkSecret string + CookieSecret string SMTPHost string SMTPPort int @@ -94,55 +85,8 @@ func (c *Config) Validate() error { } if c.CookieSecret == InvalidConfigValue { - return fmt.Errorf("error: COOKIE_SECRET is not configured!") - } - - if c.MagicLinkSecret == InvalidConfigValue { - return fmt.Errorf("error: MAGICLINK_SECRET is not configured!") - } - - if c.APISigningKey == InvalidConfigValue { - return fmt.Errorf("error: API_SIGNING_KEY is not configured!") + return fmt.Errorf("error: COOKIE_SECRET is not configured") } return nil } - -// LoadSettings loads pod settings from the given path -func LoadSettings(path string) (*Settings, error) { - var settings Settings - - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - if err := yaml.Unmarshal(data, &settings); err != nil { - return nil, err - } - - return &settings, nil -} - -// Save saves the pod settings to the given path -func (s *Settings) Save(path string) error { - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - - data, err := yaml.MarshalWithOptions(s, yaml.Indent(4)) - if err != nil { - return err - } - - if _, err = f.Write(data); err != nil { - return err - } - - if err = f.Sync(); err != nil { - return err - } - - return f.Close() -} diff --git a/internal/context.go b/internal/context.go index e32a83c..ca1187c 100644 --- a/internal/context.go +++ b/internal/context.go @@ -5,13 +5,11 @@ import ( "net/http" "time" - log "github.com/sirupsen/logrus" "github.com/vcraescu/go-paginator" "github.com/justinas/nosurf" "git.mills.io/prologic/spyda" - "git.mills.io/prologic/spyda/internal/session" ) type Meta struct { @@ -92,34 +90,5 @@ func NewContext(conf *Config, db Store, req *http.Request) *Context { ctx.CSRFToken = nosurf.Token(req) - if sess := req.Context().Value(session.SessionKey); sess != nil { - if username, ok := sess.(*session.Session).Get("username"); ok { - ctx.Authenticated = true - ctx.Username = username - } - } - - if ctx.Authenticated && ctx.Username != "" { - user, err := db.GetUser(ctx.Username) - if err != nil { - log.WithError(err).Warnf("error loading user object for %s", ctx.Username) - } - - ctx.User = user - - tokens, err := db.GetUserTokens(user) - if err != nil { - log.WithError(err).Warnf("error loading tokens for %s", ctx.Username) - } - ctx.Tokens = tokens - - } else { - ctx.User = &User{} - } - - if ctx.Username == conf.AdminUser { - ctx.IsAdmin = true - } - return ctx } diff --git a/internal/crawler.go b/internal/crawler.go index 0779364..de834d2 100644 --- a/internal/crawler.go +++ b/internal/crawler.go @@ -29,21 +29,14 @@ func NewCrawler(conf *Config, tasks *Dispatcher, db Store, indexer Indexer) (Cra } func (c *crawler) loop() { - for { - select { - case url, ok := <-c.queue: - if !ok { - log.Debugf("crawler shutting down...") - return - } - log.Debugf("crawling %s", url) - uuid, err := c.tasks.Dispatch(NewCrawlTask(c.conf, c.db, c.indexer, url)) - if err != nil { - log.WithError(err).Error("error creating crawl task for %s", url) - } else { - taskURL := URLForTask(c.conf.BaseURL, uuid) - log.WithField("uuid", uuid).Infof("successfully created crawl task for %s: %s", url, taskURL) - } + for url := range c.queue { + log.Debugf("crawling %s", url) + uuid, err := c.tasks.Dispatch(NewCrawlTask(c.conf, c.db, c.indexer, url)) + if err != nil { + log.WithError(err).Error("error creating crawl task for %s", url) + } else { + taskURL := URLForTask(c.conf.BaseURL, uuid) + log.WithField("uuid", uuid).Infof("successfully created crawl task for %s: %s", url, taskURL) } } } diff --git a/internal/email.go b/internal/email.go index 6377171..fe361b1 100644 --- a/internal/email.go +++ b/internal/email.go @@ -14,24 +14,9 @@ import ( var ( ErrSendingEmail = errors.New("error: unable to send email") - passwordResetEmailTemplate = template.Must(template.New("email").Parse(`Hello {{ .Username }}, + supportRequestEmailTemplate = template.Must(template.New("email").Parse(`Hello, -You have requested to have your password on {{ .Pod }} reset for your account. - -**IMPORTANT:** If this was __NOT__ initiated by you, please ignore this email and contract support! - -To reset your password, please visit the following link: - -{{ .BaseURL}}/newPassword?token={{ .Token }} - -Kind regards, - -{{ .Pod}} Support -`)) - - supportRequestEmailTemplate = template.Must(template.New("email").Parse(`Hello {{ .AdminUser }}, - -{{ .Name }} <{{ .Email }} from {{ .Pod }} has sent the following support request: +{{ .Name }} <{{ .Email }} from {{ .Instance }} has sent the following support request: > Subject: {{ .Subject }} > @@ -39,21 +24,12 @@ Kind regards, Kind regards, -{{ .Pod}} Support +{{ .Instance }} Support `)) ) -type PasswordResetEmailContext struct { - Pod string - BaseURL string - - Token string - Username string -} - type SupportRequestEmailContext struct { - Pod string - AdminUser string + Instance string Name string Email string @@ -96,34 +72,6 @@ func SendEmail(conf *Config, recipients []string, replyTo, subject string, body return nil } -func SendPasswordResetEmail(conf *Config, user *User, email, token string) error { - recipients := []string{email} - subject := fmt.Sprintf( - "[%s]: Password Reset Request for %s", - conf.Name, user.Username, - ) - ctx := PasswordResetEmailContext{ - Pod: conf.Name, - BaseURL: conf.BaseURL, - - Token: token, - Username: user.Username, - } - - buf := &bytes.Buffer{} - if err := passwordResetEmailTemplate.Execute(buf, ctx); err != nil { - log.WithError(err).Error("error rendering email template") - return err - } - - if err := SendEmail(conf, recipients, conf.SMTPFrom, subject, buf.String()); err != nil { - log.WithError(err).Errorf("error sending new token to %s", recipients[0]) - return err - } - - return nil -} - func SendSupportRequestEmail(conf *Config, name, email, subject, message string) error { recipients := []string{conf.AdminEmail, email} emailSubject := fmt.Sprintf( @@ -131,8 +79,7 @@ func SendSupportRequestEmail(conf *Config, name, email, subject, message string) conf.Name, subject, ) ctx := SupportRequestEmailContext{ - Pod: conf.Name, - AdminUser: conf.AdminUser, + Instance: conf.Name, Name: name, Email: email, diff --git a/internal/handlers.go b/internal/handlers.go index 4d89611..a89d7cb 100644 --- a/internal/handlers.go +++ b/internal/handlers.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" "html/template" - "io/ioutil" "net/http" + "os" "path/filepath" "strings" "time" @@ -99,7 +99,7 @@ func (s *Server) CacheHandler() httprouter.Handle { var entry Entry - data, err := ioutil.ReadFile(fn) + data, err := os.ReadFile(fn) if err != nil { log.WithError(err).Error("error reading cached entry") http.Error(w, "Internal Server Error", http.StatusInternalServerError) diff --git a/internal/manage_handlers.go b/internal/manage_handlers.go deleted file mode 100644 index ab77b90..0000000 --- a/internal/manage_handlers.go +++ /dev/null @@ -1,190 +0,0 @@ -package internal - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - "time" - - "github.com/julienschmidt/httprouter" - "github.com/renstrom/shortuuid" - log "github.com/sirupsen/logrus" -) - -// ManageHandler ... -func (s *Server) ManageHandler() httprouter.Handle { - isAdminUser := IsAdminUserFactory(s.config) - - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if !isAdminUser(ctx.User) { - ctx.Error = true - ctx.Message = "You are not a Pod Owner!" - s.render("403", w, ctx) - return - } - - if r.Method == "GET" { - s.render("managePod", w, ctx) - return - } - - name := strings.TrimSpace(r.FormValue("podName")) - description := strings.TrimSpace(r.FormValue("podDescription")) - - // Update name - if name != "" { - s.config.Name = name - } else { - ctx.Error = true - ctx.Message = "" - s.render("error", w, ctx) - return - } - - // Update pod description - if description != "" { - s.config.Description = description - } else { - ctx.Error = true - ctx.Message = "" - s.render("error", w, ctx) - return - } - - // Save config file - if err := s.config.Settings().Save(filepath.Join(s.config.Data, "settings.yaml")); err != nil { - log.WithError(err).Error("error saving config") - os.Exit(1) - } - - ctx.Error = false - ctx.Message = "Pod updated successfully" - s.render("error", w, ctx) - } -} - -// ManageUsersHandler ... -func (s *Server) ManageUsersHandler() httprouter.Handle { - isAdminUser := IsAdminUserFactory(s.config) - - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if !isAdminUser(ctx.User) { - ctx.Error = true - ctx.Message = "You are not a Pod Owner!" - s.render("403", w, ctx) - return - } - - s.render("manageUsers", w, ctx) - - } -} - -// AddUserHandler ... -func (s *Server) AddUserHandler() httprouter.Handle { - isAdminUser := IsAdminUserFactory(s.config) - - return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if !isAdminUser(ctx.User) { - ctx.Error = true - ctx.Message = "You are not a Pod Owner!" - s.render("403", w, ctx) - return - } - - username := NormalizeUsername(r.FormValue("username")) - // XXX: We DO NOT store this! (EVER) - email := strings.TrimSpace(r.FormValue("email")) - - // Random password -- User is expected to user "Password Reset" - password := shortuuid.New() - - if err := ValidateUsername(username); err != nil { - ctx.Error = true - ctx.Message = fmt.Sprintf("Username validation failed: %s", err.Error()) - s.render("error", w, ctx) - return - } - - if s.db.HasUser(username) { - ctx.Error = true - ctx.Message = "User or Feed with that name already exists! Please pick another!" - s.render("error", w, ctx) - return - } - - hash, err := s.pm.CreatePassword(password) - if err != nil { - log.WithError(err).Error("error creating password hash") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - recoveryHash := fmt.Sprintf("email:%s", FastHash(email)) - - user := NewUser() - user.Username = username - user.Recovery = recoveryHash - user.Password = hash - user.CreatedAt = time.Now() - - if err := s.db.SetUser(username, user); err != nil { - log.WithError(err).Error("error saving user object for new user") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - ctx.Error = false - ctx.Message = "User successfully created" - s.render("error", w, ctx) - } -} - -// DelUserHandler ... -func (s *Server) DelUserHandler() httprouter.Handle { - isAdminUser := IsAdminUserFactory(s.config) - - return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if !isAdminUser(ctx.User) { - ctx.Error = true - ctx.Message = "You are not a Pod Owner!" - s.render("403", w, ctx) - return - } - - username := NormalizeUsername(r.FormValue("username")) - - user, err := s.db.GetUser(username) - if err != nil { - log.WithError(err).Errorf("error loading user object for %s", username) - ctx.Error = true - ctx.Message = "Error deleting account" - s.render("error", w, ctx) - return - } - - // Delete user - if err := s.db.DelUser(user.Username); err != nil { - ctx.Error = true - ctx.Message = "An error occured whilst deleting your account" - s.render("error", w, ctx) - return - } - - s.sm.Delete(w, r) - - ctx.Error = false - ctx.Message = "Successfully deleted account" - s.render("error", w, ctx) - } -} diff --git a/internal/options.go b/internal/options.go index 5ad9238..f4b1c2e 100644 --- a/internal/options.go +++ b/internal/options.go @@ -91,11 +91,8 @@ func NewConfig() *Config { Store: DefaultStore, Theme: DefaultTheme, BaseURL: DefaultBaseURL, - AdminUser: DefaultAdminUser, - AdminPass: DefaultAdminPass, - CookieSecret: DefaultCookieSecret, - MagicLinkSecret: DefaultMagicLinkSecret, + CookieSecret: DefaultCookieSecret, SearchPrompts: DefaultSearchPrompts, ResultsPerPage: DefaultResultsPerPage, @@ -149,22 +146,6 @@ func WithBaseURL(baseURL string) Option { } } -// WithAdminUser sets the Admin username -func WithAdminUser(adminUser string) Option { - return func(cfg *Config) error { - cfg.AdminUser = adminUser - return nil - } -} - -// WithAdminPass sets the Admin password -func WithAdminPass(adminPass string) Option { - return func(cfg *Config) error { - cfg.AdminPass = adminPass - return nil - } -} - // WithAdminName sets the Admin name used to identify the pod operator func WithAdminName(adminName string) Option { return func(cfg *Config) error { @@ -221,14 +202,6 @@ func WithResultsPerPage(resultsPerPage int) Option { } } -// WithSessionCacheTTL sets the server's session cache ttl -func WithSessionCacheTTL(cacheTTL time.Duration) Option { - return func(cfg *Config) error { - cfg.SessionCacheTTL = cacheTTL - return nil - } -} - // WithSessionExpiry sets the server's session expiry time func WithSessionExpiry(expiry time.Duration) Option { return func(cfg *Config) error { @@ -237,14 +210,6 @@ func WithSessionExpiry(expiry time.Duration) Option { } } -// WithMagicLinkSecret sets the MagicLinkSecert used to create password reset tokens -func WithMagicLinkSecret(secret string) Option { - return func(cfg *Config) error { - cfg.MagicLinkSecret = secret - return nil - } -} - // WithSMTPHost sets the SMTPHost to use for sending email func WithSMTPHost(host string) Option { return func(cfg *Config) error { @@ -284,19 +249,3 @@ func WithSMTPFrom(from string) Option { return nil } } - -// WithAPISessionTime sets the API session time for tokens -func WithAPISessionTime(duration time.Duration) Option { - return func(cfg *Config) error { - cfg.APISessionTime = duration - return nil - } -} - -// WithAPISigningKey sets the API JWT signing key for tokens -func WithAPISigningKey(key string) Option { - return func(cfg *Config) error { - cfg.APISigningKey = key - return nil - } -} diff --git a/internal/page_handlers.go b/internal/page_handlers.go index 05bd055..39c2fdd 100644 --- a/internal/page_handlers.go +++ b/internal/page_handlers.go @@ -10,7 +10,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "time" "github.com/gomarkdown/markdown" @@ -20,6 +19,8 @@ import ( "github.com/julienschmidt/httprouter" sync "github.com/sasha-s/go-deadlock" log "github.com/sirupsen/logrus" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) const pagesDir = "pages" @@ -104,6 +105,8 @@ func (s *Server) PageHandler(name string) httprouter.Handle { return page, nil } + caser := cases.Title(language.English) + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := NewContext(s.config, s.db, r) @@ -157,7 +160,7 @@ func (s *Server) PageHandler(name string) httprouter.Handle { if frontmatter.Title != "" { title = frontmatter.Title } else { - title = strings.Title(name) + title = caser.String(name) } ctx.Title = title ctx.Meta.Description = frontmatter.Description diff --git a/internal/passwd_handlers.go b/internal/passwd_handlers.go deleted file mode 100644 index 4e12bad..0000000 --- a/internal/passwd_handlers.go +++ /dev/null @@ -1,194 +0,0 @@ -package internal - -import ( - "fmt" - "net/http" - "strings" - "time" - - "github.com/dgrijalva/jwt-go" - "github.com/julienschmidt/httprouter" - log "github.com/sirupsen/logrus" -) - -// ResetPasswordHandler ... -func (s *Server) ResetPasswordHandler() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if r.Method == "GET" { - ctx.Title = "Reset password" - s.render("resetPassword", w, ctx) - return - } - - username := NormalizeUsername(r.FormValue("username")) - email := strings.TrimSpace(r.FormValue("email")) - recovery := fmt.Sprintf("email:%s", FastHash(email)) - - // Check if user exist - if !s.db.HasUser(username) { - ctx.Error = true - ctx.Message = "User not found!" - s.render("error", w, ctx) - return - } - - // Get user object from DB - user, err := s.db.GetUser(username) - if err != nil { - ctx.Error = true - ctx.Message = "Error loading user" - s.render("error", w, ctx) - return - } - - if recovery != user.Recovery { - ctx.Error = true - ctx.Message = "Error! The email address you supplied does not match what you registered with :/" - s.render("error", w, ctx) - return - } - - // Create magic link expiry time - now := time.Now() - secs := now.Unix() - expiresAfterSeconds := int64(600) // Link expires after 10 minutes - - expiryTime := secs + expiresAfterSeconds - - // Create magic link - token := jwt.NewWithClaims( - jwt.SigningMethodHS256, - jwt.MapClaims{"username": username, "expiresAt": expiryTime}, - ) - tokenString, err := token.SignedString([]byte(s.config.MagicLinkSecret)) - if err != nil { - ctx.Error = true - ctx.Message = err.Error() - s.render("error", w, ctx) - return - } - - if err := SendPasswordResetEmail(s.config, user, email, tokenString); err != nil { - log.WithError(err).Errorf("unable to send reset password email to %s", user.Username) - ctx.Error = true - ctx.Message = err.Error() - s.render("error", w, ctx) - return - } - - log.Infof("reset password email sent for %s", user.Username) - - // Show success msg - ctx.Error = false - ctx.Message = "Password request request sent! Please check your email and follow the instructions" - s.render("error", w, ctx) - } -} - -// ResetPasswordMagicLinkHandler ... -func (s *Server) ResetPasswordMagicLinkHandler() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - // Get token from query string - tokens, ok := r.URL.Query()["token"] - - // Check if valid token - if !ok || len(tokens[0]) < 1 { - ctx.Error = true - ctx.Message = "Invalid token" - s.render("error", w, ctx) - return - } - - tokenEmail := tokens[0] - ctx.PasswordResetToken = tokenEmail - - // Show newPassword page - s.render("newPassword", w, ctx) - } -} - -// NewPasswordHandler ... -func (s *Server) NewPasswordHandler() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - ctx := NewContext(s.config, s.db, r) - - if r.Method == "GET" { - return - } - - password := r.FormValue("password") - tokenEmail := r.FormValue("token") - - // Check if token is valid - token, err := jwt.Parse(tokenEmail, func(token *jwt.Token) (interface{}, error) { - - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - - return []byte(s.config.MagicLinkSecret), nil - }) - - if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { - - var username = fmt.Sprintf("%v", claims["username"]) - var expiresAt int = int(claims["expiresAt"].(float64)) - - now := time.Now() - secs := now.Unix() - - // Check token expiry - if secs > int64(expiresAt) { - ctx.Error = true - ctx.Message = "Token expires" - s.render("error", w, ctx) - return - } - - user, err := s.db.GetUser(username) - if err != nil { - ctx.Error = true - ctx.Message = "Error loading user" - s.render("error", w, ctx) - return - } - - // Reset password - if password != "" { - hash, err := s.pm.CreatePassword(password) - if err != nil { - ctx.Error = true - ctx.Message = "Error loading user" - s.render("error", w, ctx) - return - } - - user.Password = hash - - // Save user - if err := s.db.SetUser(username, user); err != nil { - ctx.Error = true - ctx.Message = "Error loading user" - s.render("error", w, ctx) - return - } - } - - log.Infof("password changed: %v", user) - - // Show success msg - ctx.Error = false - ctx.Message = "Password reset successfully." - s.render("error", w, ctx) - } else { - ctx.Error = true - ctx.Message = err.Error() - s.render("error", w, ctx) - return - } - } -} diff --git a/internal/passwords/passwords.go b/internal/passwords/passwords.go deleted file mode 100644 index 249207b..0000000 --- a/internal/passwords/passwords.go +++ /dev/null @@ -1,9 +0,0 @@ -package passwords - -// Passwords is an interface for creating and verifying secure passwords -// An implementation must implement all methods and it is up to the impl -// which underlying crypto to use for hasing cleartext passwrods. -type Passwords interface { - CreatePassword(password string) (string, error) - CheckPassword(hash, password string) error -} diff --git a/internal/passwords/scrypt_passwords.go b/internal/passwords/scrypt_passwords.go deleted file mode 100644 index 359b403..0000000 --- a/internal/passwords/scrypt_passwords.go +++ /dev/null @@ -1,72 +0,0 @@ -package passwords - -import ( - "time" - - scrypt "github.com/elithrar/simple-scrypt" - log "github.com/sirupsen/logrus" -) - -const ( - // DefaultMaxTimeout default max timeout in ms - DefaultMaxTimeout = 500 * time.Millisecond - - // DefaultMaxMemory default max memory in MB - DefaultMaxMemory = 64 -) - -// Options ... -type Options struct { - maxTimeout time.Duration - maxMemory int -} - -// NewOptions ... -func NewOptions(maxTimeout time.Duration, maxMemory int) *Options { - return &Options{maxTimeout, maxMemory} -} - -// ScryptPasswords ... -type ScryptPasswords struct { - options *Options - params scrypt.Params -} - -// NewScryptPasswords ... -func NewScryptPasswords(options *Options) Passwords { - if options == nil { - options = &Options{} - } - - if options.maxTimeout == 0 { - options.maxTimeout = DefaultMaxTimeout - } - if options.maxMemory == 0 { - options.maxMemory = DefaultMaxMemory - } - - log.Info("Calibrating scrypt parameters ...") - params, err := scrypt.Calibrate( - options.maxTimeout, - options.maxMemory, - scrypt.DefaultParams, - ) - if err != nil { - log.Fatalf("error calibrating scrypt params: %s", err) - } - - log.WithField("params", params).Info("scrypt params") - - return &ScryptPasswords{options, params} -} - -// CreatePassword ... -func (sp *ScryptPasswords) CreatePassword(password string) (string, error) { - hash, err := scrypt.GenerateFromPassword([]byte(password), sp.params) - return string(hash), err -} - -// CheckPassword ... -func (sp *ScryptPasswords) CheckPassword(hash, password string) error { - return scrypt.CompareHashAndPassword([]byte(hash), []byte(password)) -} diff --git a/internal/scraper.go b/internal/scraper.go index a8f4194..8fb41e4 100644 --- a/internal/scraper.go +++ b/internal/scraper.go @@ -2,7 +2,6 @@ package internal import ( "fmt" - "io/ioutil" "os" "path/filepath" "regexp" @@ -67,7 +66,7 @@ func Scrape(conf *Config, url string) (*Entry, error) { return nil, fmt.Errorf("error serializing entry: %s", err) } - if err := ioutil.WriteFile(fn, data, 0644); err != nil { + if err := os.WriteFile(fn, data, 0644); err != nil { log.WithError(err).Error("error persisting entry") return nil, fmt.Errorf("error persisting entry: %w", err) } diff --git a/internal/server.go b/internal/server.go index e93bafd..4cf469a 100644 --- a/internal/server.go +++ b/internal/server.go @@ -6,21 +6,17 @@ import ( "net/http" "os" "os/signal" - "path/filepath" "syscall" "time" "git.mills.io/prologic/observe" "github.com/NYTimes/gziphandler" - "github.com/gabstv/merger" "github.com/justinas/nosurf" "github.com/robfig/cron" log "github.com/sirupsen/logrus" "github.com/unrolled/logger" "git.mills.io/prologic/spyda" - "git.mills.io/prologic/spyda/internal/auth" - "git.mills.io/prologic/spyda/internal/passwords" "git.mills.io/prologic/spyda/internal/session" "git.mills.io/prologic/spyda/internal/static" ) @@ -56,18 +52,9 @@ type Server struct { // Dispatcher tasks *Dispatcher - // Auth - am *auth.Manager - // Sessions - sc *SessionStore + sc session.Store sm *session.Manager - - // API - api *API - - // Passwords - pm passwords.Passwords } func (s *Server) render(name string, w http.ResponseWriter, ctx *Context) { @@ -164,37 +151,6 @@ func (s *Server) setupMetrics() { }, ) - // sessions - metrics.NewGaugeFunc( - "server", "sessions", - "Number of active in-memory sessions (non-persistent)", - func() float64 { - return float64(s.sc.Count()) - }, - ) - - // database keys - metrics.NewGaugeFunc( - "db", "sessions", - "Number of database /sessions keys", - func() float64 { - return float64(s.db.LenSessions()) - }, - ) - metrics.NewGaugeFunc( - "db", "users", - "Number of database /users keys", - func() float64 { - return float64(s.db.LenUsers()) - }, - ) - metrics.NewGaugeFunc( - "db", "tokens", - "Number of database /tokens keys", - func() float64 { - return float64(s.db.LenTokens()) - }, - ) metrics.NewGaugeFunc( "db", "urls", "Number of database /urls keys", @@ -311,18 +267,6 @@ func (s *Server) initRoutes() { s.router.GET("/cache/:hash", s.CacheHandler()) s.router.HEAD("/cache/:hash", s.CacheHandler()) - s.router.GET("/login", s.am.HasAuth(s.LoginHandler())) - s.router.POST("/login", s.LoginHandler()) - - s.router.GET("/logout", s.LogoutHandler()) - s.router.POST("/logout", s.LogoutHandler()) - - // Reset Password - s.router.GET("/pwreset", s.ResetPasswordHandler()) - s.router.POST("/pwreset", s.ResetPasswordHandler()) - s.router.GET("/chpasswd", s.ResetPasswordMagicLinkHandler()) - s.router.POST("/chpasswd", s.NewPasswordHandler()) - // Task State s.router.GET("/tasks", s.TasksHandler()) s.router.GET("/task/:uuid", s.TaskHandler()) @@ -330,13 +274,6 @@ func (s *Server) initRoutes() { s.router.GET("/add", s.AddHandler()) s.router.POST("/add", s.AddHandler()) - s.router.GET("/manage", s.ManageHandler()) - s.router.POST("/manage", s.ManageHandler()) - - s.router.GET("/manage/users", s.ManageUsersHandler()) - s.router.POST("/manage/adduser", s.AddUserHandler()) - s.router.POST("/manage/deluser", s.DelUserHandler()) - // Support s.router.GET("/support", s.SupportHandler()) s.router.POST("/support", s.SupportHandler()) @@ -353,16 +290,6 @@ func NewServer(bind string, options ...Option) (*Server, error) { } } - settings, err := LoadSettings(filepath.Join(config.Data, "settings.yaml")) - if err != nil { - log.Warnf("error loading pod settings: %s", err) - } else { - if err := merger.MergeOverwrite(config, settings); err != nil { - log.WithError(err).Error("error merging pod settings") - return nil, err - } - } - if err := config.Validate(); err != nil { log.WithError(err).Error("error validating config") return nil, fmt.Errorf("error validating config: %w", err) @@ -387,14 +314,9 @@ func NewServer(bind string, options ...Option) (*Server, error) { router := NewRouter() - am := auth.NewManager(auth.NewOptions("/login", "/register")) - tasks := NewDispatcher(2, 10) // TODO: Make this configurable? - pm := passwords.NewScryptPasswords(nil) - - sc := NewSessionStore(db, config.SessionCacheTTL) - + sc := session.NewMemoryStore(config.SessionExpiry) sm := session.NewManager( session.NewOptions( config.Name, @@ -417,8 +339,6 @@ func NewServer(bind string, options ...Option) (*Server, error) { return nil, err } - api := NewAPI(router, config, db, pm) - csrfHandler := nosurf.New(router) csrfHandler.ExemptGlob("/api/v1/*") @@ -440,9 +360,6 @@ func NewServer(bind string, options ...Option) (*Server, error) { ), }, - // API - api: api, - // Indexer indexer: indexer, @@ -458,15 +375,9 @@ func NewServer(bind string, options ...Option) (*Server, error) { // Dispatcher tasks: tasks, - // Auth Manager - am: am, - // Session Manager sc: sc, sm: sm, - - // Password Manager - pm: pm, } if err := server.setupCronJobs(); err != nil { @@ -488,17 +399,14 @@ func NewServer(bind string, options ...Option) (*Server, error) { // Log interesting configuration options log.Infof("Instance Name: %s", server.config.Name) log.Infof("Base URL: %s", server.config.BaseURL) - log.Infof("Admin User: %s", server.config.AdminUser) log.Infof("Admin Name: %s", server.config.AdminName) log.Infof("Admin Email: %s", server.config.AdminEmail) log.Infof("SMTP Host: %s", server.config.SMTPHost) log.Infof("SMTP Port: %d", server.config.SMTPPort) log.Infof("SMTP User: %s", server.config.SMTPUser) log.Infof("SMTP From: %s", server.config.SMTPFrom) - log.Infof("API Session Time: %s", server.config.APISessionTime) server.initRoutes() - api.initRoutes() go server.runStartupJobs() diff --git a/internal/session_store.go b/internal/session_store.go deleted file mode 100644 index b3ae84d..0000000 --- a/internal/session_store.go +++ /dev/null @@ -1,90 +0,0 @@ -package internal - -import ( - "time" - - "github.com/patrickmn/go-cache" - log "github.com/sirupsen/logrus" - - "git.mills.io/prologic/spyda/internal/session" -) - -// SessionStore ... -type SessionStore struct { - store Store - cached *cache.Cache -} - -func NewSessionStore(store Store, sessionCacheTTL time.Duration) *SessionStore { - return &SessionStore{ - store: store, - cached: cache.New(sessionCacheTTL, time.Minute*5), - } -} - -func (s *SessionStore) Count() int { - return s.cached.ItemCount() -} - -func (s *SessionStore) GetSession(sid string) (*session.Session, error) { - val, found := s.cached.Get(sid) - if found { - return val.(*session.Session), nil - } - - return s.store.GetSession(sid) -} - -func (s *SessionStore) SetSession(sid string, sess *session.Session) error { - s.cached.Set(sid, sess, cache.DefaultExpiration) - if persist, ok := sess.Get("persist"); !ok || persist != "1" { - return nil - } - - return s.store.SetSession(sid, sess) -} - -func (s *SessionStore) HasSession(sid string) bool { - _, ok := s.cached.Get(sid) - if ok { - return true - } - - return s.store.HasSession(sid) -} - -func (s *SessionStore) DelSession(sid string) error { - if s.store.HasSession(sid) { - if err := s.store.DelSession(sid); err != nil { - log.WithError(err).Errorf("error deleting persistent session %s", sid) - return err - } - } - s.cached.Delete(sid) - return nil -} - -func (s *SessionStore) SyncSession(sess *session.Session) error { - if persist, ok := sess.Get("persist"); ok && persist == "1" { - if err := s.store.SetSession(sess.ID, sess); err != nil { - log.WithError(err).Errorf("error persisting session %s", sess.ID) - return err - } - } - - return s.SetSession(sess.ID, sess) -} - -func (s *SessionStore) GetAllSessions() ([]*session.Session, error) { - var sessions []*session.Session - for _, item := range s.cached.Items() { - sess := item.Object.(*session.Session) - sessions = append(sessions, sess) - } - persistedSessions, err := s.store.GetAllSessions() - if err != nil { - log.WithError(err).Error("error getting all persisted sessions") - return sessions, err - } - return append(sessions, persistedSessions...), nil -} diff --git a/internal/store.go b/internal/store.go index cafc1f1..c2d298b 100644 --- a/internal/store.go +++ b/internal/store.go @@ -3,16 +3,11 @@ package internal import ( "errors" "fmt" - - "git.mills.io/prologic/spyda/internal/session" ) var ( - ErrInvalidStore = errors.New("error: invalid store") - ErrUserNotFound = errors.New("error: user not found") - ErrTokenNotFound = errors.New("error: token not found") - ErrURLNotFound = errors.New("error: url not found") - ErrInvalidSession = errors.New("error: invalid session") + ErrInvalidStore = errors.New("error: invalid store") + ErrURLNotFound = errors.New("error: url not found") ) type Store interface { @@ -20,33 +15,12 @@ type Store interface { Close() error Sync() error - DelUser(username string) error - HasUser(username string) bool - GetUser(username string) (*User, error) - SetUser(username string, user *User) error - LenUsers() int64 - SearchUsers(prefix string) []string - GetAllUsers() ([]*User, error) - DelURL(hash string) error HasURL(hash string) bool GetURL(hash string) (*URL, error) SetURL(hash string, url *URL) error URLCount() int64 ForEachURL(f func(*URL) error) error - - GetSession(sid string) (*session.Session, error) - SetSession(sid string, sess *session.Session) error - HasSession(sid string) bool - DelSession(sid string) error - SyncSession(sess *session.Session) error - LenSessions() int64 - GetAllSessions() ([]*session.Session, error) - - GetUserTokens(user *User) ([]*Token, error) - SetToken(signature string, token *Token) error - DelToken(signature string) error - LenTokens() int64 } func NewStore(store string) (Store, error) { diff --git a/internal/templates.go b/internal/templates.go index c77cc26..3a1edd8 100644 --- a/internal/templates.go +++ b/internal/templates.go @@ -42,7 +42,6 @@ func NewTemplateManager(conf *Config) (*TemplateManager, error) { funcMap["prettyURL"] = PrettyURL funcMap["isLocalURL"] = IsLocalURLFactory(conf) funcMap["formatForDateTime"] = FormatForDateTime - funcMap["isAdminUser"] = IsAdminUserFactory(conf) m := &TemplateManager{debug: conf.Debug, templates: templates, funcMap: funcMap} diff --git a/internal/templates/login.html b/internal/templates/login.html deleted file mode 100644 index 6ccf56f..0000000 --- a/internal/templates/login.html +++ /dev/null @@ -1,44 +0,0 @@ -{{define "content"}} -
-
-
-

Sign in

-

Login to your Spyda account on {{ .InstanceName }}

-
-
- - - -
- -
- -

- Forgotten your password? -

-
-
-
-
-

How to login to your account

-
-

- Login to your Spyda account on {{ .InstanceName }} by filling in - the Username and Password you used when you created your account. -

-

- Check the "Remember Me" box if you don't want to have to keep logging in - every few hours. -

-

- If you have forgotten your password you can request a - Password Reset as long as you remember - your username and email address you signed up with and retain access to - your email (We NEVER store your email address!). -

-
-
-{{end}} diff --git a/internal/utils.go b/internal/utils.go index fed3761..6f278fc 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -357,14 +357,6 @@ func PrettyURL(uri string) string { return fmt.Sprintf("%s/%s", u.Hostname(), strings.TrimPrefix(u.EscapedPath(), "/")) } -// IsAdminUserFactory returns a function that returns true if the user provided -// is the configured pod administrator, false otherwise. -func IsAdminUserFactory(conf *Config) func(user *User) bool { - return func(user *User) bool { - return NormalizeUsername(conf.AdminUser) == NormalizeUsername(user.Username) - } -} - func URLForPage(baseURL, page string) string { return fmt.Sprintf( "%s/%s",