Compare commits
5 Commits
53ee156e67
...
8d38a86f86
Author | SHA1 | Date | |
---|---|---|---|
8d38a86f86 | |||
e0c095c24d | |||
4c318b15cd | |||
5ea6bc4251 | |||
1cbe908489 |
@ -2,15 +2,12 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.local/admin/hspguard/internal/repository"
|
"gitea.local/admin/hspguard/internal/repository"
|
||||||
"gitea.local/admin/hspguard/internal/types"
|
|
||||||
"gitea.local/admin/hspguard/internal/util"
|
"gitea.local/admin/hspguard/internal/util"
|
||||||
"gitea.local/admin/hspguard/internal/web"
|
"gitea.local/admin/hspguard/internal/web"
|
||||||
"github.com/avct/uasurfer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginParams struct {
|
type LoginParams struct {
|
||||||
@ -36,12 +33,14 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
user, err := h.repo.FindUserEmail(r.Context(), params.Email)
|
user, err := h.repo.FindUserEmail(r.Context(), params.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
web.Error(w, "user with provided email does not exists", http.StatusBadRequest)
|
log.Printf("DEBUG: No user found with '%s' email: %v\n", params.Email, err)
|
||||||
|
web.Error(w, "email or/and password are incorrect", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.VerifyPassword(params.Password, user.PasswordHash) {
|
if !util.VerifyPassword(params.Password, user.PasswordHash) {
|
||||||
web.Error(w, "username or/and password are incorrect", http.StatusBadRequest)
|
log.Printf("DEBUG: Incorrect password '%s' for '%s' email: %v\n", params.Password, params.Email, err)
|
||||||
|
web.Error(w, "email or/and password are incorrect", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,29 +52,8 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
userAgent := r.UserAgent()
|
userAgent := r.UserAgent()
|
||||||
|
|
||||||
var deviceInfo types.DeviceInfo
|
ipAddr := util.GetClientIP(r)
|
||||||
|
deviceInfo := util.BuildDeviceInfo(userAgent, ipAddr)
|
||||||
parsed := uasurfer.Parse(userAgent)
|
|
||||||
|
|
||||||
deviceInfo.Browser = parsed.Browser.Name.StringTrimPrefix()
|
|
||||||
deviceInfo.BrowserVersion = fmt.Sprintf("%d.%d.%d", parsed.Browser.Version.Major, parsed.Browser.Version.Minor, parsed.Browser.Version.Patch)
|
|
||||||
deviceInfo.DeviceName = fmt.Sprintf("%s %s", parsed.OS.Platform.StringTrimPrefix(), parsed.OS.Name.StringTrimPrefix())
|
|
||||||
deviceInfo.DeviceType = parsed.DeviceType.StringTrimPrefix()
|
|
||||||
deviceInfo.OS = parsed.OS.Platform.StringTrimPrefix()
|
|
||||||
deviceInfo.OSVersion = fmt.Sprintf("%d.%d.%d", parsed.OS.Version.Major, parsed.OS.Version.Minor, parsed.OS.Version.Patch)
|
|
||||||
deviceInfo.UserAgent = userAgent
|
|
||||||
|
|
||||||
if location, err := util.GetLocation(r.RemoteAddr); err != nil {
|
|
||||||
log.Println("WARN: Failed to get location from ip (%s): %v\n", r.RemoteAddr, err)
|
|
||||||
} else {
|
|
||||||
deviceInfo.Location = fmt.Sprintf("%s, %s, %s", location.Country, location.Region, location.City)
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(deviceInfo)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERR: Failed to serialize device info: %v\n", err)
|
|
||||||
serialized = []byte{'{', '}'}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create User Session
|
// Create User Session
|
||||||
session, err := h.repo.CreateUserSession(r.Context(), repository.CreateUserSessionParams{
|
session, err := h.repo.CreateUserSession(r.Context(), repository.CreateUserSessionParams{
|
||||||
@ -83,17 +61,17 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
|
|||||||
SessionType: "user",
|
SessionType: "user",
|
||||||
ExpiresAt: &refresh.ExpiresAt,
|
ExpiresAt: &refresh.ExpiresAt,
|
||||||
LastActive: nil,
|
LastActive: nil,
|
||||||
IpAddress: &r.RemoteAddr,
|
IpAddress: &ipAddr,
|
||||||
UserAgent: &userAgent,
|
UserAgent: &userAgent,
|
||||||
AccessTokenID: &access.ID,
|
AccessTokenID: &access.ID,
|
||||||
RefreshTokenID: &refresh.ID,
|
RefreshTokenID: &refresh.ID,
|
||||||
DeviceInfo: serialized,
|
DeviceInfo: deviceInfo,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("ERR: Failedd to create user session after logging in: %v\n", err)
|
log.Printf("ERR: Failed to create user session after logging in: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("INFO: User session created for '%s': %#v\n", user.Email, session)
|
log.Printf("INFO: User session created for '%s' with '%s' id\n", user.Email, session.ID.String())
|
||||||
|
|
||||||
if err := h.repo.UpdateLastLogin(r.Context(), user.ID); err != nil {
|
if err := h.repo.UpdateLastLogin(r.Context(), user.ID); err != nil {
|
||||||
web.Error(w, "failed to update user's last login", http.StatusInternalServerError)
|
web.Error(w, "failed to update user's last login", http.StatusInternalServerError)
|
||||||
|
@ -3,10 +3,12 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.local/admin/hspguard/internal/repository"
|
||||||
"gitea.local/admin/hspguard/internal/types"
|
"gitea.local/admin/hspguard/internal/types"
|
||||||
"gitea.local/admin/hspguard/internal/util"
|
"gitea.local/admin/hspguard/internal/util"
|
||||||
"gitea.local/admin/hspguard/internal/web"
|
"gitea.local/admin/hspguard/internal/web"
|
||||||
@ -64,6 +66,44 @@ func (h *AuthHandler) refreshToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jti, err := uuid.Parse(userClaims.ID)
|
||||||
|
if session, err := h.repo.GetUserSessionByRefreshJTI(r.Context(), &jti); err != nil {
|
||||||
|
log.Printf("WARN: No existing user session found for user with '%s' email (jti: '%s'): %v\n", user.Email, userClaims.ID, err)
|
||||||
|
|
||||||
|
userAgent := r.UserAgent()
|
||||||
|
|
||||||
|
ipAddr := util.GetClientIP(r)
|
||||||
|
deviceInfo := util.BuildDeviceInfo(userAgent, ipAddr)
|
||||||
|
|
||||||
|
// Create User Session
|
||||||
|
session, err := h.repo.CreateUserSession(r.Context(), repository.CreateUserSessionParams{
|
||||||
|
UserID: user.ID,
|
||||||
|
SessionType: "user",
|
||||||
|
ExpiresAt: &refresh.ExpiresAt,
|
||||||
|
LastActive: nil,
|
||||||
|
IpAddress: &ipAddr,
|
||||||
|
UserAgent: &userAgent,
|
||||||
|
AccessTokenID: &access.ID,
|
||||||
|
RefreshTokenID: &refresh.ID,
|
||||||
|
DeviceInfo: deviceInfo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR: Failed to create user session after logging in: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("INFO: User session created for '%s' with '%s' id\n", user.Email, session.ID.String())
|
||||||
|
} else {
|
||||||
|
err := h.repo.UpdateSessionTokens(r.Context(), repository.UpdateSessionTokensParams{
|
||||||
|
ID: session.ID,
|
||||||
|
AccessTokenID: &access.ID,
|
||||||
|
RefreshTokenID: &refresh.ID,
|
||||||
|
ExpiresAt: &refresh.ExpiresAt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR: Failed to update user session with '%s' id: %v\n", session.ID.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
AccessToken string `json:"access"`
|
AccessToken string `json:"access"`
|
||||||
RefreshToken string `json:"refresh"`
|
RefreshToken string `json:"refresh"`
|
||||||
|
@ -2,7 +2,10 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocationResult struct {
|
type LocationResult struct {
|
||||||
@ -14,7 +17,7 @@ type LocationResult struct {
|
|||||||
func GetLocation(ip string) (LocationResult, error) {
|
func GetLocation(ip string) (LocationResult, error) {
|
||||||
var loc LocationResult
|
var loc LocationResult
|
||||||
// Example using ipinfo.io free API
|
// Example using ipinfo.io free API
|
||||||
resp, err := http.Get("https://ipinfo.io/" + ip + "/json")
|
resp, err := http.Get("http://ip-api.com/json/" + ip + "?fields=25")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return loc, err
|
return loc, err
|
||||||
}
|
}
|
||||||
@ -22,3 +25,22 @@ func GetLocation(ip string) (LocationResult, error) {
|
|||||||
json.NewDecoder(resp.Body).Decode(&loc)
|
json.NewDecoder(resp.Body).Decode(&loc)
|
||||||
return loc, nil
|
return loc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetClientIP(r *http.Request) string {
|
||||||
|
// This header will be set by ngrok to the original client IP
|
||||||
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||||
|
log.Printf("DEBUG: Getting IP from X-Forwarded-For: %s\n", xff)
|
||||||
|
// X-Forwarded-For: client, proxy1, proxy2, ...
|
||||||
|
ips := strings.Split(xff, ",")
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return strings.TrimSpace(ips[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to RemoteAddr (not the real client IP, but just in case)
|
||||||
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
log.Printf("DEBUG: Falling to request remote addr: %s (%s)\n", host, r.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
39
internal/util/session.go
Normal file
39
internal/util/session.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gitea.local/admin/hspguard/internal/types"
|
||||||
|
"github.com/avct/uasurfer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildDeviceInfo(userAgent string, remoteAddr string) []byte {
|
||||||
|
var deviceInfo types.DeviceInfo
|
||||||
|
|
||||||
|
parsed := uasurfer.Parse(userAgent)
|
||||||
|
|
||||||
|
deviceInfo.Browser = parsed.Browser.Name.StringTrimPrefix()
|
||||||
|
deviceInfo.BrowserVersion = fmt.Sprintf("%d.%d.%d", parsed.Browser.Version.Major, parsed.Browser.Version.Minor, parsed.Browser.Version.Patch)
|
||||||
|
deviceInfo.DeviceName = fmt.Sprintf("%s %s", parsed.OS.Platform.StringTrimPrefix(), parsed.OS.Name.StringTrimPrefix())
|
||||||
|
deviceInfo.DeviceType = parsed.DeviceType.StringTrimPrefix()
|
||||||
|
deviceInfo.OS = parsed.OS.Platform.StringTrimPrefix()
|
||||||
|
deviceInfo.OSVersion = fmt.Sprintf("%d.%d.%d", parsed.OS.Version.Major, parsed.OS.Version.Minor, parsed.OS.Version.Patch)
|
||||||
|
deviceInfo.UserAgent = userAgent
|
||||||
|
|
||||||
|
if location, err := GetLocation(remoteAddr); err != nil {
|
||||||
|
log.Printf("WARN: Failed to get location from ip (%s): %v\n", remoteAddr, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("DEBUG: Response from IP fetcher: %#v\n", location)
|
||||||
|
deviceInfo.Location = fmt.Sprintf("%s, %s, %s", location.Country, location.Region, location.City)
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(deviceInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR: Failed to serialize device info: %v\n", err)
|
||||||
|
serialized = []byte{'{', '}'}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialized
|
||||||
|
}
|
Reference in New Issue
Block a user