From fa05045d31c05c8928020f05f1d281901d983b2b Mon Sep 17 00:00:00 2001 From: Gibheer Date: Thu, 2 Dec 2021 17:54:14 +0100 Subject: cmd/monfront - import monfront from separate repository This is the import from the separate monfront repository. The history could not be imported, but this should suffice. --- cmd/monfront/authenticater.go | 183 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 cmd/monfront/authenticater.go (limited to 'cmd/monfront/authenticater.go') diff --git a/cmd/monfront/authenticater.go b/cmd/monfront/authenticater.go new file mode 100644 index 0000000..8453264 --- /dev/null +++ b/cmd/monfront/authenticater.go @@ -0,0 +1,183 @@ +package main + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "database/sql" + "encoding/base64" + "fmt" + "log" + "net/http" + "strings" + "time" +) + +const ( + BasicAuthPrompt = `Basic realm="auth for monfront"` + SessionCookie = `session` + UserAnonymous = `anonymous` +) + +type ( + // Authenticator is a middleware taking a context and authenticating + // the user. + Authenticator struct { + db *sql.DB + Mode string + Token []byte + AllowAnonymous bool + Header string + List [][]string + ClientCA string + + sessions map[string]*session // maps a session key to a user + } + + session struct { + user string + t time.Time + } +) + +// Handler returns the handler for the authentication configuration. +func (a *Authenticator) Handler() (func(*Context) error, error) { + switch a.Mode { + case "none": + return func(_ *Context) error { return nil }, nil + case "header": + if a.Header == "" { + return nil, fmt.Errorf("authentication mode is 'header' but no header was provided") + } + return func(c *Context) error { + if user := c.r.Header.Get(a.Header); user == "" { + if a.AllowAnonymous { + c.User = UserAnonymous + return nil + } + return a.Unauthorized(c) + } else { + c.User = user + } + return nil + }, nil + case "list": + return func(c *Context) error { + user, pass, ok := c.r.BasicAuth() + if !ok || user == "" || pass == "" { + if a.AllowAnonymous { + c.User = UserAnonymous + return nil + } + c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt) + return a.Unauthorized(c) + } + var found string + for _, entry := range a.List { + if entry[0] == user { + found = entry[1] + } + } + if found == "" { + c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt) + return a.Unauthorized(c) + } + p := pwHash{} + if err := p.Parse(found); err != nil { + log.Printf("could not parse hash for user '%s': %s", user, err) + return a.Unauthorized(c) + } + if ok, err := p.compare(pass); err != nil { + c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt) + return a.Unauthorized(c) + } else if !ok { + c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt) + return a.Unauthorized(c) + } + c.User = user + return nil + }, nil + case "db": + return func(c *Context) error { + sessCookie := c.GetCookieVal(SessionCookie) + if sessCookie != "" { + ses := a.getSession(sessCookie) + if ses != "" { + // TODO fix time limit to make it variable + c.SetCookie(SessionCookie, sessCookie, time.Now().Add(2*time.Hour)) + c.User = ses + return nil + } + } + return fmt.Errorf("NOT YET IMPLEMENTED") + }, fmt.Errorf("NOT YET IMPLEMENTED") + case "cert": + return func(c *Context) error { + return fmt.Errorf("NOT YET IMPLEMENTED") + }, fmt.Errorf("NOT YET IMPLEMENTED") + default: + return nil, fmt.Errorf("unknown mode '%s' for authentication", a.Mode) + } + return nil, fmt.Errorf("could not create authenticator") +} + +func (a *Authenticator) Unauthorized(c *Context) error { + c.w.WriteHeader(http.StatusUnauthorized) + fmt.Fprintf(c.w, "unauthorized\n") + return fmt.Errorf("no authentication") +} + +// creates a session for a user +func (a *Authenticator) createSession(user string) (string, error) { + raw := make([]byte, 32) + if _, err := rand.Read(raw); err != nil { + return "", fmt.Errorf("could not generate new session key") + } + res := a.mac(raw) + ses := fmt.Sprintf( + "%s-%s", + base64.StdEncoding.EncodeToString(raw), + base64.StdEncoding.EncodeToString(res), + ) + a.sessions[ses] = &session{user: user, t: time.Now()} + return ses, nil +} + +func (a *Authenticator) mac(input []byte) []byte { + mac := hmac.New(sha256.New, a.Token) + mac.Write(input) + return mac.Sum(nil) +} + +// getSession returns the username of the current session. +func (a *Authenticator) getSession(session string) string { + if session == "" { + return "" + } + parts := strings.Split(session, "-") + if len(parts) != 2 { + return "" + } + msg, err := base64.StdEncoding.DecodeString(parts[0]) + if err != nil { + return "" + } + mac, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return "" + } + verify := a.mac(msg) + if !hmac.Equal(mac, verify) { + return "" + } + if ses, found := a.sessions[session]; found { + // TODO make timeout a config option + if time.Now().Sub(ses.t) < 8*time.Hour { + delete(a.sessions, session) + return "" + } + ses.t = time.Now() + return ses.user + } + return "" +} -- cgit v1.2.3-70-g09d2