mirror of
https://git.freecumextremist.com/grumbulon/pomme.git
synced 2024-12-22 16:10:44 +00:00
linted, added a custom web server definition with timeouts, added new type of context key, and added linting to the makefile
This commit is contained in:
parent
11692efdd1
commit
c3c533558f
11 changed files with 209 additions and 79 deletions
88
.golangci.yaml
Normal file
88
.golangci.yaml
Normal file
|
@ -0,0 +1,88 @@
|
|||
# Refer to golangci-lint's example config file for more options and information:
|
||||
# https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
modules-download-mode: readonly
|
||||
skip-dirs:
|
||||
- "coverage"
|
||||
- ".github"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gci
|
||||
- gocritic
|
||||
- goconst
|
||||
- godot
|
||||
- goimports
|
||||
- govet
|
||||
- gocritic
|
||||
- goerr113
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- gosec
|
||||
- maintidx
|
||||
- makezero
|
||||
- misspell
|
||||
- nlreturn
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- predeclared
|
||||
- staticcheck
|
||||
- tagliatelle
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
disable:
|
||||
- structcheck
|
||||
- revive
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable-all: true
|
||||
disable-all: false
|
||||
revive:
|
||||
ignore-generated-header: false
|
||||
severity: warning
|
||||
confidence: 0.8
|
||||
errorCode: 1
|
||||
warningCode: 1
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: duplicated-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: errorf
|
||||
- name: exported
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: modifies-value-receiver
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: var-declaration
|
||||
- name: var-naming
|
||||
linters-settings:
|
||||
tagliatelle:
|
||||
case:
|
||||
use-field-name: false
|
||||
rules:
|
||||
# Any struct tag type can be used.
|
||||
# Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
|
||||
json: goCamel
|
||||
yaml: goCamel
|
||||
xml: goCamel
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
4
Makefile
4
Makefile
|
@ -13,3 +13,7 @@ frontend:
|
|||
|
||||
backend: $(GO_SOURCES)
|
||||
go build ./cmd/pomme
|
||||
|
||||
lint:
|
||||
golangci-lint run --timeout=5m --out-format colored-line-number:stdout
|
||||
.PHONY: lint
|
|
@ -1,9 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.freecumextremist.com/grumbulon/pomme/frontend"
|
||||
"git.freecumextremist.com/grumbulon/pomme/internal/api"
|
||||
|
@ -12,17 +12,24 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
|
||||
pomme := chi.NewRouter()
|
||||
pomme.Use(middleware.Logger)
|
||||
pomme.Use(middleware.GetHead)
|
||||
pomme.Use(middleware.Recoverer)
|
||||
|
||||
pomme.Mount("/", frontend.SvelteKitHandler("/"))
|
||||
pomme.Mount("/api", api.Api())
|
||||
pomme.Mount("/api", api.API())
|
||||
|
||||
log.Println("\t-------------------------------------")
|
||||
log.Println("\t\tRunning on port 3000")
|
||||
log.Println("\t-------------------------------------")
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", 3000), pomme))
|
||||
|
||||
s := &http.Server{
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
Addr: ":3000",
|
||||
Handler: pomme,
|
||||
}
|
||||
|
||||
log.Fatal(s.ListenAndServe())
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
//go:embed all:build
|
||||
var files embed.FS
|
||||
|
||||
// I stole this lol
|
||||
// SvelteKitHandler -- I stole this lol.
|
||||
func SvelteKitHandler(path string) http.Handler {
|
||||
fsys, err := fs.Sub(files, "build")
|
||||
if err != nil {
|
||||
|
@ -27,7 +27,7 @@ func SvelteKitHandler(path string) http.Handler {
|
|||
filesystem := http.FS(fsys)
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, path)
|
||||
path := strings.TrimPrefix(r.URL.Path, path) //nolint: govet
|
||||
|
||||
_, err := filesystem.Open(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -14,11 +13,19 @@ import (
|
|||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const (
|
||||
keyPrincipalContextID key = iota
|
||||
)
|
||||
|
||||
// SetDBMiddleware is the http Handler func for the GORM middleware with context.
|
||||
func SetDBMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
db := db.InitDb()
|
||||
timeoutContext, _ := context.WithTimeout(context.Background(), time.Second)
|
||||
ctx := context.WithValue(r.Context(), "DB", db.WithContext(timeoutContext))
|
||||
timeoutContext, cancelContext := context.WithTimeout(context.Background(), time.Second)
|
||||
ctx := context.WithValue(r.Context(), keyPrincipalContextID, db.WithContext(timeoutContext))
|
||||
defer cancelContext()
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
@ -28,19 +35,17 @@ func basicAuthFailed(w http.ResponseWriter, realm string) {
|
|||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// API handler
|
||||
func Api() http.Handler {
|
||||
// API subroute handler.
|
||||
func API() http.Handler {
|
||||
api := chi.NewRouter()
|
||||
|
||||
// Protected routes
|
||||
api.Group(func(api chi.Router) {
|
||||
|
||||
api.Use(jwtauth.Verifier(tokenAuth))
|
||||
|
||||
api.Use(jwtauth.Authenticator)
|
||||
api.Post("/check", Ingest)
|
||||
api.Get("/private", AuthTest)
|
||||
|
||||
})
|
||||
|
||||
// Open routes
|
||||
|
@ -54,23 +59,25 @@ func Api() http.Handler {
|
|||
return api
|
||||
}
|
||||
|
||||
// Ingest is a function to ingest Zonefiles.
|
||||
func Ingest(w http.ResponseWriter, r *http.Request) {
|
||||
data := &internal.ZoneRequest{}
|
||||
log.Println(data)
|
||||
if err := render.Bind(r, data); err != nil {
|
||||
http.Error(w, "Unable to parse Zonefile", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_ = &internal.ZoneRequest{}
|
||||
|
||||
zonefile := data.Zone
|
||||
render.Status(r, http.StatusAccepted)
|
||||
render.Render(w, r, internal.NewZoneResponse(zonefile))
|
||||
// todo write to database, maybe?
|
||||
|
||||
// todo -- add functions to apply to master zonefile if above check is OK
|
||||
// todo -- add functions to apply to master zonefile if above check is OK.
|
||||
|
||||
render.Status(r, http.StatusAccepted)
|
||||
}
|
||||
|
||||
// AuthTest is for testing protected routes.
|
||||
func AuthTest(w http.ResponseWriter, r *http.Request) {
|
||||
_, claims, _ := jwtauth.FromContext(r.Context())
|
||||
w.Write([]byte(fmt.Sprintf("protected area. hi %v", claims["username"])))
|
||||
|
||||
_, err := w.Write([]byte(fmt.Sprintf("protected area. hi %v", claims["username"])))
|
||||
if err != nil {
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,29 +10,43 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Login checks user credentials and creates a jwt session.
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
var result internal.User
|
||||
r.ParseForm()
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
username := r.Form.Get("username")
|
||||
|
||||
if username == "" {
|
||||
username = autoUname()
|
||||
}
|
||||
|
||||
password := r.Form.Get("password")
|
||||
|
||||
if password == "" {
|
||||
http.Error(w, "No password provided", http.StatusInternalServerError) // this should prob be handled by the frontend
|
||||
}
|
||||
|
||||
db, ok := r.Context().Value("DB").(*gorm.DB)
|
||||
db, ok := r.Context().Value(keyPrincipalContextID).(*gorm.DB)
|
||||
if !ok {
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
db.Model(internal.User{Username: username}).First(&result)
|
||||
err := bcrypt.CompareHashAndPassword([]byte(result.HashedPassword), []byte(password))
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(result.HashedPassword), []byte(password))
|
||||
|
||||
if err != nil {
|
||||
basicAuthFailed(w, "user")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -48,17 +62,23 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
|||
Value: token,
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(
|
||||
err = json.NewEncoder(w).Encode(
|
||||
internal.Response{
|
||||
Message: "Successfully logged in",
|
||||
HTTPResponse: 200,
|
||||
})
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// Logout destroys a users JWT cookie.
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
HttpOnly: true,
|
||||
MaxAge: -1, // Delete the cookie.
|
||||
|
@ -68,10 +88,17 @@ func Logout(w http.ResponseWriter, r *http.Request) {
|
|||
Value: "",
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(
|
||||
|
||||
err := json.NewEncoder(w).Encode(
|
||||
internal.Response{
|
||||
Message: "Successfully logged out",
|
||||
HTTPResponse: 200,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
|
@ -20,6 +19,6 @@ func makeToken(username string) string {
|
|||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println(tokenString)
|
||||
|
||||
return tokenString
|
||||
}
|
||||
|
|
|
@ -1,31 +1,41 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.freecumextremist.com/grumbulon/pomme/internal"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NewUser takes a POST request and user form and creates a user in the database.
|
||||
func NewUser(w http.ResponseWriter, r *http.Request) {
|
||||
db, ok := r.Context().Value("DB").(*gorm.DB)
|
||||
db, ok := r.Context().Value(keyPrincipalContextID).(*gorm.DB)
|
||||
if !ok {
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
var result internal.User
|
||||
|
||||
r.ParseForm()
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
username := r.Form.Get("username")
|
||||
|
||||
if username == "" {
|
||||
username = autoUname()
|
||||
}
|
||||
|
||||
password := r.Form.Get("password")
|
||||
|
||||
if password == "" {
|
||||
http.Error(w, "No password entered", http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -34,6 +44,7 @@ func NewUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if result.Username != "" {
|
||||
http.Error(w, "User already exists", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -46,14 +57,24 @@ func NewUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(
|
||||
err = json.NewEncoder(w).Encode(
|
||||
internal.Response{
|
||||
Username: username,
|
||||
HTTPResponse: http.StatusCreated,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func autoUname() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return fmt.Sprintf("user%d", rand.Intn(99999-00000))
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(1000))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("user%d", n.Int64())
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// InitDb is the init function for the database.
|
||||
func InitDb() *gorm.DB {
|
||||
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
|
@ -13,7 +14,10 @@ func InitDb() *gorm.DB {
|
|||
}
|
||||
|
||||
// Migrate the schema
|
||||
db.AutoMigrate(&internal.User{})
|
||||
err = db.AutoMigrate(&internal.User{})
|
||||
if err != nil {
|
||||
panic("failed to run DB migration")
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package util
|
||||
|
||||
// ValidateQuery does nothing.
|
||||
func ValidateQuery(request string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ZoneRequest represents a Zone file request.
|
||||
type ZoneRequest struct {
|
||||
*Zone
|
||||
|
||||
|
@ -17,6 +17,7 @@ type ZoneRequest struct {
|
|||
RequestID string `json:"id"`
|
||||
}
|
||||
|
||||
// ZoneResponse represents a Zone file request response.
|
||||
type ZoneResponse struct {
|
||||
*Zone
|
||||
|
||||
|
@ -24,12 +25,14 @@ type ZoneResponse struct {
|
|||
Elapsed int64 `json:"elapsed"`
|
||||
}
|
||||
|
||||
// Zone struct represents a zonefile.
|
||||
type Zone struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
UserID string `json:"user_id"` //nolint: tagliatelle
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// User struct represents a user.
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Username string
|
||||
|
@ -37,55 +40,24 @@ type User struct {
|
|||
HashedPassword string
|
||||
}
|
||||
|
||||
// Response struct represents a json response.
|
||||
type Response struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
HTTPResponse int `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Parse will be used to parse zonefiles.
|
||||
func (zone *ZoneRequest) Parse() error {
|
||||
zp := dns.NewZoneParser(strings.NewReader(zone.Body), "", "")
|
||||
|
||||
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
|
||||
log.Println(rr)
|
||||
}
|
||||
|
||||
if err := zp.Err(); err != nil {
|
||||
log.Println(err)
|
||||
return errors.New("unable to parse Zonefile")
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zone *ZoneRequest) Bind(r *http.Request) error {
|
||||
if zone.Zone == nil {
|
||||
return errors.New("missing required zone file fields")
|
||||
}
|
||||
zone.Zone.Body = strings.ToLower(zone.Zone.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewZoneResponse(zone *Zone) *ZoneResponse {
|
||||
resp := &ZoneResponse{Zone: zone}
|
||||
if resp.User == nil {
|
||||
if user, _ := dbGetUser(resp.UserID); user != nil {
|
||||
resp.User = NewUserPayloadResponse(user)
|
||||
}
|
||||
return fmt.Errorf("unable to parse Zonefile: %w", err)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func NewUserPayloadResponse(user *User) *User {
|
||||
return &User{Username: user.Username}
|
||||
}
|
||||
|
||||
func dbGetUser(s string) (*User, error) {
|
||||
return &User{Username: "user14651"}, nil
|
||||
}
|
||||
|
||||
func (rd *ZoneResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
// Pre-processing before a response is marshalled and sent across the wire
|
||||
rd.Elapsed = 10
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue