(WIP) Refresh & Access
This commit is contained in:
@@ -15,9 +15,9 @@ type API struct {
|
||||
|
||||
func NewApi(db *db.DBManager, auth *auth.AuthManager) *API {
|
||||
api := &API{
|
||||
Router: http.NewServeMux(),
|
||||
DB: db,
|
||||
Auth: auth,
|
||||
Router: http.NewServeMux(),
|
||||
Auth: auth,
|
||||
DB: db,
|
||||
}
|
||||
api.registerRoutes()
|
||||
return api
|
||||
|
||||
@@ -35,22 +35,25 @@ func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Is user already logged in? If so refresh token, if different user, kill session and log in new user?
|
||||
|
||||
// Do login
|
||||
resp := api.Auth.AuthenticateUser(creds)
|
||||
if resp == true {
|
||||
// Return Success
|
||||
cookie := http.Cookie{
|
||||
Name: "Token",
|
||||
Value: "testToken",
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
successJSON(w, "Login success.", http.StatusOK)
|
||||
}else {
|
||||
// Return Failure
|
||||
if !resp {
|
||||
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Create tokens
|
||||
accessToken := api.Auth.CreateJWTAccessToken()
|
||||
refreshToken := api.Auth.CreateRefreshToken()
|
||||
|
||||
// Set appropriate cookies
|
||||
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken}
|
||||
refreshCookie := http.Cookie{Name: "RefreshToken", Value: refreshToken}
|
||||
http.SetCookie(w, &accessCookie)
|
||||
http.SetCookie(w, &refreshCookie)
|
||||
|
||||
// Response success
|
||||
successJSON(w, "Login success.", http.StatusOK)
|
||||
}
|
||||
|
||||
func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -73,3 +76,20 @@ func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func (api *API) refreshLoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ok := api.Auth.ValidateRefreshToken()
|
||||
if !ok {
|
||||
// TODO: Clear Access & Refresh Cookies
|
||||
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Update token
|
||||
accessToken := api.Auth.CreateJWTAccessToken()
|
||||
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken}
|
||||
http.SetCookie(w, &accessCookie)
|
||||
|
||||
// Response success
|
||||
successJSON(w, "Refresh success.", http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Middleware func(http.Handler) http.Handler
|
||||
type Middleware func(http.Handler) http.HandlerFunc
|
||||
|
||||
func MultipleMiddleware(h http.Handler, m ...Middleware) http.Handler {
|
||||
func multipleMiddleware(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
|
||||
if len(m) < 1 {
|
||||
return h
|
||||
}
|
||||
@@ -19,19 +20,33 @@ func MultipleMiddleware(h http.Handler, m ...Middleware) http.Handler {
|
||||
return wrapped
|
||||
}
|
||||
|
||||
// func authMiddleware(h http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// _, ok := ValidateUserToken(r)
|
||||
//
|
||||
// if ok {
|
||||
// next.ServeHTTP(w, r)
|
||||
// } else {
|
||||
// w.WriteHeader(http.StatusUnauthorized)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("Token")
|
||||
if err != nil {
|
||||
log.Warn("[middleware] Cookie not found")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
func logMiddleware(h http.Handler) http.Handler {
|
||||
// Validate cookie.Value JWT with
|
||||
api.Auth.ValidateJWTToken(cookie.Value)
|
||||
|
||||
|
||||
log.Info("[middleware] Cookie Name: ", cookie.Name)
|
||||
log.Info("[middleware] Cookie Value: ", cookie.Value)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
// if true {
|
||||
// next.ServeHTTP(w, r)
|
||||
// } else {
|
||||
// w.WriteHeader(http.StatusUnauthorized)
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) logMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.Println(r.Method, r.URL)
|
||||
|
||||
@@ -6,15 +6,38 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) registerRoutes() {
|
||||
api.Router.HandleFunc("/MediaItems", api.mediaItemsHandler)
|
||||
api.Router.HandleFunc("/Upload", api.uploadHandler)
|
||||
api.Router.HandleFunc("/Albums", api.albumsHandler)
|
||||
api.Router.HandleFunc("/Logout", api.logoutHandler)
|
||||
api.Router.HandleFunc("/Login", api.loginHandler)
|
||||
api.Router.HandleFunc("/Users", api.usersHandler)
|
||||
api.Router.HandleFunc("/Tags", api.tagsHandler)
|
||||
api.Router.HandleFunc("/Info", api.infoHandler)
|
||||
api.Router.HandleFunc("/Me", api.meHandler)
|
||||
api.Router.HandleFunc("/MediaItems", multipleMiddleware(
|
||||
api.mediaItemsHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Upload", multipleMiddleware(
|
||||
api.uploadHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Albums", multipleMiddleware(
|
||||
api.albumsHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Users", multipleMiddleware(
|
||||
api.usersHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Tags", multipleMiddleware(
|
||||
api.tagsHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Info", multipleMiddleware(
|
||||
api.infoHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Me", multipleMiddleware(
|
||||
api.meHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
|
||||
api.Router.HandleFunc("/Logout", api.logoutHandler)
|
||||
api.Router.HandleFunc("/Login", api.loginHandler)
|
||||
api.Router.HandleFunc("/RefreshLogin", api.refreshLoginHandler)
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/59764037
|
||||
|
||||
@@ -31,9 +31,9 @@ func (api *API) meHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get Authenticated User & Return Object
|
||||
authCookie, err := r.Cookie("Token")
|
||||
if err != nil {
|
||||
log.Error("[routes] ", err)
|
||||
log.Error("[api] ", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("[routes] INFO: ", authCookie)
|
||||
log.Info("[api] Auth Cookie: ", authCookie)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
"reichard.io/imagini/internal/db"
|
||||
"reichard.io/imagini/internal/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"reichard.io/imagini/internal/db"
|
||||
"reichard.io/imagini/internal/config"
|
||||
"reichard.io/imagini/internal/models"
|
||||
"reichard.io/imagini/internal/session"
|
||||
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
)
|
||||
|
||||
type AuthManager struct {
|
||||
DB *db.DBManager
|
||||
DB *db.DBManager
|
||||
Config *config.Config
|
||||
Session *session.SessionManager
|
||||
}
|
||||
|
||||
func NewMgr(db *db.DBManager) *AuthManager {
|
||||
func NewMgr(db *db.DBManager, c *config.Config) *AuthManager {
|
||||
session := session.NewMgr()
|
||||
return &AuthManager{
|
||||
DB: db,
|
||||
Config: c,
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,3 +63,123 @@ func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *AuthManager) ValidateJWTToken(userJWT string) bool {
|
||||
byteUserJWT := []byte(userJWT)
|
||||
|
||||
serverToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, auth.Config.JWTSecret))
|
||||
if err != nil {
|
||||
fmt.Println("failed to parse payload: ", err)
|
||||
}
|
||||
|
||||
uid, ok := serverToken.Get("uid");
|
||||
if !ok {
|
||||
fmt.Println("failed to acquire uid")
|
||||
}
|
||||
|
||||
userID := fmt.Sprintf("%v", uid)
|
||||
userKey := auth.Session.Get(userID)
|
||||
|
||||
userToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, userKey))
|
||||
if err != nil {
|
||||
fmt.Println("failed to parse payload: ", err)
|
||||
}
|
||||
_ = userToken
|
||||
|
||||
// TODO:
|
||||
// - Get User ID from UNVALIDATED token
|
||||
// - Lookup user key, concat with server key
|
||||
// - Validate with concatted user & server key
|
||||
// validatedToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, concatKey))
|
||||
// if err != nil {
|
||||
// fmt.Printf("failed to parse payload: %s\n", err)
|
||||
// }
|
||||
|
||||
// userToken := auth.Session.Get(userID)
|
||||
// log.Info("[auth] DEBUG: ", userToken)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *AuthManager) RevokeRefreshToken() {
|
||||
|
||||
}
|
||||
|
||||
func (auth *AuthManager) ValidateRefreshToken(refreshToken, deviceID string) bool {
|
||||
// Acquire Device
|
||||
deviceUUID, err := uuid.Parse(deviceID)
|
||||
device := models.Device{Base: models.Base{UUID: deviceUUID}}
|
||||
foundDevice, err := auth.DB.Device(device)
|
||||
|
||||
// Validate Expiration
|
||||
expTime, err := time.Parse(time.RFC3339, foundDevice.RefreshExp)
|
||||
if expTime.Before(time.Now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate Token
|
||||
bRefreshToken :=[]byte(refreshToken)
|
||||
err = bcrypt.CompareHashAndPassword([]byte(foundDevice.RefreshToken), bRefreshToken)
|
||||
if err == nil {
|
||||
log.Info("[auth] Refresh Token validation succeeded: ", foundDevice.UUID)
|
||||
return true
|
||||
}
|
||||
log.Warn("[auth] Refresh Token validation failed: ", foundDevice.UUID)
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *AuthManager) UpdateRefreshToken(deviceID string) error {
|
||||
// TODO:
|
||||
// - Remove Refresh token from Session AND DB
|
||||
// - Call CreateRefreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *AuthManager) CreateRefreshToken(deviceID string) (string, error) {
|
||||
// TODO:
|
||||
// - Create regular bcrypt password
|
||||
// - Create Expiration (Depends on Device Type)
|
||||
// - Store in DB: DeviceID, ValidUntil
|
||||
|
||||
generatedToken := uuid.New().String()
|
||||
hashedRefreshToken, err := bcrypt.GenerateFromPassword([]byte(generatedToken), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
_ = string(hashedRefreshToken)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (auth *AuthManager) CreateJWTAccessToken(user, role, deviceID string) (string, error) {
|
||||
// Create New Token
|
||||
tm := time.Now()
|
||||
t := jwt.New()
|
||||
t.Set(`did`, deviceID) // Device ID
|
||||
t.Set(`role`, role) // User Role (Admin / User)
|
||||
t.Set(jwt.SubjectKey, user) // User ID
|
||||
t.Set(jwt.AudienceKey, `imagini`) // App ID
|
||||
t.Set(jwt.IssuedAtKey, tm) // Issued At
|
||||
t.Set(jwt.ExpirationKey, tm.Add(time.Minute * 30)) // 30 Minute Access Key
|
||||
|
||||
// Validate Token Creation
|
||||
_, err := json.MarshalIndent(t, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate JSON: %s\n", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Use Server Key
|
||||
byteKey := []byte(auth.Config.JWTSecret)
|
||||
|
||||
// Sign Token
|
||||
signed, err := jwt.Sign(t, jwa.HS256, byteKey)
|
||||
if err != nil {
|
||||
log.Printf("failed to sign token: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return Token
|
||||
return string(signed), nil
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ func NewMgr(c *config.Config) *DBManager {
|
||||
|
||||
// Initialize database
|
||||
dbm.db.AutoMigrate(&models.ServerSetting{})
|
||||
dbm.db.AutoMigrate(&models.Device{})
|
||||
dbm.db.AutoMigrate(&models.User{})
|
||||
dbm.db.AutoMigrate(&models.MediaItem{})
|
||||
dbm.db.AutoMigrate(&models.Tag{})
|
||||
@@ -83,71 +84,3 @@ func (dbm *DBManager) ItemsFromAlbum(user models.User, album models.Album) []mod
|
||||
// INNER JOIN MediaItems ON MediaAlbums.mediaID = MediaItems.mediaID
|
||||
// WHERE MediaAlbums.albumID = ? AND MediaItems.userID = ?`, albumID, userID)
|
||||
}
|
||||
|
||||
// func ItemsFromTags(userID int, tagID int) []MediaItem {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func IndexMediaItems(newItems []MediaItem) {
|
||||
// }
|
||||
|
||||
// func PopulateTestData() {
|
||||
// user1 := User{Username: "Evan", Email: "evan@reichard.io", FirstName: "Evan", LastName: "Reichard", AuthType: "LDAP", Salt: "1234", HashedPWSalt: "1234"}
|
||||
// user2 := User{Username: "Ryan", Email: "ryan@example.com", FirstName: "Ryan", LastName: "Dunfrey", AuthType: "Local", Salt: "2345", HashedPWSalt: "2345"}
|
||||
// user3 := User{Username: "Bill", Email: "bill@example.com", FirstName: "Bill", LastName: "Smith", AuthType: "LDAP", Salt: "3456", HashedPWSalt: "3456"}
|
||||
//
|
||||
// mi1 := MediaItem{
|
||||
// User: user1,
|
||||
// EXIFDate: time.Now(),
|
||||
// Latitude: "1234",
|
||||
// Longitude: "1234",
|
||||
// RelPath: "./1234.jpg",
|
||||
// Tags: []Tag{
|
||||
// {Name: "Tag1"},
|
||||
// {Name: "Tag2"},
|
||||
// },
|
||||
// Albums: []Album{
|
||||
// {Name: "Album1"},
|
||||
// {Name: "Album2"},
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// mi2 := MediaItem{
|
||||
// User: user2,
|
||||
// EXIFDate: time.Now(),
|
||||
// Latitude: "1234",
|
||||
// Longitude: "1234",
|
||||
// RelPath: "./1234.jpg",
|
||||
// Tags: []Tag{
|
||||
// {Name: "Tag3"},
|
||||
// {Name: "Tag4"},
|
||||
// },
|
||||
// Albums: []Album{
|
||||
// {Name: "Album3"},
|
||||
// {Name: "Album4"},
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// mi3 := MediaItem{
|
||||
// User: user3,
|
||||
// EXIFDate: time.Now(),
|
||||
// Latitude: "1234",
|
||||
// Longitude: "1234",
|
||||
// RelPath: "./1234.jpg",
|
||||
// Tags: []Tag{
|
||||
// {Name: "Tag4"},
|
||||
// {Name: "Tag5"},
|
||||
// },
|
||||
// Albums: []Album{
|
||||
// {Name: "Album1"},
|
||||
// {Name: "Album7"},
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // db.Create(&user1)
|
||||
// // db.Create(&user2)
|
||||
// // db.Create(&user3)
|
||||
// db.Create(&mi1)
|
||||
// db.Create(&mi2)
|
||||
// db.Create(&mi3)
|
||||
// }
|
||||
|
||||
44
internal/db/devices.go
Normal file
44
internal/db/devices.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"reichard.io/imagini/internal/models"
|
||||
)
|
||||
|
||||
func (dbm *DBManager) CreateDevice(device models.Device) error {
|
||||
log.Info("[query] Creating device: ", device.Name)
|
||||
_, err := dbm.Device(device)
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn("[query] Device already exists: ", device.Name)
|
||||
return errors.New("Device already exists")
|
||||
}
|
||||
|
||||
// Generate random password
|
||||
refreshToken := "asd123"
|
||||
hashedToken, err := bcrypt.GenerateFromPassword([]byte(refreshToken), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
device.RefreshToken = string(hashedToken)
|
||||
return dbm.db.Create(&device).Error
|
||||
}
|
||||
|
||||
func (dbm *DBManager) Device (device models.Device) (models.Device, error) {
|
||||
var foundDevice models.Device
|
||||
var count int64
|
||||
err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error
|
||||
return foundDevice, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) DeleteDevice (user models.Device) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbm *DBManager) UpdateRefreshToken (device models.Device, refreshToken string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,40 +1,53 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Might not even need this
|
||||
// Base contains common columns for all tables.
|
||||
type Base struct {
|
||||
UUID uuid.UUID `gorm:"type:uuid;default:default:uuid_generate_v4();primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
||||
func (base *Base) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
base.UUID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
type ServerSetting struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Description string `json:"description" gorm:"not null"`
|
||||
Value string `json:"value" gorm:"not null"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
gorm.Model
|
||||
User User `json:"user" gorm:"ForeignKey:ID"`
|
||||
DeviceName string `json:"name"`
|
||||
Type string `json:"type"` // Android, iOS, Chrome, FireFox, Edge, etc
|
||||
Base
|
||||
User User `json:"user" gorm:"ForeignKey:UUID"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // Android, iOS, Chrome, FireFox, Edge, etc
|
||||
RefreshExp string `json:"refresh_exp"`
|
||||
RefreshToken string `json:"-"`
|
||||
}
|
||||
|
||||
// TODO: ID -> UUID?
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Email string `json:"email" gorm:"unique"`
|
||||
Username string `json:"username" gorm:"unique"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
AuthType string `json:"auth_type" gorm:"default:Local;not null"`
|
||||
Password string `json:"-"`
|
||||
JWTSecret string `json:"-" gorm:"unique;not null"` // TODO: Auto Generate UUID
|
||||
}
|
||||
|
||||
type MediaItem struct {
|
||||
gorm.Model
|
||||
User User `json:"user" gorm:"ForeignKey:ID;not null"`
|
||||
Base
|
||||
User User `json:"user" gorm:"ForeignKey:UUID;not null"`
|
||||
EXIFDate time.Time `json:"exif_date"`
|
||||
Latitude string `json:"latitude"`
|
||||
Longitude string `json:"longitude"`
|
||||
@@ -45,11 +58,11 @@ type MediaItem struct {
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Used to maintain a cache of user specific jwt secrets
|
||||
// This will prevent DB lookups on every request
|
||||
type SessionManager struct {
|
||||
Mutex sync.Mutex
|
||||
mutex sync.Mutex
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func NewMgr() *SessionManager {
|
||||
@@ -13,13 +16,23 @@ func NewMgr() *SessionManager {
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Set(key, value string) {
|
||||
|
||||
sm.mutex.Lock()
|
||||
sm.values[key] = value
|
||||
sm.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Get(key string) string {
|
||||
return ""
|
||||
sm.mutex.Lock()
|
||||
defer sm.mutex.Unlock()
|
||||
return sm.values[key]
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Delete(key string) {
|
||||
|
||||
sm.mutex.Lock()
|
||||
defer sm.mutex.Unlock()
|
||||
_, exists := sm.values[key]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
delete(sm.values, key)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user