simpleauth/cmd/simpleauth/main.go

169 lines
3.7 KiB
Go
Raw Normal View History

2021-08-15 11:03:25 -06:00
package main
import (
2022-09-07 20:41:23 -06:00
"bufio"
2021-08-15 11:03:25 -06:00
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
"time"
2022-09-07 20:41:23 -06:00
"github.com/GehirnInc/crypt"
_ "github.com/GehirnInc/crypt/sha256_crypt"
"git.woozle.org/neale/simpleauth/pkg/token"
2021-08-15 11:03:25 -06:00
)
2022-09-10 13:55:04 -06:00
const CookieName = "simpleauth-token"
2021-08-15 11:03:25 -06:00
var secret []byte = make([]byte, 256)
var lifespan time.Duration
2022-09-07 20:41:23 -06:00
var cryptedPasswords map[string]string
2021-08-15 11:03:25 -06:00
var loginHtml []byte
2022-09-07 20:41:23 -06:00
func authenticationValid(username, password string) bool {
c := crypt.SHA256.New()
if crypted, ok := cryptedPasswords[username]; ok {
if err := c.Verify(crypted, []byte(password)); err == nil {
return true
}
2021-08-15 11:03:25 -06:00
}
2022-09-07 20:41:23 -06:00
return false
}
func rootHandler(w http.ResponseWriter, req *http.Request) {
2021-08-15 11:03:25 -06:00
if cookie, err := req.Cookie(CookieName); err == nil {
t, _ := token.ParseString(cookie.Value)
if t.Valid(secret) {
2022-09-10 13:55:04 -06:00
fmt.Print(w, "Valid token")
2022-09-07 20:41:23 -06:00
return
2021-08-15 11:03:25 -06:00
}
}
2022-09-10 13:55:04 -06:00
acceptsHtml := false
if strings.Contains(req.Header.Get("Accept"), "text/html") {
acceptsHtml = true
2022-09-07 20:41:23 -06:00
}
2022-09-10 13:55:04 -06:00
authenticated := false
if username, password, ok := req.BasicAuth(); ok {
authenticated = authenticationValid(username, password)
2022-09-07 20:41:23 -06:00
}
// Log the request
clientIP := req.Header.Get("X-Real-IP")
if clientIP == "" {
clientIP = req.RemoteAddr
}
2022-09-10 13:55:04 -06:00
log.Printf("%s %s %s [auth:%v]", clientIP, req.Method, req.URL, authenticated)
2022-09-07 20:41:23 -06:00
2022-09-10 13:55:04 -06:00
if !authenticated {
w.Header().Set("Content-Type", "text/html")
if !acceptsHtml {
w.Header().Set("WWW-Authenticate", "Basic realm=\"simpleauth\"")
}
2022-09-07 20:41:23 -06:00
w.WriteHeader(http.StatusUnauthorized)
w.Write(loginHtml)
2022-09-10 13:55:04 -06:00
return
2021-08-15 11:03:25 -06:00
}
2022-09-10 13:55:04 -06:00
// Set Cookie
t := token.New(secret, time.Now().Add(lifespan))
http.SetCookie(w, &http.Cookie{
Name: CookieName,
Value: t.String(),
Path: "/",
Secure: true,
SameSite: http.SameSiteStrictMode,
})
// Set cookie value in our fancypants header
w.Header().Set("X-Simpleauth-Token", t.String())
if req.Header.Get("X-Simpleauth-Login") != "" {
// Caddy treats any response <300 as "please serve original content",
// so we'll use 302 (Found).
// According to RFC9110, the server SHOULD send a Location header with 302.
// We don't do that, because we don't know where to send you.
// It's possible 300 is a less incorrect code to use here.
w.WriteHeader(http.StatusFound)
}
fmt.Fprintln(w, "Authenticated")
2021-08-15 11:03:25 -06:00
}
func main() {
listen := flag.String(
"listen",
":8080",
"Bind address for incoming HTTP connections",
)
flag.DurationVar(
&lifespan,
"lifespan",
100*24*time.Hour,
"How long an issued token is valid",
)
passwordPath := flag.String(
"passwd",
2022-09-07 20:41:23 -06:00
"/run/secrets/passwd",
"Path to a file containing passwords",
2021-08-15 11:03:25 -06:00
)
secretPath := flag.String(
"secret",
2023-02-18 12:47:05 -07:00
"/run/secrets/simpleauth.key",
2021-08-15 11:03:25 -06:00
"Path to a file containing some sort of secret, for signing requests",
)
htmlPath := flag.String(
"html",
"web",
2021-08-15 11:03:25 -06:00
"Path to HTML files",
)
flag.Parse()
2022-09-07 20:41:23 -06:00
cryptedPasswords = make(map[string]string, 10)
if f, err := os.Open(*passwordPath); err != nil {
2021-08-15 11:03:25 -06:00
log.Fatal(err)
2022-09-07 20:41:23 -06:00
} else {
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) >= 2 {
username := parts[0]
password := parts[1]
cryptedPasswords[username] = password
}
}
2021-08-15 11:03:25 -06:00
}
2022-09-07 20:41:23 -06:00
var err error
2021-08-15 11:03:25 -06:00
loginHtml, err = ioutil.ReadFile(path.Join(*htmlPath, "login.html"))
if err != nil {
log.Fatal(err)
}
// Read in secret
f, err := os.Open(*secretPath)
if err != nil {
log.Fatal(err)
}
defer f.Close()
l, err := f.Read(secret)
2022-09-07 20:41:23 -06:00
if l < 8 {
log.Fatalf("Secret file provided %d bytes. That's not enough bytes!", l)
2021-08-15 11:03:25 -06:00
} else if err != nil {
log.Fatal(err)
}
secret = secret[:l]
http.HandleFunc("/", rootHandler)
2022-03-05 15:53:47 -07:00
fmt.Println("listening on", *listen)
2021-08-15 11:03:25 -06:00
log.Fatal(http.ListenAndServe(*listen, nil))
}