2021-05-14 16:24:02 +00:00
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"time"
"github.com/araddon/dateparse"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
)
const (
sessionCreatedOn = "created"
sessionModifiedOn = "modified"
sessionExpiresOn = "expires"
)
2021-06-06 12:39:42 +00:00
func ( a * goBlog ) initSessions ( ) {
2021-05-14 16:24:02 +00:00
deleteExpiredSessions := func ( ) {
2021-07-13 15:23:10 +00:00
if _ , err := a . db . exec (
"delete from sessions where expires < @now" ,
sql . Named ( "now" , utcNowString ( ) ) ,
) ; err != nil {
2021-05-14 16:24:02 +00:00
log . Println ( "Failed to delete expired sessions:" , err . Error ( ) )
}
}
deleteExpiredSessions ( )
2021-06-18 12:32:03 +00:00
a . hourlyHooks = append ( a . hourlyHooks , deleteExpiredSessions )
2021-06-06 12:39:42 +00:00
a . loginSessions = & dbSessionStore {
2021-07-24 11:35:26 +00:00
codecs : securecookie . CodecsFromPairs ( [ ] byte ( a . cfg . Server . JWTSecret ) ) ,
2021-05-14 16:24:02 +00:00
options : & sessions . Options {
2021-06-06 12:39:42 +00:00
Secure : a . httpsConfigured ( ) ,
2021-05-14 16:24:02 +00:00
HttpOnly : true ,
SameSite : http . SameSiteLaxMode ,
MaxAge : int ( ( 7 * 24 * time . Hour ) . Seconds ( ) ) ,
2021-07-14 09:54:20 +00:00
Path : "/" , // Cookie for all pages
2021-05-14 16:24:02 +00:00
} ,
2021-06-06 12:39:42 +00:00
db : a . db ,
2021-05-14 16:24:02 +00:00
}
2021-06-06 12:39:42 +00:00
a . captchaSessions = & dbSessionStore {
2021-07-24 11:35:26 +00:00
codecs : securecookie . CodecsFromPairs ( [ ] byte ( a . cfg . Server . JWTSecret ) ) ,
2021-05-14 16:24:02 +00:00
options : & sessions . Options {
2021-06-06 12:39:42 +00:00
Secure : a . httpsConfigured ( ) ,
2021-05-14 16:24:02 +00:00
HttpOnly : true ,
SameSite : http . SameSiteLaxMode ,
MaxAge : int ( ( 24 * time . Hour ) . Seconds ( ) ) ,
2021-07-14 09:54:20 +00:00
Path : "/" , // Cookie for all pages
2021-05-14 16:24:02 +00:00
} ,
2021-06-06 12:39:42 +00:00
db : a . db ,
2021-05-14 16:24:02 +00:00
}
}
type dbSessionStore struct {
options * sessions . Options
codecs [ ] securecookie . Codec
2021-06-06 12:39:42 +00:00
db * database
2021-05-14 16:24:02 +00:00
}
func ( s * dbSessionStore ) Get ( r * http . Request , name string ) ( * sessions . Session , error ) {
return sessions . GetRegistry ( r ) . Get ( s , name )
}
func ( s * dbSessionStore ) New ( r * http . Request , name string ) ( session * sessions . Session , err error ) {
session = sessions . NewSession ( s , name )
opts := * s . options
session . Options = & opts
session . IsNew = true
if cook , errCookie := r . Cookie ( name ) ; errCookie == nil {
if err = securecookie . DecodeMulti ( name , cook . Value , & session . ID , s . codecs ... ) ; err == nil {
session . IsNew = s . load ( session ) == nil
}
}
return session , err
}
2021-07-24 11:35:26 +00:00
func ( s * dbSessionStore ) Save ( r * http . Request , w http . ResponseWriter , ss * sessions . Session ) ( err error ) {
2021-05-14 16:24:02 +00:00
if ss . ID == "" {
if err = s . insert ( ss ) ; err != nil {
2021-07-24 11:35:26 +00:00
return err
2021-05-14 16:24:02 +00:00
}
} else if err = s . save ( ss ) ; err != nil {
2021-07-24 11:35:26 +00:00
return err
2021-05-14 16:24:02 +00:00
}
if encoded , err := securecookie . EncodeMulti ( ss . Name ( ) , ss . ID , s . codecs ... ) ; err != nil {
2021-07-24 11:35:26 +00:00
return err
2021-05-14 16:24:02 +00:00
} else {
2021-07-24 11:35:26 +00:00
http . SetCookie ( w , sessions . NewCookie ( ss . Name ( ) , encoded , ss . Options ) )
return nil
2021-05-14 16:24:02 +00:00
}
}
func ( s * dbSessionStore ) Delete ( r * http . Request , w http . ResponseWriter , session * sessions . Session ) error {
options := * session . Options
options . MaxAge = - 1
http . SetCookie ( w , sessions . NewCookie ( session . Name ( ) , "" , & options ) )
for k := range session . Values {
delete ( session . Values , k )
}
2021-06-06 12:39:42 +00:00
if _ , err := s . db . exec ( "delete from sessions where id = @id" , sql . Named ( "id" , session . ID ) ) ; err != nil {
2021-05-14 16:24:02 +00:00
return err
}
return nil
}
func ( s * dbSessionStore ) load ( session * sessions . Session ) ( err error ) {
2021-07-24 11:35:26 +00:00
row , err := s . db . queryRow ( "select data, created, modified, expires from sessions where id = @id and expires > @now" , sql . Named ( "id" , session . ID ) , sql . Named ( "now" , utcNowString ( ) ) )
2021-05-14 16:24:02 +00:00
if err != nil {
return err
}
var data , createdStr , modifiedStr , expiresStr string
2021-07-24 11:35:26 +00:00
if err = row . Scan ( & data , & createdStr , & modifiedStr , & expiresStr ) ; err != nil {
2021-05-14 16:24:02 +00:00
return err
}
if err = securecookie . DecodeMulti ( session . Name ( ) , data , & session . Values , s . codecs ... ) ; err != nil {
return err
}
2021-07-27 10:51:08 +00:00
session . Values [ sessionCreatedOn ] = timeNoErr ( dateparse . ParseLocal ( createdStr ) )
session . Values [ sessionModifiedOn ] = timeNoErr ( dateparse . ParseLocal ( modifiedStr ) )
session . Values [ sessionExpiresOn ] = timeNoErr ( dateparse . ParseLocal ( expiresStr ) )
2021-05-14 16:24:02 +00:00
return nil
}
func ( s * dbSessionStore ) insert ( session * sessions . Session ) ( err error ) {
2021-07-13 15:23:10 +00:00
created := time . Now ( ) . UTC ( )
modified := time . Now ( ) . UTC ( )
expires := time . Now ( ) . UTC ( ) . Add ( time . Second * time . Duration ( session . Options . MaxAge ) )
2021-05-14 16:24:02 +00:00
delete ( session . Values , sessionCreatedOn )
delete ( session . Values , sessionExpiresOn )
delete ( session . Values , sessionModifiedOn )
encoded , err := securecookie . EncodeMulti ( session . Name ( ) , session . Values , s . codecs ... )
if err != nil {
return err
}
2021-07-24 11:35:26 +00:00
res , err := s . db . exec ( "insert or replace into sessions(data, created, modified, expires) values(@data, @created, @modified, @expires)" ,
2021-07-13 15:23:10 +00:00
sql . Named ( "data" , encoded ) , sql . Named ( "created" , created . Format ( time . RFC3339 ) ) , sql . Named ( "modified" , modified . Format ( time . RFC3339 ) ) , sql . Named ( "expires" , expires . Format ( time . RFC3339 ) ) )
2021-05-14 16:24:02 +00:00
if err != nil {
return err
}
lastInserted , err := res . LastInsertId ( )
if err != nil {
return err
}
session . ID = fmt . Sprintf ( "%d" , lastInserted )
return nil
}
func ( s * dbSessionStore ) save ( session * sessions . Session ) ( err error ) {
if session . IsNew {
return s . insert ( session )
}
delete ( session . Values , sessionCreatedOn )
delete ( session . Values , sessionExpiresOn )
delete ( session . Values , sessionModifiedOn )
encoded , err := securecookie . EncodeMulti ( session . Name ( ) , session . Values , s . codecs ... )
if err != nil {
return err
}
2021-06-06 12:39:42 +00:00
_ , err = s . db . exec ( "update sessions set data = @data, modified = @modified where id = @id" ,
2021-07-13 15:23:10 +00:00
sql . Named ( "data" , encoded ) , sql . Named ( "modified" , utcNowString ( ) ) , sql . Named ( "id" , session . ID ) )
2021-05-14 16:24:02 +00:00
if err != nil {
return err
}
return nil
}