140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"5803025048/W04/internal/auth"
|
|
"5803025048/W04/internal/middleware"
|
|
"5803025048/W04/internal/models"
|
|
"5803025048/W04/internal/store"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// bcrypt menolak password lebih dari 72 byte.
|
|
const maxPasswordBytes = 72
|
|
|
|
type AuthHandler struct {
|
|
userStore UserStore
|
|
sessionStore SessionStore
|
|
}
|
|
|
|
func NewAuthHandler(userStore UserStore, sessionStore SessionStore) *AuthHandler {
|
|
return &AuthHandler{
|
|
userStore: userStore,
|
|
sessionStore: sessionStore,
|
|
}
|
|
}
|
|
|
|
type authRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
|
var req authRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Bad Request")
|
|
return
|
|
}
|
|
|
|
req.Username = strings.TrimSpace(req.Username)
|
|
n := utf8.RuneCountInString(req.Username)
|
|
if n < 3 || n > 32 || req.Password == "" || len(req.Password) > maxPasswordBytes {
|
|
writeError(w, http.StatusBadRequest, "Invalid username or password")
|
|
return
|
|
}
|
|
|
|
hash, err := auth.HashPassword(req.Password)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
|
|
user := &models.User{
|
|
ID: uuid.New().String(),
|
|
Username: req.Username,
|
|
PasswordHash: hash,
|
|
}
|
|
|
|
if err := h.userStore.Create(r.Context(), user); err != nil {
|
|
if errors.Is(err, store.ErrUsernameTaken) {
|
|
writeError(w, http.StatusConflict, "Username already exists")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, user)
|
|
}
|
|
|
|
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|
var req authRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Bad Request")
|
|
return
|
|
}
|
|
|
|
user, err := h.userStore.GetByUsername(r.Context(), req.Username)
|
|
if err != nil {
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
writeError(w, http.StatusUnauthorized, "Invalid credentials")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
if !auth.CheckPassword(req.Password, user.PasswordHash) {
|
|
writeError(w, http.StatusUnauthorized, "Invalid credentials")
|
|
return
|
|
}
|
|
|
|
token, err := auth.GenerateToken()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
|
|
session := &models.Session{
|
|
Token: token,
|
|
UserID: user.ID,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
if err := h.sessionStore.Create(r.Context(), session); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]string{"session_token": session.Token})
|
|
}
|
|
|
|
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
|
token, ok := middleware.BearerToken(r)
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "Unauthorized")
|
|
return
|
|
}
|
|
|
|
if _, err := h.sessionStore.Get(r.Context(), token); err != nil {
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
writeError(w, http.StatusUnauthorized, "Unauthorized")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
|
|
if err := h.sessionStore.Delete(r.Context(), token); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Internal Server Error")
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|