Simple password auth Still need to do a bit of a security check here
alex wennerberg alex@alexwennerberg.com
Fri, 26 Feb 2021 19:27:15 -0800
M
db.go
→
db.go
@@ -3,6 +3,7 @@
import ( "crypto/rand" "database/sql" + "fmt" "golang.org/x/crypto/bcrypt" "io" "io/ioutil"@@ -11,6 +12,7 @@ "os"
"path" "path/filepath" "sort" + "strings" "time" )@@ -23,6 +25,32 @@ if err != nil {
log.Fatal(err) } createTablesIfDNE() +} + +// returns nil if login OK, err otherwise +// log in with email or username +func checkLogin(name string, password string) (string, bool, error) { + row := DB.QueryRow("SELECT username, password_hash, active, admin FROM user where username = $1 OR email = $1", name) + var db_password []byte + var username string + var active bool + var isAdmin bool + err := row.Scan(&username, &db_password, &active, &isAdmin) + if err != nil { + if strings.Contains(err.Error(), "no rows") { + return username, isAdmin, fmt.Errorf("Username or email '" + name + "' does not exist") + } else { + return username, isAdmin, err + } + } + if db_password != nil && !active { + return username, isAdmin, fmt.Errorf("Your account is not active yet. Pending admin approval", c) + } + if bcrypt.CompareHashAndPassword(db_password, []byte(password)) == nil { + return username, isAdmin, nil + } else { + return username, isAdmin, fmt.Errorf("Invalid password") + } } func getAnalyticsDB() (*sql.DB, error) {
M
http.go
→
http.go
@@ -360,36 +360,8 @@ } else if r.Method == "POST" {
r.ParseForm() name := strings.ToLower(r.Form.Get("username")) password := r.Form.Get("password") - row := DB.QueryRow("SELECT username, password_hash, active, admin FROM user where username = $1 OR email = $1", name) - var db_password []byte - var username string - var active bool - var isAdmin bool - err := row.Scan(&username, &db_password, &active, &isAdmin) + username, isAdmin, err := checkLogin(name, password) if err != nil { - if strings.Contains(err.Error(), "no rows") { - data := struct { - Error string - Config Config - }{"Username or email '" + name + "' does not exist", c} - w.WriteHeader(401) - t.ExecuteTemplate(w, "login.html", data) - return - } else { - serverError(w, err) - return - } - } - if db_password != nil && !active { - data := struct { - Error string - Config Config - }{"Your account is not active yet. Pending admin approval", c} - w.WriteHeader(401) - t.ExecuteTemplate(w, "login.html", data) - return - } - if bcrypt.CompareHashAndPassword(db_password, []byte(password)) == nil { log.Println("logged in") session, _ := SessionStore.Get(r, "cookie-session") session.Values["auth_user"] = username@@ -400,7 +372,7 @@ } else {
data := struct { Error string Config Config - }{"Invalid login or password", c} + }{err.Error(), c} w.WriteHeader(401) err := t.ExecuteTemplate(w, "login.html", data) if err != nil {
M
sftp.go
→
sftp.go
@@ -4,7 +4,6 @@ // so not for real use!
package main import ( - "flag" "fmt" "io" "io/ioutil"@@ -35,7 +34,6 @@ }
func (con *Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { // check user perms -- cant write others files - // check if file is inside your directory -- strings prefix? fullpath := path.Join(c.FilesDirectory, filepath.Clean(request.Filepath)) userDir := getUserDirectory(con.User) // NOTE -- not cross platform if strings.HasPrefix(fullpath, userDir) {@@ -92,32 +90,23 @@
// Based on example server code from golang.org/x/crypto/ssh and server_standalone func runSFTPServer() { - var ( - readOnly bool - debugStderr bool - ) - - flag.BoolVar(&readOnly, "R", false, "read-only server") - flag.BoolVar(&debugStderr, "e", false, "debug to stderr") - flag.Parse() - - debugStream := ioutil.Discard - if debugStderr { - debugStream = os.Stderr - } - // An SSH server is represented by a ServerConfig, which holds // certificate details and handles authentication of ServerConns. config := &ssh.ServerConfig{ - // PublicKeyCallback PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { // Should use constant-time compare (or better, salt+hash) in // a production setting. - fmt.Fprintf(debugStream, "Login: %s\n", c.User()) - if c.User() == "alex" && string(pass) == "alex" { + if isOkUsername(c.User()) != nil { // extra check, probably unnecessary + return nil, fmt.Errorf("Invalid username") + } + _, _, err := checkLogin(c.User(), string(pass)) + // TODO maybe give admin extra permissions? + fmt.Fprintf(os.Stderr, "Login: %s\n", c.User()) + if err != nil { + return nil, fmt.Errorf("password rejected for %q", c.User()) + } else { return nil, nil } - return nil, fmt.Errorf("password rejected for %q", c.User()) }, }@@ -152,7 +141,7 @@ if err != nil {
log.Fatal("failed to handshake", err) } log.Println("login detected:", sconn.User()) - fmt.Fprintf(debugStream, "SSH server established\n") + fmt.Fprintf(os.Stderr, "SSH server established\n") // The incoming Request channel must be serviced. go ssh.DiscardRequests(reqs)@@ -162,33 +151,33 @@ for newChannel := range chans {
// Channels have a type, depending on the application level // protocol intended. In the case of an SFTP session, this is "subsystem" // with a payload string of "<length=4>sftp" - fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType()) + fmt.Fprintf(os.Stderr, "Incoming channel: %s\n", newChannel.ChannelType()) if newChannel.ChannelType() != "session" { newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") - fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType()) + fmt.Fprintf(os.Stderr, "Unknown channel type: %s\n", newChannel.ChannelType()) continue } channel, requests, err := newChannel.Accept() if err != nil { log.Fatal("could not accept channel.", err) } - fmt.Fprintf(debugStream, "Channel accepted\n") + fmt.Fprintf(os.Stderr, "Channel accepted\n") // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "subsystem" request. go func(in <-chan *ssh.Request) { for req := range in { - fmt.Fprintf(debugStream, "Request: %v\n", req.Type) + fmt.Fprintf(os.Stderr, "Request: %v\n", req.Type) ok := false switch req.Type { case "subsystem": - fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:]) + fmt.Fprintf(os.Stderr, "Subsystem: %s\n", req.Payload[4:]) if string(req.Payload[4:]) == "sftp" { ok = true } } - fmt.Fprintf(debugStream, " - accepted: %v\n", ok) + fmt.Fprintf(os.Stderr, " - accepted: %v\n", ok) req.Reply(ok, nil) } }(requests)