implement some auth (user) endpoints
This commit is contained in:
@@ -3,35 +3,217 @@
|
||||
package api_auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.oblat.lv/alex/triggerssmith/internal/auth"
|
||||
"git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
"git.oblat.lv/alex/triggerssmith/internal/server"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func setRefreshCookie(w http.ResponseWriter, token string, ttl time.Duration, secure bool) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: token,
|
||||
Path: "/api/auth/refresh",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: int(ttl.Seconds()),
|
||||
Secure: secure,
|
||||
})
|
||||
}
|
||||
|
||||
type authHandler struct {
|
||||
cfg *config.Config
|
||||
a *auth.Service
|
||||
}
|
||||
|
||||
func MustRoute(config *config.Config) func(chi.Router) {
|
||||
func MustRoute(config *config.Config, authService *auth.Service) func(chi.Router) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
if authService == nil {
|
||||
panic("authService is nil")
|
||||
}
|
||||
h := &authHandler{
|
||||
cfg: config,
|
||||
a: authService,
|
||||
}
|
||||
return func(r chi.Router) {
|
||||
r.Get("/login", h.handleLogin)
|
||||
r.Get("/logout", h.handleLogout)
|
||||
r.Get("/me", h.handleMe)
|
||||
r.Get("/revoke", h.handleRevoke)
|
||||
r.Get("/getUserData", h.handleGetUserData) // legacy support
|
||||
|
||||
r.Post("/register", h.handleRegister)
|
||||
r.Post("/login", h.handleLogin)
|
||||
r.Post("/logout", h.handleLogout) // !requires authentication
|
||||
r.Post("/refresh", h.handleRefresh) // !requires authentication
|
||||
|
||||
r.Get("/me", h.handleMe) // !requires authentication
|
||||
r.Get("/get-user-data", h.handleGetUserData)
|
||||
|
||||
r.Post("/revoke", h.handleRevoke) // not implemented
|
||||
}
|
||||
}
|
||||
|
||||
func (h *authHandler) handleLogin(w http.ResponseWriter, r *http.Request) {}
|
||||
type registerRequest struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (h *authHandler) handleLogout(w http.ResponseWriter, r *http.Request) {}
|
||||
type registerResponse struct {
|
||||
UserID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func (h *authHandler) handleMe(w http.ResponseWriter, r *http.Request) {}
|
||||
func (h *authHandler) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
var req registerRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
func (h *authHandler) handleRevoke(w http.ResponseWriter, r *http.Request) {}
|
||||
user, err := h.a.Register(req.Username, req.Email, req.Password)
|
||||
if err != nil {
|
||||
http.Error(w, "Registration failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(registerResponse{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type loginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
Token string `json:"accessToken"`
|
||||
}
|
||||
|
||||
func (h *authHandler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
var req loginRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
tokens, err := h.a.Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
http.Error(w, "Authentication failed", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
setRefreshCookie(w, tokens.Refresh, h.cfg.Auth.RefreshTokenTTL, false)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(loginResponse{Token: tokens.Access})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *authHandler) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
claims, err := h.a.AuthenticateRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
rjti := claims.(jwt.MapClaims)["rjti"].(string)
|
||||
err = h.a.Logout(rjti)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to logout, taking cookie anyways", http.StatusInternalServerError)
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: "",
|
||||
MaxAge: -1,
|
||||
Path: "/api/users/refresh",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
if err == nil {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
type meResponse struct {
|
||||
UserID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
func (h *authHandler) handleMe(w http.ResponseWriter, r *http.Request) {
|
||||
refresh_token_cookie, err := r.Cookie("refresh_token")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
userID, err := h.a.ValidateRefreshToken(refresh_token_cookie.Value)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
user, err := h.a.Get("id", fmt.Sprint(userID))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(meResponse{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type GetUserDataResponse meResponse
|
||||
|
||||
func (h *authHandler) handleGetUserData(w http.ResponseWriter, r *http.Request) {
|
||||
by := r.URL.Query().Get("by")
|
||||
value := r.URL.Query().Get("value")
|
||||
if value == "" {
|
||||
value = r.URL.Query().Get(by)
|
||||
}
|
||||
user, err := h.a.Get(by, value)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(meResponse{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *authHandler) handleRevoke(w http.ResponseWriter, r *http.Request) {
|
||||
server.NotImplemented(w)
|
||||
}
|
||||
|
||||
func (h *authHandler) handleRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
server.NotImplemented(w)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user