internal/api/auth.go (view raw)
1package api
2
3import (
4 "encoding/json"
5 "net/http"
6
7 "github.com/mjensen/weimar/internal/auth"
8)
9
10type loginRequest struct {
11 Username string `json:"username"`
12 Password string `json:"password"`
13}
14
15type registerRequest struct {
16 Username string `json:"username"`
17 Password string `json:"password"`
18}
19
20func (a *API) HandleLogin(w http.ResponseWriter, r *http.Request) {
21 var req loginRequest
22 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
23 writeError(w, http.StatusBadRequest, "invalid JSON body")
24 return
25 }
26 if req.Username == "" || req.Password == "" {
27 writeError(w, http.StatusBadRequest, "username and password are required")
28 return
29 }
30
31 user, token, err := a.Auth.Login(r.Context(), req.Username, req.Password)
32 if err != nil {
33 if err == auth.ErrInvalidCredentials {
34 writeError(w, http.StatusUnauthorized, "invalid username or password")
35 return
36 }
37 writeError(w, http.StatusInternalServerError, "internal error")
38 return
39 }
40
41 http.SetCookie(w, &http.Cookie{
42 Name: auth.SessionCookieName,
43 Value: token,
44 Path: "/",
45 HttpOnly: true,
46 SameSite: http.SameSiteLaxMode,
47 })
48
49 writeJSON(w, http.StatusOK, user)
50}
51
52func (a *API) HandleRegister(w http.ResponseWriter, r *http.Request) {
53 if !a.Cfg.Auth.AllowRegistration {
54 writeError(w, http.StatusForbidden, "registration is closed")
55 return
56 }
57
58 var req registerRequest
59 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
60 writeError(w, http.StatusBadRequest, "invalid JSON body")
61 return
62 }
63
64 user, err := auth.Register(r.Context(), a.DB, req.Username, req.Password, false)
65 if err != nil {
66 switch err {
67 case auth.ErrUsernameTooShort, auth.ErrPasswordTooShort, auth.ErrUsernameTaken:
68 writeError(w, http.StatusBadRequest, err.Error())
69 return
70 default:
71 writeError(w, http.StatusInternalServerError, "internal error")
72 return
73 }
74 }
75
76 // Auto-login after successful registration so the user doesn't need to
77 // make a separate login call. If this fails we still return the user.
78 if _, token, err := a.Auth.Login(r.Context(), req.Username, req.Password); err == nil {
79 http.SetCookie(w, &http.Cookie{
80 Name: auth.SessionCookieName,
81 Value: token,
82 Path: "/",
83 HttpOnly: true,
84 SameSite: http.SameSiteLaxMode,
85 })
86 }
87
88 writeJSON(w, http.StatusCreated, user)
89}
90
91func (a *API) HandleLogout(w http.ResponseWriter, r *http.Request) {
92 cookie, err := r.Cookie(auth.SessionCookieName)
93 if err != nil {
94 writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
95 return
96 }
97
98 a.Auth.Logout(r.Context(), cookie.Value)
99
100 http.SetCookie(w, &http.Cookie{
101 Name: auth.SessionCookieName,
102 Value: "",
103 Path: "/",
104 HttpOnly: true,
105 SameSite: http.SameSiteLaxMode,
106 MaxAge: -1,
107 })
108
109 writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
110}