>1 account

This commit is contained in:
Neale Pickett 2022-09-07 20:41:23 -06:00
parent 8b8491199c
commit 12f5bf35f6
7 changed files with 112 additions and 35 deletions

View File

@ -5,9 +5,23 @@ Upon successful login, the browser gets a cookie,
and further attempts to access will get the success page. and further attempts to access will get the success page.
I made this to use with the Traefik forward-auth middleware. I made this to use with the Traefik forward-auth middleware.
I now use Caddy: it works with that too.
All I need is a simple password, that's easy to fill with a password manager. All I need is a simple password, that's easy to fill with a password manager.
This checks those boxes. This checks those boxes.
## Format of the `passwd` file
It's just like `/etc/shadow`.
username:crypted-password
We use sha256,
until there's a Go library that supports everything.
There's a program included called `crypt` that will output lines for this file.
## Installation with Traefik ## Installation with Traefik
You need to have traefik forward the Path `/` to this application. You need to have traefik forward the Path `/` to this application.

24
cmd/crypt/main.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"log"
"os"
"github.com/GehirnInc/crypt"
_ "github.com/GehirnInc/crypt/sha256_crypt"
)
func main() {
if len(os.Args) != 3 {
log.Fatal("Usage: crypt USERNAME PASSWORD")
}
username := os.Args[1]
password := os.Args[2]
c := crypt.SHA256.New()
if crypted, err := c.Generate([]byte(password), nil); err != nil {
log.Fatal(err)
} else {
fmt.Printf("%s:%s\n", username, crypted)
}
}

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -11,6 +12,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/GehirnInc/crypt"
_ "github.com/GehirnInc/crypt/sha256_crypt"
"github.com/nealey/simpleauth/pkg/token" "github.com/nealey/simpleauth/pkg/token"
) )
@ -18,21 +21,25 @@ const CookieName = "auth"
var secret []byte = make([]byte, 256) var secret []byte = make([]byte, 256)
var lifespan time.Duration var lifespan time.Duration
var password string var cryptedPasswords map[string]string
var loginHtml []byte var loginHtml []byte
var successHtml []byte var successHtml []byte
func authenticationValid(username, password string) bool {
c := crypt.SHA256.New()
fmt.Println("checking", username, password)
if crypted, ok := cryptedPasswords[username]; ok {
fmt.Println(username, password, crypted)
if err := c.Verify(crypted, []byte(password)); err == nil {
return true
} else {
log.Println(err)
}
}
return false
}
func rootHandler(w http.ResponseWriter, req *http.Request) { func rootHandler(w http.ResponseWriter, req *http.Request) {
mechanism := "unauthenticated"
authenticated := false
if _, passwd, _ := req.BasicAuth(); passwd == password {
authenticated = true
mechanism = "HTTP-Basic"
}
if req.FormValue("passwd") == password {
authenticated = true
mechanism = "Form"
}
if cookie, err := req.Cookie(CookieName); err == nil { if cookie, err := req.Cookie(CookieName); err == nil {
t, _ := token.ParseString(cookie.Value) t, _ := token.ParseString(cookie.Value)
if t.Valid(secret) { if t.Valid(secret) {
@ -43,14 +50,29 @@ func rootHandler(w http.ResponseWriter, req *http.Request) {
} }
} }
authenticated := ""
if username, password, ok := req.BasicAuth(); ok {
if authenticationValid(username, password) {
authenticated = "HTTP-Basic"
}
}
if authenticationValid(req.FormValue("username"), req.FormValue("password")) {
authenticated = "Form"
}
// Log the request // Log the request
clientIP := req.Header.Get("X-Real-IP") clientIP := req.Header.Get("X-Real-IP")
if clientIP == "" { if clientIP == "" {
clientIP = req.RemoteAddr clientIP = req.RemoteAddr
} }
log.Printf("%s %s %s [%s]", clientIP, req.Method, req.URL, mechanism) log.Printf("%s %s %s [%s]", clientIP, req.Method, req.URL, authenticated)
if authenticated { if authenticated == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write(loginHtml)
} else {
t := token.New(secret, time.Now().Add(lifespan)) t := token.New(secret, time.Now().Add(lifespan))
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: CookieName, Name: CookieName,
@ -59,10 +81,8 @@ func rootHandler(w http.ResponseWriter, req *http.Request) {
Secure: true, Secure: true,
SameSite: http.SameSiteStrictMode, SameSite: http.SameSiteStrictMode,
}) })
w.WriteHeader(http.StatusOK)
w.Write(successHtml) w.Write(successHtml)
} else {
w.WriteHeader(http.StatusUnauthorized)
w.Write(loginHtml)
} }
} }
@ -80,8 +100,8 @@ func main() {
) )
passwordPath := flag.String( passwordPath := flag.String(
"passwd", "passwd",
"/run/secrets/password", "/run/secrets/passwd",
"Path to a file containing the password", "Path to a file containing passwords",
) )
secretPath := flag.String( secretPath := flag.String(
"secret", "secret",
@ -95,11 +115,25 @@ func main() {
) )
flag.Parse() flag.Parse()
passwordBytes, err := ioutil.ReadFile(*passwordPath) cryptedPasswords = make(map[string]string, 10)
if err != nil { if f, err := os.Open(*passwordPath); err != nil {
log.Fatal(err) log.Fatal(err)
} 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]
fmt.Println(username, password)
cryptedPasswords[username] = password
} }
password = strings.TrimSpace(string(passwordBytes)) }
}
var err error
loginHtml, err = ioutil.ReadFile(path.Join(*htmlPath, "login.html")) loginHtml, err = ioutil.ReadFile(path.Join(*htmlPath, "login.html"))
if err != nil { if err != nil {
@ -117,8 +151,8 @@ func main() {
} }
defer f.Close() defer f.Close()
l, err := f.Read(secret) l, err := f.Read(secret)
if l == 0 { if l < 8 {
log.Fatal("Secret file provided 0 bytes. That's not enough bytes!") log.Fatalf("Secret file provided %d bytes. That's not enough bytes!", l)
} else if err != nil { } else if err != nil {
log.Fatal(err) log.Fatal(err)
} }

2
go.mod
View File

@ -1,3 +1,5 @@
module github.com/nealey/simpleauth module github.com/nealey/simpleauth
go 1.13 go 1.13
require github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=

View File

@ -46,7 +46,8 @@
<body> <body>
<h1>Log In</h1> <h1>Log In</h1>
<form action="/" method="post"> <form action="/" method="post">
<div>Password: <input type="password" name="passwd"></div> <div>Username: <input name="username"></div>
<div>Password: <input type="password" name="password"></div>
<div><input type="submit" value="Log In"></div> <div><input type="submit" value="Log In"></div>
</form> </form>
<div id="error"></div> <div id="error"></div>

View File

@ -8,7 +8,7 @@
html { html {
font-family: sans-serif; font-family: sans-serif;
color: white; color: white;
background: seagreen linear-gradient(315, rgba(255,255,255,0.2), transparent); background: seagreen linear-gradient(315deg, rgba(255,255,255,0.2), transparent);
height: 100%; height: 100%;
} }
div { div {