diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/filekeeper.iml b/.idea/filekeeper.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/filekeeper.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..eefade7
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..33fcc00
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/db/model/user/user.go b/db/model/user/user.go
index 81917d3..bd2240c 100644
--- a/db/model/user/user.go
+++ b/db/model/user/user.go
@@ -4,23 +4,31 @@ import (
"fmt"
"github.com/fossyy/filekeeper/db"
"github.com/fossyy/filekeeper/logger"
- "github.com/fossyy/filekeeper/types"
+ "github.com/google/uuid"
"sync"
"time"
)
type Cache struct {
- users map[string]*types.UserWithExpired
+ users map[string]*UserWithExpired
mu sync.Mutex
}
+type UserWithExpired struct {
+ UserID uuid.UUID
+ Username string
+ Email string
+ Password string
+ AccessAt time.Time
+}
+
var log *logger.AggregatedLogger
var UserCache *Cache
func init() {
log = logger.Logger()
- UserCache = &Cache{users: make(map[string]*types.UserWithExpired)}
+ UserCache = &Cache{users: make(map[string]*UserWithExpired)}
ticker := time.NewTicker(time.Hour * 8)
go func() {
@@ -44,7 +52,7 @@ func init() {
}()
}
-func Get(email string) (*types.UserWithExpired, error) {
+func Get(email string) (*UserWithExpired, error) {
UserCache.mu.Lock()
defer UserCache.mu.Unlock()
@@ -52,13 +60,13 @@ func Get(email string) (*types.UserWithExpired, error) {
return user, nil
}
- var userData types.UserWithExpired
+ var userData UserWithExpired
err := db.DB.Table("users").Where("email = ?", email).First(&userData).Error
if err != nil {
return nil, err
}
- UserCache.users[email] = &types.UserWithExpired{
+ UserCache.users[email] = &UserWithExpired{
UserID: userData.UserID,
Username: userData.Username,
Email: userData.Email,
diff --git a/handler/signin/signin.go b/handler/signin/signin.go
index 13c8bd0..ef8a55a 100644
--- a/handler/signin/signin.go
+++ b/handler/signin/signin.go
@@ -9,6 +9,7 @@ import (
"github.com/fossyy/filekeeper/utils"
signinView "github.com/fossyy/filekeeper/view/signin"
"net/http"
+ "strings"
)
var log *logger.AggregatedLogger
@@ -64,8 +65,21 @@ func POST(w http.ResponseWriter, r *http.Request) {
Authenticated: true,
}
+ userAgent := r.Header.Get("User-Agent")
+ browserInfo, osInfo := parseUserAgent(userAgent)
+
+ sessionInfo := session.SessionInfo{
+ SessionID: storeSession.ID,
+ Browser: browserInfo["browser"],
+ Version: browserInfo["version"],
+ OS: osInfo["os"],
+ OSVersion: osInfo["version"],
+ IP: utils.ClientIP(r),
+ Location: "Indonesia",
+ }
+
storeSession.Save(w)
- session.AppendSession(email, storeSession)
+ session.AppendSession(email, &sessionInfo)
cookie, err := r.Cookie("redirect")
if errors.Is(err, http.ErrNoCookie) {
@@ -90,3 +104,64 @@ func POST(w http.ResponseWriter, r *http.Request) {
return
}
}
+
+func parseUserAgent(userAgent string) (map[string]string, map[string]string) {
+ browserInfo := make(map[string]string)
+ osInfo := make(map[string]string)
+ if strings.Contains(userAgent, "Firefox") {
+ browserInfo["browser"] = "Firefox"
+ parts := strings.Split(userAgent, "Firefox/")
+ if len(parts) > 1 {
+ version := strings.Split(parts[1], " ")[0]
+ browserInfo["version"] = version
+ }
+ } else if strings.Contains(userAgent, "Chrome") {
+ browserInfo["browser"] = "Chrome"
+ parts := strings.Split(userAgent, "Chrome/")
+ if len(parts) > 1 {
+ version := strings.Split(parts[1], " ")[0]
+ browserInfo["version"] = version
+ }
+ } else {
+ browserInfo["browser"] = "Unknown"
+ browserInfo["version"] = "Unknown"
+ }
+
+ if strings.Contains(userAgent, "Windows") {
+ osInfo["os"] = "Windows"
+ parts := strings.Split(userAgent, "Windows ")
+ if len(parts) > 1 {
+ version := strings.Split(parts[1], ";")[0]
+ osInfo["version"] = version
+ }
+ } else if strings.Contains(userAgent, "Macintosh") {
+ osInfo["os"] = "Mac OS"
+ parts := strings.Split(userAgent, "Mac OS X ")
+ if len(parts) > 1 {
+ version := strings.Split(parts[1], ";")[0]
+ osInfo["version"] = version
+ }
+ } else if strings.Contains(userAgent, "Linux") {
+ osInfo["os"] = "Linux"
+ osInfo["version"] = "Unknown"
+ } else if strings.Contains(userAgent, "Android") {
+ osInfo["os"] = "Android"
+ parts := strings.Split(userAgent, "Android ")
+ if len(parts) > 1 {
+ version := strings.Split(parts[1], ";")[0]
+ osInfo["version"] = version
+ }
+ } else if strings.Contains(userAgent, "iPhone") || strings.Contains(userAgent, "iPad") || strings.Contains(userAgent, "iPod") {
+ osInfo["os"] = "iOS"
+ parts := strings.Split(userAgent, "OS ")
+ if len(parts) > 1 {
+ version := strings.Split(parts[1], " ")[0]
+ osInfo["version"] = version
+ }
+ } else {
+ osInfo["os"] = "Unknown"
+ osInfo["version"] = "Unknown"
+ }
+
+ return browserInfo, osInfo
+}
diff --git a/handler/user/user.go b/handler/user/user.go
index 8b4cc59..a11d2e3 100644
--- a/handler/user/user.go
+++ b/handler/user/user.go
@@ -34,7 +34,8 @@ func GET(w http.ResponseWriter, r *http.Request) {
log.Error(err.Error())
return
}
- component := userView.Main("User Page", userSession.Email, userSession.Username)
+
+ component := userView.Main("User Page", userSession.Email, userSession.Username, session.UserSessions[userSession.Email])
err = component.Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/middleware/middleware.go b/middleware/middleware.go
index 55324a2..4b94b3e 100644
--- a/middleware/middleware.go
+++ b/middleware/middleware.go
@@ -75,6 +75,7 @@ func Auth(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
return
}
storeSession, err := session.Store.Get(cookie.Value)
+
if err != nil {
if errors.Is(err, &session.SessionNotFound{}) {
storeSession.Destroy(w)
@@ -87,6 +88,7 @@ func Auth(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
}
userSession := GetUser(storeSession)
if userSession.Authenticated {
+ session.GetSessionInfo(storeSession.Values["user"].(types.User).Email, cookie.Value).UpdateAccessTime()
next.ServeHTTP(w, r)
return
}
diff --git a/routes/routes.go b/routes/routes.go
index 4c5646c..58fdb63 100644
--- a/routes/routes.go
+++ b/routes/routes.go
@@ -1,6 +1,7 @@
package routes
import (
+ "encoding/json"
downloadHandler "github.com/fossyy/filekeeper/handler/download"
downloadFileHandler "github.com/fossyy/filekeeper/handler/download/file"
forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword"
@@ -15,6 +16,7 @@ import (
"github.com/fossyy/filekeeper/handler/upload/initialisation"
userHandler "github.com/fossyy/filekeeper/handler/user"
"github.com/fossyy/filekeeper/middleware"
+ "github.com/fossyy/filekeeper/session"
"net/http"
)
@@ -35,6 +37,10 @@ func SetupRoutes() *http.ServeMux {
}
})
+ handler.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
+ json.NewEncoder(w).Encode(session.Getses())
+ })
+
handler.HandleFunc("/signin", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
diff --git a/session/session.go b/session/session.go
index 67f1fac..0bef255 100644
--- a/session/session.go
+++ b/session/session.go
@@ -5,6 +5,7 @@ import (
"net/http"
"strconv"
"sync"
+ "time"
)
type Session struct {
@@ -17,8 +18,21 @@ type StoreSession struct {
mu sync.Mutex
}
+type SessionInfo struct {
+ SessionID string
+ Browser string
+ Version string
+ OS string
+ OSVersion string
+ IP string
+ Location string
+ AccessAt string
+}
+
+type ListSessionInfo map[string][]*SessionInfo
+
var Store = StoreSession{Sessions: make(map[string]*Session)}
-var userSessions = make(map[string][]string)
+var UserSessions = make(ListSessionInfo)
type SessionNotFound struct{}
@@ -68,33 +82,48 @@ func (s *Session) Destroy(w http.ResponseWriter) {
})
}
-func AppendSession(email string, session *Session) {
- userSessions[email] = append(userSessions[email], session.ID)
+func AppendSession(email string, sessionInfo *SessionInfo) {
+ UserSessions[email] = append(UserSessions[email], sessionInfo)
}
func RemoveSession(email string, id string) {
- sessions := userSessions[email]
- var updatedSessions []string
+ sessions := UserSessions[email]
+ var updatedSessions []*SessionInfo
for _, userSession := range sessions {
- if userSession != id {
+ if userSession.SessionID != id {
updatedSessions = append(updatedSessions, userSession)
}
}
if len(updatedSessions) > 0 {
- userSessions[email] = updatedSessions
+ UserSessions[email] = updatedSessions
return
}
- delete(userSessions, email)
+ delete(UserSessions, email)
}
func RemoveAllSession(email string) {
- sessions := userSessions[email]
+ sessions := UserSessions[email]
for _, session := range sessions {
- delete(Store.Sessions, session)
+ delete(Store.Sessions, session.SessionID)
}
- delete(userSessions, email)
+ delete(UserSessions, email)
}
-func Getses() map[string][]string {
- return userSessions
+func GetSessionInfo(email string, id string) *SessionInfo {
+ for _, session := range UserSessions[email] {
+ if session.SessionID == id {
+ return session
+ }
+ }
+ return nil
+}
+
+func (user *SessionInfo) UpdateAccessTime() {
+ currentTime := time.Now()
+ formattedTime := currentTime.Format("01-02-2006")
+ user.AccessAt = formattedTime
+}
+
+func Getses() *ListSessionInfo {
+ return &UserSessions
}
diff --git a/types/types.go b/types/types.go
index e7a2683..5744ef9 100644
--- a/types/types.go
+++ b/types/types.go
@@ -2,7 +2,6 @@ package types
import (
"github.com/google/uuid"
- "time"
)
type Message struct {
@@ -17,14 +16,6 @@ type User struct {
Authenticated bool
}
-type UserWithExpired struct {
- UserID uuid.UUID
- Username string
- Email string
- Password string
- AccessAt time.Time
-}
-
type FileInfo struct {
Name string `json:"name"`
Size int `json:"size"`
diff --git a/view/user/user.templ b/view/user/user.templ
index 3e520b1..6ae8132 100644
--- a/view/user/user.templ
+++ b/view/user/user.templ
@@ -1,53 +1,267 @@
package userView
-import "github.com/fossyy/filekeeper/view/layout"
+import (
+ "github.com/fossyy/filekeeper/view/layout"
+ "github.com/fossyy/filekeeper/session"
+)
-templ content(email string, username string, title string) {
+templ content(email string, username string, title string, ListSession []*session.SessionInfo) {
@layout.Base(title){
-
-
-
- User Profile
-
-
- This is some information about the user.
-
+
+
+
+
+ Profile
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JL
+
+
+
+
+
+
+
+ Session Management
+
+
+
+
+
+
+
+
+
+ IP Address
+ |
+
+ Browser
+ |
+
+ Device
+ |
+
+ Last Activity
+ |
+
+ Actions
+ |
+
+
+
+ for _, ses := range ListSession {
+
+ {ses.IP}
+ |
+ {ses.Browser + ses.Version}
+ |
+ {ses.OS + ses.OSVersion}
+ |
+ {ses.AccessAt}
+ |
+
+
+ Terminate
+
+ |
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Click to log out or terminate the current session.
+
+
+
+
+
+
+
+
Storage Usage
+
+
+
+
+
+
+
Upgrade Storage
+
+
+
+
+
Pro Plan
+
50GB of storage for $9.99/month
+
+
+
+
Enterprise Plan
+
1TB of storage for $49.99/month
+
+
+
+
+
-
-
-
-
-
- Full name
-
- -
- { username }
-
-
-
-
-
- Email address
-
- -
- { email }
-
-
-
-
-
- Password
-
- -
- ntah lah
-
-
-
-
-
-
+
}
}
-templ Main(title string, email string, username string) {
- @content(email, username, title)
+templ Main(title string, email string, username string, ListSession []*session.SessionInfo) {
+ @content(email, username, title, ListSession)
}
\ No newline at end of file