package charakterin import ( "database/sql" "errors" "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) } // Logout loggt einen Charakter aus. Wird direkt über den Request gehandlet. func (c *Charakterin) Logout(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session") if err != nil { http.Redirect(w, r, c.FallbackRoute, 302) return } stmt, err := c.Database.Prepare("DELETE FROM login.sessions WHERE id=$1") if err != nil { http.Redirect(w, r, c.FallbackRoute, 302) return } result, err := stmt.Exec(cookie.Value) if err != nil { log.Println(err) http.Redirect(w, r, c.FallbackRoute, 302) return } if val, err := result.RowsAffected(); err != nil || val == 0 { log.Println("could not remove session",cookie.Value,err) http.Redirect(w, r, c.FallbackRoute, 302) return } http.SetCookie(w, &http.Cookie{ Name: "session", Value: "benis", Expires: time.Now(), MaxAge: 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 { return false } var result string err = c.Database.QueryRow(`SELECT login.get_user_by_session($1)`, cookie.Value).Scan(&result) if err != nil { log.Println(err) return false } return true } func (c *Charakterin) GetUserFromRequest(r *http.Request) (*User, error) { cookie, err := r.Cookie("session") if err != nil { return nil, errors.New("not logged in") } var email, name string var displayName *string var password []byte var id int var lastActivity time.Time err = c.Database.QueryRow(`SELECT * FROM login.get_user_by_session($1)`, cookie.Value).Scan(&id, &email, &name, &password, &displayName, &lastActivity) if err != nil { return nil, err } dspName := "" if displayName != nil { dspName = *displayName } user := &User{ id, name, email, password, dspName, lastActivity, } return user, nil }