From 24bd36bae2ed724c6f134a006da5684459932b39 Mon Sep 17 00:00:00 2001 From: Evgeny <113383200+KlassnayaAfrodita@users.noreply.github.com> Date: Thu, 16 Jan 2025 05:52:28 +0300 Subject: [PATCH 1/4] Create auth_session.go --- auth/auth_session.go | 153 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 auth/auth_session.go diff --git a/auth/auth_session.go b/auth/auth_session.go new file mode 100644 index 000000000..993513c9d --- /dev/null +++ b/auth/auth_session.go @@ -0,0 +1,153 @@ +package auth + +import ( + "fmt" + "time" + + "github.com/spf13/cast" + "gorm.io/gorm/clause" + + contractsauth "github.com/goravel/framework/contracts/auth" + "github.com/goravel/framework/contracts/cache" + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/errors" + "github.com/goravel/framework/support/database" +) + +const sessionCtxKey = "GoravelSessionAuth" + +type Session struct { + SessionID string +} + +type Sessions map[string]*Session + +type SessionAuth struct { + cache cache.Cache + config config.Config + ctx http.Context + session string + orm orm.Orm +} + +func NewSessionAuth(session string, cache cache.Cache, config config.Config, ctx http.Context, orm orm.Orm) *SessionAuth { + return &SessionAuth{ + cache: cache, + config: config, + ctx: ctx, + session: session, + orm: orm, + } +} + +func (a *SessionAuth) Session(name string) contractsauth.Auth { + return NewAuth(name, a.cache, a.config, a.ctx, a.orm) +} + +func (a *SessionAuth) SessionUser(user any) error { + auth, ok := a.ctx.Value(sessionCtxKey).(Sessions) + if !ok || auth[a.session] == nil { + return errors.AuthParseTokenFirst + } + if auth[a.session].SessionID == "" { + return errors.AuthInvalidKey + } + + if err := a.orm.Query().FindOrFail(user, clause.Eq{Column: clause.PrimaryColumn, Value: auth[a.session].SessionID}); err != nil { + return err + } + + return nil +} + +func (a *SessionAuth) SessionID() (string, error) { + auth, ok := a.ctx.Value(sessionCtxKey).(Sessions) + if !ok || auth[a.session] == nil { + return "", errors.AuthParseTokenFirst + } + if auth[a.session].SessionID == "" { + return "", errors.AuthInvalidKey + } + + return auth[a.session].SessionID, nil +} + +func (a *SessionAuth) SessionLogin(user any) (string, error) { + id := database.GetID(user) + if id == nil { + return "", errors.AuthNoPrimaryKeyField + } + + sessionID := cast.ToString(id) + if sessionID == "" { + return "", errors.AuthInvalidKey + } + + if err := a.cache.Put(getSessionCacheKey(sessionID), true, time.Duration(a.getSessionTtl())*time.Minute); err != nil { + return "", err + } + + a.makeSessionAuthContext(sessionID) + + return sessionID, nil +} + +func (a *SessionAuth) SessionLogout() error { + auth, ok := a.ctx.Value(sessionCtxKey).(Sessions) + if !ok || auth[a.session] == nil || auth[a.session].SessionID == "" { + return nil + } + + if err := a.cache.Put(getSessionCacheKey(auth[a.session].SessionID), true, time.Duration(a.getSessionTtl())*time.Minute); err != nil { + return err + } + + delete(auth, a.session) + a.ctx.WithValue(sessionCtxKey, auth) + + return nil +} + +func (a *SessionAuth) SessionRefresh() (string, error) { + auth, ok := a.ctx.Value(sessionCtxKey).(Sessions) + if !ok || auth[a.session] == nil { + return "", errors.AuthParseTokenFirst + } + + if !a.cache.GetBool(getSessionCacheKey(auth[a.session].SessionID), false) { + return "", errors.AuthTokenExpired + } + + return auth[a.session].SessionID, nil +} + +func (a *SessionAuth) makeSessionAuthContext(sessionID string) { + sessions, ok := a.ctx.Value(sessionCtxKey).(Sessions) + if !ok { + sessions = make(Sessions) + } + sessions[a.session] = &Session{SessionID: sessionID} + a.ctx.WithValue(sessionCtxKey, sessions) +} + +func (a *SessionAuth) getSessionTtl() int { + var ttl int + SessionTtl := a.config.Get(fmt.Sprintf("auth.Sessions.%s.ttl", a.session)) + if SessionTtl == nil { + ttl = a.config.GetInt("session.ttl") + } else { + ttl = cast.ToInt(SessionTtl) + } + + if ttl == 0 { + ttl = 60 * 24 * 30 + } + + return ttl +} + +func getSessionCacheKey(sessionID string) string { + return "session:" + sessionID +} From 0a882780a602e18069066243aa89b773eb601dd1 Mon Sep 17 00:00:00 2001 From: Evgeny <113383200+KlassnayaAfrodita@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:41:42 +0300 Subject: [PATCH 2/4] Create driver.go --- contracts/auth/driver.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 contracts/auth/driver.go diff --git a/contracts/auth/driver.go b/contracts/auth/driver.go new file mode 100644 index 000000000..7e4c954aa --- /dev/null +++ b/contracts/auth/driver.go @@ -0,0 +1,7 @@ +package auth + +type Driver interface { + Login(userID string, data map[string]interface{}) (string, error) // User login + Logout(sessionID string) error // User logout + Authenticate(sessionID string) error // Check authentication +} From fc159d6a13dc05891583bc853cdda68fe2f11c30 Mon Sep 17 00:00:00 2001 From: Evgeny <113383200+KlassnayaAfrodita@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:42:25 +0300 Subject: [PATCH 3/4] Create jwt_driver.go --- auth/jwt_driver.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 auth/jwt_driver.go diff --git a/auth/jwt_driver.go b/auth/jwt_driver.go new file mode 100644 index 000000000..5ab4d659a --- /dev/null +++ b/auth/jwt_driver.go @@ -0,0 +1,63 @@ +package auth + +import ( + "errors" + "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/contracts/http" +) + +type JWTDriver struct { + auth *Auth +} + +func NewJWTDriver(config config.Config, cache Cache, ctx http.Context, orm orm.Orm) *JWTDriver { + return &JWTDriver{ + auth: NewAuth("jwt", cache, config, ctx, orm), + } +} + +func (d *JWTDriver) Login(userID string, data map[string]interface{}) (string, error) { + if userID == "" { + return "", errors.New("user ID is required") + } + + token, err := d.auth.LoginUsingID(userID) + if err != nil { + return "", err + } + + return token, nil +} + +func (d *JWTDriver) Logout(sessionID string) error { + if sessionID == "" { + return errors.New("session ID is required") + } + + d.auth.ctx.WithValue(ctxKey, Guards{ + "jwt": &Guard{ + Token: sessionID, + }, + }) + + err := d.auth.Logout() + if err != nil { + return err + } + + return nil +} + +func (d *JWTDriver) Authenticate(sessionID string) error { + if sessionID == "" { + return errors.New("session ID is required") + } + + _, err := d.auth.Parse(sessionID) + if err != nil { + return err + } + + return nil +} From 6cc87825f1d86ce8d2ded3ea6b539d47f2557947 Mon Sep 17 00:00:00 2001 From: Evgeny <113383200+KlassnayaAfrodita@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:46:23 +0300 Subject: [PATCH 4/4] Update jwt_driver.go --- auth/jwt_driver.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth/jwt_driver.go b/auth/jwt_driver.go index 5ab4d659a..434938233 100644 --- a/auth/jwt_driver.go +++ b/auth/jwt_driver.go @@ -2,6 +2,7 @@ package auth import ( "errors" + "github.com/goravel/framework/contracts/cache" "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/database/orm" "github.com/goravel/framework/contracts/http" @@ -11,7 +12,7 @@ type JWTDriver struct { auth *Auth } -func NewJWTDriver(config config.Config, cache Cache, ctx http.Context, orm orm.Orm) *JWTDriver { +func NewJWTDriver(config config.Config, cache cache.Cache, ctx http.Context, orm orm.Orm) *JWTDriver { return &JWTDriver{ auth: NewAuth("jwt", cache, config, ctx, orm), }