package charakterin import ( "database/sql" "io/ioutil" "log" "net/http" "net/url" "time" _ "github.com/lib/pq" ) // Renderer wird verwendet, um die Routen (bspw. Login-Route) zu rendern. Damit bleibt Charakterin selbst ohne Template. type Renderer interface { // RenderLoginPage zeigt die Login-Seite an. RenderLoginPage(w http.ResponseWriter, data map[string]interface{}) } // Charakterin ist das tolle Login- und Accountmanagementsystem. type Charakterin struct { renderer Renderer FallbackRoute string Database *sql.DB } // New erstellt eine neue Instanz von Charakterin. func New(db *sql.DB) *Charakterin { if err := db.Ping(); err != nil { log.Fatalln("no valid database connection supplied:", err) return nil } return &Charakterin{ nil, "/", db, } } // UseRenderer sagt charakterin, welchen Renderer es benutzen soll. func (c *Charakterin) UseRenderer(renderer Renderer) { c.renderer = renderer } // DisplayLoginWithData rendert die Loginseite mit Daten (vorheriger Benutzer, Fehlermeldung) func (c *Charakterin) DisplayLoginWithData(w http.ResponseWriter, r *http.Request, data map[string]interface{}) { if c.IsLoggedIn(r) { http.Redirect(w, r, c.FallbackRoute, 302) return } if c.renderer == nil { log.Println("charakterin: no renderer set") return } if _, ok := data["previous_user"]; !ok { data["previous_user"] = "" } if _, ok := data["error"]; !ok { data["error"] = "" } c.renderer.RenderLoginPage(w, data) } // DisplayLogin zeigt die Route für den Login an, wenn der User nicht bereits eingeloggt ist. func (c *Charakterin) DisplayLogin(w http.ResponseWriter, r *http.Request) { c.DisplayLoginWithData(w, r, make(map[string]interface{})) } // Login versucht einen User einzuloggen. func (c *Charakterin) Login(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { return } // POST-Data lesen defer r.Body.Close() data, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } values, err := url.ParseQuery(string(data)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } username := values.Get("username") var result string err = c.Database.QueryRow("SELECT * FROM login.new_session($1, $2)", username, values.Get("password")).Scan(&result) if err != nil { errStr := err.Error() if errStr == "pq: no_such_user" || errStr == "pq: invalid_password" { log.Printf("invalid login attempt by '%s': %s\n", username, errStr[4:]) data := make(map[string]interface{}) data["previous_user"] = username data["error"] = "Ungültiger Benutzername oder Passwort. Oder Lukas hats mal wieder kaputt gemacht." c.DisplayLoginWithData(w, r, data) return } http.Error(w, errStr, http.StatusInternalServerError) return } http.SetCookie(w, &http.Cookie{ Name: "session", Value: result, Expires: time.Now().AddDate(1, 0, 0), }) http.Redirect(w, r, c.FallbackRoute, 302) } // IsLoggedIn überprüft anhand eines Request, ob der User eingeloggt ist. func (c *Charakterin) IsLoggedIn(r *http.Request) bool { cookie, err := r.Cookie("session") if err != nil { log.Println("no session") return false } rows, err := c.Database.Query(`SELECT * FROM login.sessions WHERE id = $1`, cookie.Value) if err != nil { log.Println(err) return false } if rows.Next() { return true } return false }