2017-02-15 19:06:19 +01:00
package sessions
import (
"net/http"
"time"
2017-07-10 17:32:42 +02:00
"github.com/kataras/iris/context"
2017-02-15 19:06:19 +01:00
)
2017-07-10 17:32:42 +02:00
// A Sessions manager should be responsible to Start a sesion, based
// on a Context, which should return
// a compatible Session interface, type. If the external session manager
// doesn't qualifies, then the user should code the rest of the functions with empty implementation.
//
// Sessions should be responsible to Destroy a session based
// on the Context.
type Sessions struct {
config Config
provider * provider
}
2017-02-15 19:06:19 +01:00
// New returns a new fast, feature-rich sessions manager
2017-07-10 17:32:42 +02:00
// it can be adapted to an iris station
func New ( cfg Config ) * Sessions {
return & Sessions {
2017-02-15 19:06:19 +01:00
config : cfg . Validate ( ) ,
provider : newProvider ( ) ,
}
}
// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
2017-07-10 17:32:42 +02:00
func ( s * Sessions ) UseDatabase ( db Database ) {
2017-02-15 19:06:19 +01:00
s . provider . RegisterDatabase ( db )
}
2017-07-31 20:49:30 +02:00
// updateCookie gains the ability of updating the session browser cookie to any method which wants to update it
2017-08-07 05:04:35 +02:00
func ( s * Sessions ) updateCookie ( ctx context . Context , sid string , expires time . Duration ) {
2017-07-31 20:49:30 +02:00
cookie := & http . Cookie { }
2017-02-15 19:06:19 +01:00
2017-07-31 20:49:30 +02:00
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
cookie . Name = s . config . Cookie
2017-03-18 11:22:20 +01:00
2017-07-31 20:49:30 +02:00
cookie . Value = sid
cookie . Path = "/"
2018-04-22 13:00:08 +02:00
cookie . Domain = formatCookieDomain ( ctx , s . config . DisableSubdomainPersistence )
2017-07-31 20:49:30 +02:00
cookie . HttpOnly = true
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
if expires >= 0 {
if expires == 0 { // unlimited life
cookie . Expires = CookieExpireUnlimited
} else { // > 0
cookie . Expires = time . Now ( ) . Add ( expires )
2017-02-15 19:06:19 +01:00
}
2017-07-31 20:49:30 +02:00
cookie . MaxAge = int ( cookie . Expires . Sub ( time . Now ( ) ) . Seconds ( ) )
}
2017-02-15 19:06:19 +01:00
2017-07-31 20:49:30 +02:00
// set the cookie to secure if this is a tls wrapped request
// and the configuration allows it.
if ctx . Request ( ) . TLS != nil && s . config . CookieSecureTLS {
cookie . Secure = true
}
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
2017-07-31 20:49:30 +02:00
// encode the session id cookie client value right before send it.
cookie . Value = s . encodeCookieValue ( cookie . Value )
2017-12-15 14:46:18 +01:00
AddCookie ( ctx , cookie , s . config . AllowReclaim )
2017-07-31 20:49:30 +02:00
}
// Start should start the session for the particular request.
func ( s * Sessions ) Start ( ctx context . Context ) * Session {
2017-08-02 08:40:54 +02:00
cookieValue := s . decodeCookieValue ( GetCookie ( ctx , s . config . Cookie ) )
2017-07-31 20:49:30 +02:00
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
sid := s . config . SessionIDGenerator ( )
sess := s . provider . Init ( sid , s . config . Expires )
2018-04-22 12:52:36 +02:00
sess . isNew = s . provider . db . Len ( sid ) == 0
2017-08-01 10:00:30 +02:00
2017-08-07 05:04:35 +02:00
s . updateCookie ( ctx , sid , s . config . Expires )
2017-03-18 11:22:20 +01:00
2017-07-10 17:32:42 +02:00
return sess
2017-02-15 19:06:19 +01:00
}
2017-07-10 17:32:42 +02:00
sess := s . provider . Read ( cookieValue , s . config . Expires )
2017-08-01 10:00:30 +02:00
return sess
2017-02-15 19:06:19 +01:00
}
2017-08-14 15:21:51 +02:00
// ShiftExpiration move the expire date of a session to a new date
2017-08-07 05:04:35 +02:00
// by using session default timeout configuration.
2017-08-14 15:21:51 +02:00
func ( s * Sessions ) ShiftExpiration ( ctx context . Context ) {
s . UpdateExpiration ( ctx , s . config . Expires )
2017-07-31 20:49:30 +02:00
}
2017-08-14 15:21:51 +02:00
// UpdateExpiration change expire date of a session to a new date
2017-08-07 05:04:35 +02:00
// by using timeout value passed by `expires` receiver.
2017-08-14 15:21:51 +02:00
func ( s * Sessions ) UpdateExpiration ( ctx context . Context , expires time . Duration ) {
2017-08-02 08:40:54 +02:00
cookieValue := s . decodeCookieValue ( GetCookie ( ctx , s . config . Cookie ) )
2017-08-01 07:34:18 +02:00
if cookieValue != "" {
2017-12-15 14:46:18 +01:00
// we should also allow it to expire when the browser closed
if s . provider . UpdateExpiration ( cookieValue , expires ) || expires == - 1 {
2017-08-07 05:04:35 +02:00
s . updateCookie ( ctx , cookieValue , expires )
2017-08-01 07:34:18 +02:00
}
2017-07-31 20:49:30 +02:00
}
}
2018-04-22 13:13:40 +02:00
// DestroyListener is the form of a destroy listener.
// Look `OnDestroy` for more.
type DestroyListener func ( sid string )
// OnDestroy registers one or more destroy listeners.
// A destroy listener is fired when a session has been removed entirely from the server (the entry) and client-side (the cookie).
// Note that if a destroy listener is blocking, then the session manager will delay respectfully,
// use a goroutine inside the listener to avoid that behavior.
func ( s * Sessions ) OnDestroy ( listeners ... DestroyListener ) {
for _ , ln := range listeners {
s . provider . registerDestroyListener ( ln )
}
}
2017-03-18 22:43:04 +01:00
// Destroy remove the session data and remove the associated cookie.
2017-07-10 17:32:42 +02:00
func ( s * Sessions ) Destroy ( ctx context . Context ) {
cookieValue := GetCookie ( ctx , s . config . Cookie )
2017-03-18 22:43:04 +01:00
// decode the client's cookie value in order to find the server's session id
// to destroy the session data.
cookieValue = s . decodeCookieValue ( cookieValue )
2017-02-15 19:06:19 +01:00
if cookieValue == "" { // nothing to destroy
return
}
2018-04-04 09:25:00 +02:00
RemoveCookie ( ctx , s . config )
2017-03-18 11:22:20 +01:00
2017-02-15 19:06:19 +01:00
s . provider . Destroy ( cookieValue )
}
// DestroyByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
2017-03-18 22:43:04 +01:00
//
// Note: the sid should be the original one (i.e: fetched by a store )
// it's not decoded.
2017-07-10 17:32:42 +02:00
func ( s * Sessions ) DestroyByID ( sid string ) {
2017-02-15 19:06:19 +01:00
s . provider . Destroy ( sid )
}
// DestroyAll removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
2017-07-10 17:32:42 +02:00
func ( s * Sessions ) DestroyAll ( ) {
2017-02-15 19:06:19 +01:00
s . provider . DestroyAll ( )
}
2017-03-18 22:43:04 +01:00
// let's keep these funcs simple, we can do it with two lines but we may add more things in the future.
2017-07-10 17:32:42 +02:00
func ( s * Sessions ) decodeCookieValue ( cookieValue string ) string {
2017-08-02 08:40:54 +02:00
if cookieValue == "" {
return ""
}
2017-03-18 22:43:04 +01:00
var cookieValueDecoded * string
2017-08-02 08:40:54 +02:00
2017-03-18 22:43:04 +01:00
if decode := s . config . Decode ; decode != nil {
err := decode ( s . config . Cookie , cookieValue , & cookieValueDecoded )
if err == nil {
cookieValue = * cookieValueDecoded
} else {
cookieValue = ""
}
}
2017-08-02 08:40:54 +02:00
2017-03-18 22:43:04 +01:00
return cookieValue
}
2017-07-10 17:32:42 +02:00
func ( s * Sessions ) encodeCookieValue ( cookieValue string ) string {
2017-03-18 22:43:04 +01:00
if encode := s . config . Encode ; encode != nil {
newVal , err := encode ( s . config . Cookie , cookieValue )
if err == nil {
cookieValue = newVal
} else {
cookieValue = ""
}
}
return cookieValue
}