package plg_authenticate_local import ( "bytes" "encoding/base64" "encoding/json" "fmt" "html" "image/png" "net/http" "text/template" . "github.com/mickael-kerjean/filestash/server/common" "github.com/pquerna/otp/totp" "golang.org/x/crypto/bcrypt" ) type SimpleAuth struct{} func (this SimpleAuth) Setup() Form { nUsers := 0 aUsers := 0 if users, err := getUsers(); err == nil { nUsers = len(users) for i := range users { if users[i].Disabled == false { aUsers += 1 } } } return Form{ Elmnts: []FormElement{ { Name: "banner", Type: "hidden", Description: fmt.Sprintf(`
MANAGEMENT GUI: /admin/simple-user-management
STATS:
┌─────────────┐   ┌──────────────┐
│ TOTAL USERS │   │ ACTIVE USERS │
|    %.4d     │   |     %.4d     │
└─────────────┘   └──────────────┘
EMAIL SERVER: %t
`, nUsers, aUsers, isEmailSetup()), }, { Name: "type", Type: "hidden", Value: "local", }, { Name: "mfa", Type: "select", Default: "", Opts: []string{"", "TOTP"}, }, { Name: "notification_subject", Type: "text", }, { Name: "notification_body", Type: "long_text", Placeholder: `Hello, Your account to Filestash was created by an administrator. You can access it via http://demo.filestash.app. Your password is: {{ .password }} The roles assigned to you: {{ .role }} Cheers!`, }, { Name: "db", Type: "hidden", }, }, } } func (this SimpleAuth) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { getFlash := func() string { c, err := req.Cookie("flash") if err != nil { return "" } http.SetCookie(res, &http.Cookie{ Name: "flash", MaxAge: -1, Path: "/", }) return fmt.Sprintf(`

%s

`, html.EscapeString(c.Value)) } res.Header().Set("Content-Type", "text/html; charset=utf-8") res.WriteHeader(http.StatusOK) if c, err := req.Cookie("mfa"); err == nil && c.Value != "" { user := withMFA(User{}, c.Value) key, err := totp.Generate(totp.GenerateOpts{ Issuer: Config.Get("general.name").String(), AccountName: user.Email, }) if err != nil { return err } var buf bytes.Buffer img, err := key.Image(200, 200) if err != nil { return err } if err = png.Encode(&buf, img); err != nil { return err } template.Must(template.New("app").Parse(Page(`
{{ if eq .User.MFA "" }}
{{ end }} `+getFlash()+`
`))).Execute(res, struct { User User Session string MFASecret string QRCode string }{ User: user, Session: c.Value, MFASecret: key.Secret(), QRCode: base64.StdEncoding.EncodeToString(buf.Bytes()), }) return nil } res.Write([]byte(Page(`
` + getFlash() + `
`))) return nil } func (this SimpleAuth) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { users, err := getUsers() if err != nil { return nil, err } requestedUser := withMFA(User{ Email: formData["email"], Password: formData["password"], }, formData["session"]) requestedUser.Code = formData["code"] for i := range users { if users[i].Email != requestedUser.Email { continue } if err = bcrypt.CompareHashAndPassword([]byte(users[i].Password), []byte(requestedUser.Password)); err != nil { break } if users[i].Disabled == true { http.SetCookie(res, &http.Cookie{ Name: "flash", Value: "Account is disabled", MaxAge: 1, Path: "/", }) Log.Warning("plg_authentication_simple::auth action=authenticate email=%s err=disabled", users[i].Email) return nil, ErrAuthenticationFailed } if idpParams["mfa"] == "TOTP" { shouldSaveMFAKey := false if users[i].MFA == "" { users[i].MFA = formData["mfa"] shouldSaveMFAKey = true } if totp.Validate(requestedUser.Code, users[i].MFA) == false { requestedUser.MFA = users[i].MFA http.SetCookie(res, &http.Cookie{ Name: "mfa", Value: requestedUser.EncryptedString(), MaxAge: 1, }) return nil, ErrAuthenticationFailed } if shouldSaveMFAKey { saveUsers(users) } } session := map[string]string{ "user": requestedUser.Email, "password": requestedUser.Password, "bcrypt": users[i].Password, "role": users[i].Role, } s := "" for k, v := range session { if k == "password" || k == "bcrypt" { v = "*****" } s += fmt.Sprintf("%s[%s] ", k, v) } Log.Debug("IDP Attributes => %s", s) return session, nil } http.SetCookie(res, &http.Cookie{ Name: "flash", Value: "Invalid username or password", MaxAge: 1, Path: "/", }) return nil, ErrAuthenticationFailed } func withMFA(user User, session string) User { if session == "" { return user } data, err := DecryptString(SECRET_KEY_DERIVATE_FOR_USER, session) if err != nil { return User{} } var u User if err = json.Unmarshal([]byte(data), &u); err != nil { return User{} } user.Email = u.Email user.Password = u.Password return u } func (user User) EncryptedString() string { b, err := json.Marshal(user) if err != nil { return "" } d, err := EncryptString(SECRET_KEY_DERIVATE_FOR_USER, string(b)) if err != nil { return "" } return d }