diff options
-rw-r--r-- | charakterin.go | 179 | ||||
-rw-r--r-- | user.go | 13 |
2 files changed, 157 insertions, 35 deletions
diff --git a/charakterin.go b/charakterin.go index abf9627..142484c 100644 --- a/charakterin.go +++ b/charakterin.go | |||
@@ -12,10 +12,19 @@ import ( | |||
12 | _ "github.com/lib/pq" | 12 | _ "github.com/lib/pq" |
13 | ) | 13 | ) |
14 | 14 | ||
15 | const ( | ||
16 | NoSuchUser = "pq: no_such_user" | ||
17 | InvalidPassword = "pq: invalid_password" | ||
18 | UsernameTaken = "pq: username_taken" | ||
19 | EmailAlreadyRegistered = "pq: email_already_registered" | ||
20 | ) | ||
21 | |||
15 | // Renderer wird verwendet, um die Routen (bspw. Login-Route) zu rendern. Damit bleibt Charakterin selbst ohne Template. | 22 | // Renderer wird verwendet, um die Routen (bspw. Login-Route) zu rendern. Damit bleibt Charakterin selbst ohne Template. |
16 | type Renderer interface { | 23 | type Renderer interface { |
17 | // RenderLoginPage zeigt die Login-Seite an. | 24 | // RenderLoginPage zeigt die Login-Seite an. |
18 | RenderLoginPage(w http.ResponseWriter, data map[string]interface{}) | 25 | RenderLoginPage(w http.ResponseWriter, data map[string]interface{}) |
26 | // RenderRegistrationPage zeigt die Registrations-Seite an. | ||
27 | RenderRegistrationPage(w http.ResponseWriter, data map[string]interface{}) | ||
19 | } | 28 | } |
20 | 29 | ||
21 | // Charakterin ist das tolle Login- und Accountmanagementsystem. | 30 | // Charakterin ist das tolle Login- und Accountmanagementsystem. |
@@ -70,33 +79,35 @@ func (c *Charakterin) DisplayLogin(w http.ResponseWriter, r *http.Request) { | |||
70 | c.DisplayLoginWithData(w, r, make(map[string]interface{})) | 79 | c.DisplayLoginWithData(w, r, make(map[string]interface{})) |
71 | } | 80 | } |
72 | 81 | ||
73 | // Login versucht einen User einzuloggen. | 82 | // LoginRequest versucht, einen Benutzer mit den gegebenen Daten einzuloggen. |
83 | func (c *Charakterin) LoginRequest(username, password string) (string, error) { | ||
84 | var result string | ||
85 | err := c.Database.QueryRow("SELECT * FROM login.new_session($1, $2)", username, password).Scan(&result) | ||
86 | if err != nil { | ||
87 | return "", err | ||
88 | } | ||
89 | return result, nil | ||
90 | } | ||
91 | |||
92 | // Login versucht einen User durch einen Request einzuloggen. | ||
74 | func (c *Charakterin) Login(w http.ResponseWriter, r *http.Request) { | 93 | func (c *Charakterin) Login(w http.ResponseWriter, r *http.Request) { |
75 | if r.Method != "POST" { | 94 | if r.Method != "POST" { |
76 | return | 95 | return |
77 | } | 96 | } |
78 | 97 | ||
79 | // POST-Data lesen | 98 | // POST-Data lesen |
80 | defer r.Body.Close() | 99 | values, err := readBody(r) |
81 | data, err := ioutil.ReadAll(r.Body) | ||
82 | if err != nil { | ||
83 | http.Error(w, err.Error(), http.StatusInternalServerError) | ||
84 | return | ||
85 | } | ||
86 | |||
87 | values, err := url.ParseQuery(string(data)) | ||
88 | if err != nil { | 100 | if err != nil { |
89 | http.Error(w, err.Error(), http.StatusInternalServerError) | 101 | http.Error(w, err.Error(), http.StatusInternalServerError) |
90 | return | 102 | return |
91 | } | 103 | } |
92 | 104 | ||
93 | username := values.Get("username") | 105 | username := values.Get("username") |
94 | var result string | 106 | session, err := c.LoginRequest(username, values.Get("password")) |
95 | err = c.Database.QueryRow("SELECT * FROM login.new_session($1, $2)", username, values.Get("password")).Scan(&result) | ||
96 | if err != nil { | 107 | if err != nil { |
97 | errStr := err.Error() | 108 | errStr := err.Error() |
98 | 109 | ||
99 | if errStr == "pq: no_such_user" || errStr == "pq: invalid_password" { | 110 | if errStr == NoSuchUser || errStr == InvalidPassword { |
100 | log.Printf("invalid login attempt by '%s': %s\n", username, errStr[4:]) | 111 | log.Printf("invalid login attempt by '%s': %s\n", username, errStr[4:]) |
101 | data := make(map[string]interface{}) | 112 | data := make(map[string]interface{}) |
102 | data["previous_user"] = username | 113 | data["previous_user"] = username |
@@ -111,7 +122,7 @@ func (c *Charakterin) Login(w http.ResponseWriter, r *http.Request) { | |||
111 | 122 | ||
112 | http.SetCookie(w, &http.Cookie{ | 123 | http.SetCookie(w, &http.Cookie{ |
113 | Name: "session", | 124 | Name: "session", |
114 | Value: result, | 125 | Value: session, |
115 | Expires: time.Now().AddDate(1, 0, 0), | 126 | Expires: time.Now().AddDate(1, 0, 0), |
116 | }) | 127 | }) |
117 | http.Redirect(w, r, c.FallbackRoute, 302) | 128 | http.Redirect(w, r, c.FallbackRoute, 302) |
@@ -119,32 +130,37 @@ func (c *Charakterin) Login(w http.ResponseWriter, r *http.Request) { | |||
119 | 130 | ||
120 | // Logout loggt einen Charakter aus. Wird direkt über den Request gehandlet. | 131 | // Logout loggt einen Charakter aus. Wird direkt über den Request gehandlet. |
121 | func (c *Charakterin) Logout(w http.ResponseWriter, r *http.Request) { | 132 | func (c *Charakterin) Logout(w http.ResponseWriter, r *http.Request) { |
122 | user, err := c.GetUserFromRequest(r) | 133 | cookie, err := r.Cookie("session") |
123 | if err != nil { | 134 | if err != nil { |
124 | http.Redirect(w, r, c.FallbackRoute, 302) | 135 | http.Redirect(w, r, c.FallbackRoute, 302) |
125 | return | 136 | return |
126 | } | 137 | } |
127 | 138 | ||
128 | stmt, err := c.Database.Prepare("DELETE FROM login.sessions WHERE id=$1") | 139 | stmt, err := c.Database.Prepare("DELETE FROM login.sessions WHERE id=$1") |
129 | if err != nil { | 140 | if err != nil { |
130 | http.Error(w, "500", http.StatusInternalServerError) | 141 | http.Error(w, "500", http.StatusInternalServerError) |
131 | return | 142 | return |
132 | } | 143 | } |
133 | 144 | ||
134 | result, err := stmt.Exec(user.SessionID) | 145 | result, err := stmt.Exec(cookie.Value) |
135 | if err != nil { | 146 | if err != nil { |
136 | log.Println(err) | 147 | log.Println(err) |
137 | http.Error(w, "500", http.StatusInternalServerError) | 148 | http.Error(w, "500", http.StatusInternalServerError) |
138 | return | 149 | return |
139 | } | 150 | } |
140 | 151 | ||
141 | if val, err := result.RowsAffected(); err != nil || val == 0 { | 152 | if val, err := result.RowsAffected(); err != nil || val == 0 { |
142 | log.Println("could not remove session",user.SessionID,err) | 153 | log.Println("could not remove session", cookie.Value, err) |
143 | http.Redirect(w, r, c.FallbackRoute, 302) | 154 | http.Redirect(w, r, c.FallbackRoute, 302) |
144 | return | 155 | return |
145 | } | 156 | } |
146 | 157 | ||
147 | user.Logout(w) | 158 | http.SetCookie(w, &http.Cookie{ |
159 | Name: "session", | ||
160 | Value: "benis", | ||
161 | Expires: time.Now(), | ||
162 | MaxAge: 0, | ||
163 | }) | ||
148 | http.Redirect(w, r, c.FallbackRoute, 302) | 164 | http.Redirect(w, r, c.FallbackRoute, 302) |
149 | } | 165 | } |
150 | 166 | ||
@@ -165,6 +181,96 @@ func (c *Charakterin) IsLoggedIn(r *http.Request) bool { | |||
165 | return true | 181 | return true |
166 | } | 182 | } |
167 | 183 | ||
184 | // DisplayRegistrationWithData rendert die Registration mit Daten (vorheriger Benutzer, Fehlermeldung) | ||
185 | func (c *Charakterin) DisplayRegistrationWithData(w http.ResponseWriter, r *http.Request, data map[string]interface{}) { | ||
186 | if c.IsLoggedIn(r) { | ||
187 | http.Redirect(w, r, c.FallbackRoute, 302) | ||
188 | return | ||
189 | } | ||
190 | |||
191 | if c.renderer == nil { | ||
192 | log.Println("charakterin: no renderer set") | ||
193 | return | ||
194 | } | ||
195 | |||
196 | if _, ok := data["previous_user"]; !ok { | ||
197 | data["previous_user"] = "" | ||
198 | } | ||
199 | if _, ok := data["previous_email"]; !ok { | ||
200 | data["previous_email"] = "" | ||
201 | } | ||
202 | if _, ok := data["error"]; !ok { | ||
203 | data["error"] = "" | ||
204 | } | ||
205 | |||
206 | c.renderer.RenderRegistrationPage(w, data) | ||
207 | } | ||
208 | |||
209 | // DisplayRegistration zeigt die Route für die Registration an, wenn der User nicht bereits eingeloggt ist. | ||
210 | func (c *Charakterin) DisplayRegistration(w http.ResponseWriter, r *http.Request) { | ||
211 | c.DisplayRegistrationWithData(w, r, make(map[string]interface{})) | ||
212 | } | ||
213 | |||
214 | // Register versucht einen Benutzer zu registrieren. | ||
215 | func (c *Charakterin) Register(w http.ResponseWriter, r *http.Request) { | ||
216 | if r.Method != "POST" { | ||
217 | return | ||
218 | } | ||
219 | |||
220 | // POST-Data lesen | ||
221 | values, err := readBody(r) | ||
222 | if err != nil { | ||
223 | http.Error(w, err.Error(), http.StatusInternalServerError) | ||
224 | return | ||
225 | } | ||
226 | |||
227 | username := values.Get("username") | ||
228 | password := values.Get("password") | ||
229 | email := values.Get("email") | ||
230 | var result string | ||
231 | err = c.Database.QueryRow("SELECT * FROM login.register_user($1, $2, $3)", username, password, email).Scan(&result) | ||
232 | if err != nil { | ||
233 | errStr := err.Error() | ||
234 | |||
235 | if errStr == UsernameTaken { | ||
236 | data := make(map[string]interface{}) | ||
237 | data["error"] = "Der Benutzername wird bereits verwendet." | ||
238 | data["previous_email"] = email | ||
239 | c.DisplayLoginWithData(w, r, data) | ||
240 | return | ||
241 | } else if errStr == EmailAlreadyRegistered { | ||
242 | data := make(map[string]interface{}) | ||
243 | data["error"] = "Diese E-Mail wird bereits verwendet." | ||
244 | data["previous_user"] = username | ||
245 | c.DisplayLoginWithData(w, r, data) | ||
246 | return | ||
247 | } | ||
248 | |||
249 | http.Error(w, errStr, http.StatusInternalServerError) | ||
250 | return | ||
251 | } | ||
252 | |||
253 | log.Printf("user '%s' has been registered.\n", username) | ||
254 | if err := c.ConfirmEmail(result); err != nil { | ||
255 | log.Println("could not activate user", err) | ||
256 | http.Error(w, "user created, could not activate", http.StatusInternalServerError) | ||
257 | return | ||
258 | } | ||
259 | session, err := c.LoginRequest(username, password) | ||
260 | if err != nil { | ||
261 | log.Println("failed auto-login for", username, err) | ||
262 | http.Redirect(w, r, c.FallbackRoute, 302) | ||
263 | return | ||
264 | } | ||
265 | |||
266 | http.SetCookie(w, &http.Cookie{ | ||
267 | Name: "session", | ||
268 | Value: session, | ||
269 | Expires: time.Now().AddDate(1, 0, 0), | ||
270 | }) | ||
271 | http.Redirect(w, r, c.FallbackRoute, 302) | ||
272 | } | ||
273 | |||
168 | func (c *Charakterin) GetUserFromRequest(r *http.Request) (*User, error) { | 274 | func (c *Charakterin) GetUserFromRequest(r *http.Request) (*User, error) { |
169 | cookie, err := r.Cookie("session") | 275 | cookie, err := r.Cookie("session") |
170 | if err != nil { | 276 | if err != nil { |
@@ -176,7 +282,8 @@ func (c *Charakterin) GetUserFromRequest(r *http.Request) (*User, error) { | |||
176 | var password []byte | 282 | var password []byte |
177 | var id int | 283 | var id int |
178 | var lastActivity time.Time | 284 | var lastActivity time.Time |
179 | err = c.Database.QueryRow(`SELECT * FROM login.get_user_by_session($1)`, cookie.Value).Scan(&id, &email, &name, &password, &displayName, &lastActivity) | 285 | var isActive bool |
286 | err = c.Database.QueryRow(`SELECT id, email, name, password, display_name, last_activity, is_active FROM login.get_user_by_session($1)`, cookie.Value).Scan(&id, &email, &name, &password, &displayName, &lastActivity, &isActive) | ||
180 | if err != nil { | 287 | if err != nil { |
181 | return nil, err | 288 | return nil, err |
182 | } | 289 | } |
@@ -193,8 +300,34 @@ func (c *Charakterin) GetUserFromRequest(r *http.Request) (*User, error) { | |||
193 | password, | 300 | password, |
194 | dspName, | 301 | dspName, |
195 | lastActivity, | 302 | lastActivity, |
196 | cookie.Value, | 303 | isActive, |
197 | } | 304 | } |
198 | 305 | ||
199 | return user, nil | 306 | return user, nil |
200 | } | 307 | } |
308 | |||
309 | // ConfirmEmail konfirmiert die email addresse mit der gegebenen confirm id. | ||
310 | func (c *Charakterin) ConfirmEmail(confirmId string) error { | ||
311 | var result string | ||
312 | err := c.Database.QueryRow(`SELECT * FROM login.confirm_user_email($1)`, confirmId).Scan(&result) | ||
313 | if err != nil { | ||
314 | return err | ||
315 | } | ||
316 | |||
317 | return nil | ||
318 | } | ||
319 | |||
320 | func readBody(r *http.Request) (url.Values, error) { | ||
321 | defer r.Body.Close() | ||
322 | data, err := ioutil.ReadAll(r.Body) | ||
323 | if err != nil { | ||
324 | return nil, err | ||
325 | } | ||
326 | |||
327 | values, err := url.ParseQuery(string(data)) | ||
328 | if err != nil { | ||
329 | return nil, err | ||
330 | } | ||
331 | |||
332 | return values, nil | ||
333 | } | ||
@@ -2,7 +2,6 @@ package charakterin | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "time" | 4 | "time" |
5 | "net/http" | ||
6 | ) | 5 | ) |
7 | 6 | ||
8 | // Ein User ist ein ganz toller Benutzer. | 7 | // Ein User ist ein ganz toller Benutzer. |
@@ -13,7 +12,7 @@ type User struct { | |||
13 | Password []byte | 12 | Password []byte |
14 | DisplayName string | 13 | DisplayName string |
15 | LastActivity time.Time | 14 | LastActivity time.Time |
16 | SessionID string | 15 | IsActive bool |
17 | } | 16 | } |
18 | 17 | ||
19 | // GetName gibt den Anzeigenamen oder wenn dieser nicht gesetzt ist den Benutzernamen zurück. | 18 | // GetName gibt den Anzeigenamen oder wenn dieser nicht gesetzt ist den Benutzernamen zurück. |
@@ -23,13 +22,3 @@ func (u *User) GetName() string { | |||
23 | } | 22 | } |
24 | return u.Name | 23 | return u.Name |
25 | } | 24 | } |
26 | |||
27 | func (u *User) Logout(w http.ResponseWriter) { | ||
28 | http.SetCookie(w, &http.Cookie{ | ||
29 | Name: "session", | ||
30 | Value: "benis", | ||
31 | Expires: time.Now(), | ||
32 | MaxAge: 0, | ||
33 | }) | ||
34 | return | ||
35 | } \ No newline at end of file | ||