wip 2
This commit is contained in:
227
api/v1/auth.go
227
api/v1/auth.go
@@ -3,7 +3,6 @@ package v1
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -13,24 +12,125 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// authData represents session authentication data
|
||||
type authData struct {
|
||||
UserName string
|
||||
IsAdmin bool
|
||||
AuthHash string
|
||||
// POST /auth/login
|
||||
func (s *Server) Login(ctx context.Context, request LoginRequestObject) (LoginResponseObject, error) {
|
||||
if request.Body == nil {
|
||||
return Login400JSONResponse{Code: 400, Message: "Invalid request body"}, nil
|
||||
}
|
||||
|
||||
req := *request.Body
|
||||
if req.Username == "" || req.Password == "" {
|
||||
return Login400JSONResponse{Code: 400, Message: "Invalid credentials"}, nil
|
||||
}
|
||||
|
||||
// MD5 - KOSync compatibility
|
||||
password := fmt.Sprintf("%x", md5.Sum([]byte(req.Password)))
|
||||
|
||||
// Verify credentials
|
||||
user, err := s.db.Queries.GetUser(ctx, req.Username)
|
||||
if err != nil {
|
||||
return Login401JSONResponse{Code: 401, Message: "Invalid credentials"}, nil
|
||||
}
|
||||
|
||||
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || !match {
|
||||
return Login401JSONResponse{Code: 401, Message: "Invalid credentials"}, nil
|
||||
}
|
||||
|
||||
// Get request and response from context (set by middleware)
|
||||
r := s.getRequestFromContext(ctx)
|
||||
w := s.getResponseWriterFromContext(ctx)
|
||||
|
||||
if r == nil || w == nil {
|
||||
return Login500JSONResponse{Code: 500, Message: "Internal context error"}, nil
|
||||
}
|
||||
|
||||
// Create session
|
||||
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
||||
if s.cfg.CookieEncKey != "" {
|
||||
if len(s.cfg.CookieEncKey) == 16 || len(s.cfg.CookieEncKey) == 32 {
|
||||
store = sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey), []byte(s.cfg.CookieEncKey))
|
||||
}
|
||||
}
|
||||
|
||||
session, _ := store.Get(r, "token")
|
||||
session.Values["authorizedUser"] = user.ID
|
||||
session.Values["isAdmin"] = user.Admin
|
||||
session.Values["expiresAt"] = time.Now().Unix() + (60 * 60 * 24 * 7)
|
||||
session.Values["authHash"] = *user.AuthHash
|
||||
|
||||
if err := session.Save(r, w); err != nil {
|
||||
return Login500JSONResponse{Code: 500, Message: "Failed to create session"}, nil
|
||||
}
|
||||
|
||||
return Login200JSONResponse{
|
||||
Username: user.ID,
|
||||
IsAdmin: user.Admin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// withAuth wraps a handler with session authentication
|
||||
func (s *Server) withAuth(handler http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
auth, ok := s.getSession(r)
|
||||
if !ok {
|
||||
writeJSONError(w, http.StatusUnauthorized, "Unauthorized")
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), "auth", auth)
|
||||
handler(w, r.WithContext(ctx))
|
||||
// POST /auth/logout
|
||||
func (s *Server) Logout(ctx context.Context, request LogoutRequestObject) (LogoutResponseObject, error) {
|
||||
_, ok := s.getSessionFromContext(ctx)
|
||||
if !ok {
|
||||
return Logout401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||
}
|
||||
|
||||
r := s.getRequestFromContext(ctx)
|
||||
w := s.getResponseWriterFromContext(ctx)
|
||||
|
||||
if r == nil || w == nil {
|
||||
return Logout401JSONResponse{Code: 401, Message: "Internal context error"}, nil
|
||||
}
|
||||
|
||||
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
||||
session, _ := store.Get(r, "token")
|
||||
session.Values = make(map[any]any)
|
||||
|
||||
if err := session.Save(r, w); err != nil {
|
||||
return Logout401JSONResponse{Code: 401, Message: "Failed to logout"}, nil
|
||||
}
|
||||
|
||||
return Logout200Response{}, nil
|
||||
}
|
||||
|
||||
// GET /auth/me
|
||||
func (s *Server) GetMe(ctx context.Context, request GetMeRequestObject) (GetMeResponseObject, error) {
|
||||
auth, ok := s.getSessionFromContext(ctx)
|
||||
if !ok {
|
||||
return GetMe401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||
}
|
||||
|
||||
return GetMe200JSONResponse{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getSessionFromContext extracts authData from context
|
||||
func (s *Server) getSessionFromContext(ctx context.Context) (authData, bool) {
|
||||
auth, ok := ctx.Value("auth").(authData)
|
||||
if !ok {
|
||||
return authData{}, false
|
||||
}
|
||||
return auth, true
|
||||
}
|
||||
|
||||
// getRequestFromContext extracts the HTTP request from context
|
||||
func (s *Server) getRequestFromContext(ctx context.Context) *http.Request {
|
||||
r, ok := ctx.Value("request").(*http.Request)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// getResponseWriterFromContext extracts the response writer from context
|
||||
func (s *Server) getResponseWriterFromContext(ctx context.Context) http.ResponseWriter {
|
||||
w, ok := ctx.Value("response").(http.ResponseWriter)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// getSession retrieves auth data from the session cookie
|
||||
@@ -86,94 +186,9 @@ func (s *Server) getUserAuthHash(ctx context.Context, username string) (string,
|
||||
return *user.AuthHash, nil
|
||||
}
|
||||
|
||||
// apiLogin handles POST /api/v1/auth/login
|
||||
func (s *Server) apiLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSONError(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
var req LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSONError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" || req.Password == "" {
|
||||
writeJSONError(w, http.StatusBadRequest, "Invalid credentials")
|
||||
return
|
||||
}
|
||||
|
||||
// MD5 - KOSync compatibility
|
||||
password := fmt.Sprintf("%x", md5.Sum([]byte(req.Password)))
|
||||
|
||||
// Verify credentials
|
||||
user, err := s.db.Queries.GetUser(r.Context(), req.Username)
|
||||
if err != nil {
|
||||
writeJSONError(w, http.StatusUnauthorized, "Invalid credentials")
|
||||
return
|
||||
}
|
||||
|
||||
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || !match {
|
||||
writeJSONError(w, http.StatusUnauthorized, "Invalid credentials")
|
||||
return
|
||||
}
|
||||
|
||||
// Create session
|
||||
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
||||
if s.cfg.CookieEncKey != "" {
|
||||
if len(s.cfg.CookieEncKey) == 16 || len(s.cfg.CookieEncKey) == 32 {
|
||||
store = sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey), []byte(s.cfg.CookieEncKey))
|
||||
}
|
||||
}
|
||||
|
||||
session, _ := store.Get(r, "token")
|
||||
session.Values["authorizedUser"] = user.ID
|
||||
session.Values["isAdmin"] = user.Admin
|
||||
session.Values["expiresAt"] = time.Now().Unix() + (60 * 60 * 24 * 7)
|
||||
session.Values["authHash"] = *user.AuthHash
|
||||
|
||||
if err := session.Save(r, w); err != nil {
|
||||
writeJSONError(w, http.StatusInternalServerError, "Failed to create session")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, LoginResponse{
|
||||
Username: user.ID,
|
||||
IsAdmin: user.Admin,
|
||||
})
|
||||
// authData represents authenticated user information
|
||||
type authData struct {
|
||||
UserName string
|
||||
IsAdmin bool
|
||||
AuthHash string
|
||||
}
|
||||
|
||||
// apiLogout handles POST /api/v1/auth/logout
|
||||
func (s *Server) apiLogout(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSONError(w, http.StatusMethodNotAllowed, "Method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
store := sessions.NewCookieStore([]byte(s.cfg.CookieAuthKey))
|
||||
session, _ := store.Get(r, "token")
|
||||
session.Values = make(map[any]any)
|
||||
|
||||
if err := session.Save(r, w); err != nil {
|
||||
writeJSONError(w, http.StatusInternalServerError, "Failed to logout")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "logged out"})
|
||||
}
|
||||
|
||||
// apiGetMe handles GET /api/v1/auth/me
|
||||
func (s *Server) apiGetMe(w http.ResponseWriter, r *http.Request) {
|
||||
auth, ok := r.Context().Value("auth").(authData)
|
||||
if !ok {
|
||||
writeJSONError(w, http.StatusUnauthorized, "Unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, UserData{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user