Simplify Gothic to use our session store instead of creating a different store (#17507)
* Simplify Gothic to use our session store instead of creating a different store We have been using xormstore to provide a separate session store for our OAuth2 logins however, this relies on using gorilla context and some doubling of our session storing. We can however, simplify and simply use our own chi-based session store. Thus removing a cookie and some of the weirdness with missing contexts. Signed-off-by: Andrew Thornton <art27@cantab.net> * as per review Signed-off-by: Andrew Thornton <art27@cantab.net> * as per review Signed-off-by: Andrew Thornton <art27@cantab.net> * Handle MaxTokenLength Signed-off-by: Andrew Thornton <art27@cantab.net> * oops Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: Lauris BH <lauris@nix.lv>tokarchuk/v1.17
parent
95da01c5cd
commit
9d855bd6a1
@ -1,18 +0,0 @@ |
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package db |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/lafriks/xormstore" |
|
||||||
) |
|
||||||
|
|
||||||
// CreateStore creates a xormstore for the provided table and key
|
|
||||||
func CreateStore(table, key string) (*xormstore.Store, error) { |
|
||||||
store, err := xormstore.NewOptions(x, xormstore.Options{ |
|
||||||
TableName: table, |
|
||||||
}, []byte(key)) |
|
||||||
|
|
||||||
return store, err |
|
||||||
} |
|
@ -0,0 +1,91 @@ |
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2 |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/gob" |
||||||
|
"fmt" |
||||||
|
"net/http" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
chiSession "gitea.com/go-chi/session" |
||||||
|
"github.com/gorilla/sessions" |
||||||
|
) |
||||||
|
|
||||||
|
// SessionsStore creates a gothic store from our session
|
||||||
|
type SessionsStore struct { |
||||||
|
maxLength int64 |
||||||
|
} |
||||||
|
|
||||||
|
// Get should return a cached session.
|
||||||
|
func (st *SessionsStore) Get(r *http.Request, name string) (*sessions.Session, error) { |
||||||
|
return st.getOrNew(r, name, false) |
||||||
|
} |
||||||
|
|
||||||
|
// New should create and return a new session.
|
||||||
|
//
|
||||||
|
// Note that New should never return a nil session, even in the case of
|
||||||
|
// an error if using the Registry infrastructure to cache the session.
|
||||||
|
func (st *SessionsStore) New(r *http.Request, name string) (*sessions.Session, error) { |
||||||
|
return st.getOrNew(r, name, true) |
||||||
|
} |
||||||
|
|
||||||
|
// getOrNew gets the session from the chi-session if it exists. Override permits the overriding of an unexpected object.
|
||||||
|
func (st *SessionsStore) getOrNew(r *http.Request, name string, override bool) (*sessions.Session, error) { |
||||||
|
chiStore := chiSession.GetSession(r) |
||||||
|
|
||||||
|
session := sessions.NewSession(st, name) |
||||||
|
|
||||||
|
rawData := chiStore.Get(name) |
||||||
|
if rawData != nil { |
||||||
|
oldSession, ok := rawData.(*sessions.Session) |
||||||
|
if ok { |
||||||
|
session.ID = oldSession.ID |
||||||
|
session.IsNew = oldSession.IsNew |
||||||
|
session.Options = oldSession.Options |
||||||
|
session.Values = oldSession.Values |
||||||
|
|
||||||
|
return session, nil |
||||||
|
} else if !override { |
||||||
|
log.Error("Unexpected object in session at name: %s: %v", name, rawData) |
||||||
|
return nil, fmt.Errorf("unexpected object in session at name: %s", name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
session.ID = chiStore.ID() // Simply copy the session id from the chi store
|
||||||
|
|
||||||
|
return session, chiStore.Set(name, session) |
||||||
|
} |
||||||
|
|
||||||
|
// Save should persist session to the underlying store implementation.
|
||||||
|
func (st *SessionsStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { |
||||||
|
chiStore := chiSession.GetSession(r) |
||||||
|
|
||||||
|
if err := chiStore.Set(session.Name(), session); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if st.maxLength > 0 { |
||||||
|
sizeWriter := &sizeWriter{} |
||||||
|
|
||||||
|
_ = gob.NewEncoder(sizeWriter).Encode(session) |
||||||
|
if sizeWriter.size > st.maxLength { |
||||||
|
return fmt.Errorf("encode session: Data too long: %d > %d", sizeWriter.size, st.maxLength) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return chiStore.Release() |
||||||
|
} |
||||||
|
|
||||||
|
type sizeWriter struct { |
||||||
|
size int64 |
||||||
|
} |
||||||
|
|
||||||
|
func (s *sizeWriter) Write(data []byte) (int, error) { |
||||||
|
s.size += int64(len(data)) |
||||||
|
return len(data), nil |
||||||
|
} |
||||||
|
|
||||||
|
var _ (sessions.Store) = &SessionsStore{} |
@ -1,19 +0,0 @@ |
|||||||
language: go |
|
||||||
sudo: false |
|
||||||
|
|
||||||
matrix: |
|
||||||
include: |
|
||||||
- go: 1.3 |
|
||||||
- go: 1.4 |
|
||||||
- go: 1.5 |
|
||||||
- go: 1.6 |
|
||||||
- go: 1.7 |
|
||||||
- go: tip |
|
||||||
allow_failures: |
|
||||||
- go: tip |
|
||||||
|
|
||||||
script: |
|
||||||
- go get -t -v ./... |
|
||||||
- diff -u <(echo -n) <(gofmt -d .) |
|
||||||
- go vet $(go list ./... | grep -v /vendor/) |
|
||||||
- go test -v -race ./... |
|
@ -1,27 +0,0 @@ |
|||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved. |
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without |
|
||||||
modification, are permitted provided that the following conditions are |
|
||||||
met: |
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright |
|
||||||
notice, this list of conditions and the following disclaimer. |
|
||||||
* Redistributions in binary form must reproduce the above |
|
||||||
copyright notice, this list of conditions and the following disclaimer |
|
||||||
in the documentation and/or other materials provided with the |
|
||||||
distribution. |
|
||||||
* Neither the name of Google Inc. nor the names of its |
|
||||||
contributors may be used to endorse or promote products derived from |
|
||||||
this software without specific prior written permission. |
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
@ -1,10 +0,0 @@ |
|||||||
context |
|
||||||
======= |
|
||||||
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) |
|
||||||
|
|
||||||
gorilla/context is a general purpose registry for global request variables. |
|
||||||
|
|
||||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well |
|
||||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`. |
|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context |
|
@ -1,143 +0,0 @@ |
|||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package context |
|
||||||
|
|
||||||
import ( |
|
||||||
"net/http" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
mutex sync.RWMutex |
|
||||||
data = make(map[*http.Request]map[interface{}]interface{}) |
|
||||||
datat = make(map[*http.Request]int64) |
|
||||||
) |
|
||||||
|
|
||||||
// Set stores a value for a given key in a given request.
|
|
||||||
func Set(r *http.Request, key, val interface{}) { |
|
||||||
mutex.Lock() |
|
||||||
if data[r] == nil { |
|
||||||
data[r] = make(map[interface{}]interface{}) |
|
||||||
datat[r] = time.Now().Unix() |
|
||||||
} |
|
||||||
data[r][key] = val |
|
||||||
mutex.Unlock() |
|
||||||
} |
|
||||||
|
|
||||||
// Get returns a value stored for a given key in a given request.
|
|
||||||
func Get(r *http.Request, key interface{}) interface{} { |
|
||||||
mutex.RLock() |
|
||||||
if ctx := data[r]; ctx != nil { |
|
||||||
value := ctx[key] |
|
||||||
mutex.RUnlock() |
|
||||||
return value |
|
||||||
} |
|
||||||
mutex.RUnlock() |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
|
||||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) { |
|
||||||
mutex.RLock() |
|
||||||
if _, ok := data[r]; ok { |
|
||||||
value, ok := data[r][key] |
|
||||||
mutex.RUnlock() |
|
||||||
return value, ok |
|
||||||
} |
|
||||||
mutex.RUnlock() |
|
||||||
return nil, false |
|
||||||
} |
|
||||||
|
|
||||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
|
||||||
func GetAll(r *http.Request) map[interface{}]interface{} { |
|
||||||
mutex.RLock() |
|
||||||
if context, ok := data[r]; ok { |
|
||||||
result := make(map[interface{}]interface{}, len(context)) |
|
||||||
for k, v := range context { |
|
||||||
result[k] = v |
|
||||||
} |
|
||||||
mutex.RUnlock() |
|
||||||
return result |
|
||||||
} |
|
||||||
mutex.RUnlock() |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
|
||||||
// the request was registered.
|
|
||||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { |
|
||||||
mutex.RLock() |
|
||||||
context, ok := data[r] |
|
||||||
result := make(map[interface{}]interface{}, len(context)) |
|
||||||
for k, v := range context { |
|
||||||
result[k] = v |
|
||||||
} |
|
||||||
mutex.RUnlock() |
|
||||||
return result, ok |
|
||||||
} |
|
||||||
|
|
||||||
// Delete removes a value stored for a given key in a given request.
|
|
||||||
func Delete(r *http.Request, key interface{}) { |
|
||||||
mutex.Lock() |
|
||||||
if data[r] != nil { |
|
||||||
delete(data[r], key) |
|
||||||
} |
|
||||||
mutex.Unlock() |
|
||||||
} |
|
||||||
|
|
||||||
// Clear removes all values stored for a given request.
|
|
||||||
//
|
|
||||||
// This is usually called by a handler wrapper to clean up request
|
|
||||||
// variables at the end of a request lifetime. See ClearHandler().
|
|
||||||
func Clear(r *http.Request) { |
|
||||||
mutex.Lock() |
|
||||||
clear(r) |
|
||||||
mutex.Unlock() |
|
||||||
} |
|
||||||
|
|
||||||
// clear is Clear without the lock.
|
|
||||||
func clear(r *http.Request) { |
|
||||||
delete(data, r) |
|
||||||
delete(datat, r) |
|
||||||
} |
|
||||||
|
|
||||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
|
||||||
// It returns the amount of requests removed.
|
|
||||||
//
|
|
||||||
// If maxAge <= 0, all request data is removed.
|
|
||||||
//
|
|
||||||
// This is only used for sanity check: in case context cleaning was not
|
|
||||||
// properly set some request data can be kept forever, consuming an increasing
|
|
||||||
// amount of memory. In case this is detected, Purge() must be called
|
|
||||||
// periodically until the problem is fixed.
|
|
||||||
func Purge(maxAge int) int { |
|
||||||
mutex.Lock() |
|
||||||
count := 0 |
|
||||||
if maxAge <= 0 { |
|
||||||
count = len(data) |
|
||||||
data = make(map[*http.Request]map[interface{}]interface{}) |
|
||||||
datat = make(map[*http.Request]int64) |
|
||||||
} else { |
|
||||||
min := time.Now().Unix() - int64(maxAge) |
|
||||||
for r := range data { |
|
||||||
if datat[r] < min { |
|
||||||
clear(r) |
|
||||||
count++ |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
mutex.Unlock() |
|
||||||
return count |
|
||||||
} |
|
||||||
|
|
||||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
|
||||||
// of a request lifetime.
|
|
||||||
func ClearHandler(h http.Handler) http.Handler { |
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|
||||||
defer Clear(r) |
|
||||||
h.ServeHTTP(w, r) |
|
||||||
}) |
|
||||||
} |
|
@ -1,88 +0,0 @@ |
|||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/* |
|
||||||
Package context stores values shared during a request lifetime. |
|
||||||
|
|
||||||
Note: gorilla/context, having been born well before `context.Context` existed, |
|
||||||
does not play well > with the shallow copying of the request that |
|
||||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
|
||||||
(added to net/http Go 1.7 onwards) performs. You should either use *just* |
|
||||||
gorilla/context, or moving forward, the new `http.Request.Context()`. |
|
||||||
|
|
||||||
For example, a router can set variables extracted from the URL and later |
|
||||||
application handlers can access those values, or it can be used to store |
|
||||||
sessions values to be saved at the end of a request. There are several |
|
||||||
others common uses. |
|
||||||
|
|
||||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: |
|
||||||
|
|
||||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
|
||||||
|
|
||||||
Here's the basic usage: first define the keys that you will need. The key |
|
||||||
type is interface{} so a key can be of any type that supports equality. |
|
||||||
Here we define a key using a custom int type to avoid name collisions: |
|
||||||
|
|
||||||
package foo |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/gorilla/context" |
|
||||||
) |
|
||||||
|
|
||||||
type key int |
|
||||||
|
|
||||||
const MyKey key = 0 |
|
||||||
|
|
||||||
Then set a variable. Variables are bound to an http.Request object, so you |
|
||||||
need a request instance to set a value: |
|
||||||
|
|
||||||
context.Set(r, MyKey, "bar") |
|
||||||
|
|
||||||
The application can later access the variable using the same key you provided: |
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) { |
|
||||||
// val is "bar".
|
|
||||||
val := context.Get(r, foo.MyKey) |
|
||||||
|
|
||||||
// returns ("bar", true)
|
|
||||||
val, ok := context.GetOk(r, foo.MyKey) |
|
||||||
// ...
|
|
||||||
} |
|
||||||
|
|
||||||
And that's all about the basic usage. We discuss some other ideas below. |
|
||||||
|
|
||||||
Any type can be stored in the context. To enforce a given type, make the key |
|
||||||
private and wrap Get() and Set() to accept and return values of a specific |
|
||||||
type: |
|
||||||
|
|
||||||
type key int |
|
||||||
|
|
||||||
const mykey key = 0 |
|
||||||
|
|
||||||
// GetMyKey returns a value for this package from the request values.
|
|
||||||
func GetMyKey(r *http.Request) SomeType { |
|
||||||
if rv := context.Get(r, mykey); rv != nil { |
|
||||||
return rv.(SomeType) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// SetMyKey sets a value for this package in the request values.
|
|
||||||
func SetMyKey(r *http.Request, val SomeType) { |
|
||||||
context.Set(r, mykey, val) |
|
||||||
} |
|
||||||
|
|
||||||
Variables must be cleared at the end of a request, to remove all values |
|
||||||
that were stored. This can be done in an http.Handler, after a request was |
|
||||||
served. Just call Clear() passing the request: |
|
||||||
|
|
||||||
context.Clear(r) |
|
||||||
|
|
||||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear |
|
||||||
variables at the end of a request lifetime. |
|
||||||
|
|
||||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear() |
|
||||||
so if you are using either of them you don't need to clear the context manually. |
|
||||||
*/ |
|
||||||
package context |
|
@ -1,3 +0,0 @@ |
|||||||
vendor/ |
|
||||||
debug.test |
|
||||||
coverage.txt |
|
@ -1,23 +0,0 @@ |
|||||||
sudo: required |
|
||||||
services: |
|
||||||
- docker |
|
||||||
|
|
||||||
branches: |
|
||||||
only: |
|
||||||
- master |
|
||||||
|
|
||||||
language: go |
|
||||||
go: |
|
||||||
- "tip" |
|
||||||
- "1.11" |
|
||||||
- "1.12" |
|
||||||
- "1.x" |
|
||||||
|
|
||||||
env: |
|
||||||
global: |
|
||||||
- GO111MODULE=on |
|
||||||
|
|
||||||
script: ./test |
|
||||||
|
|
||||||
after_success: |
|
||||||
- bash <(curl -s https://codecov.io/bash) |
|
@ -1,19 +0,0 @@ |
|||||||
Copyright (c) 2018 Lauris Bukšis-Haberkorns, Mattias Wadman |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
||||||
this software and associated documentation files (the "Software"), to deal in |
|
||||||
the Software without restriction, including without limitation the rights to |
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do |
|
||||||
so, subject to the following conditions: |
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all |
|
||||||
copies or substantial portions of the Software. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||||
SOFTWARE. |
|
@ -1,48 +0,0 @@ |
|||||||
[![GoDoc](https://godoc.org/github.com/lafriks/xormstore?status.svg)](https://godoc.org/github.com/lafriks/xormstore) |
|
||||||
[![Build Status](https://travis-ci.org/lafriks/xormstore.svg?branch=master)](https://travis-ci.org/lafriks/xormstore) |
|
||||||
[![codecov](https://codecov.io/gh/lafriks/xormstore/branch/master/graph/badge.svg)](https://codecov.io/gh/lafriks/xormstore) |
|
||||||
|
|
||||||
#### XORM backend for gorilla sessions |
|
||||||
|
|
||||||
go get github.com/lafriks/xormstore |
|
||||||
|
|
||||||
#### Example |
|
||||||
|
|
||||||
```go |
|
||||||
// initialize and setup cleanup |
|
||||||
store := xormstore.New(engine, []byte("secret")) |
|
||||||
// db cleanup every hour |
|
||||||
// close quit channel to stop cleanup |
|
||||||
quit := make(chan struct{}) |
|
||||||
go store.PeriodicCleanup(1*time.Hour, quit) |
|
||||||
``` |
|
||||||
|
|
||||||
```go |
|
||||||
// in HTTP handler |
|
||||||
func handlerFunc(w http.ResponseWriter, r *http.Request) { |
|
||||||
session, err := store.Get(r, "session") |
|
||||||
session.Values["user_id"] = 123 |
|
||||||
store.Save(r, w, session) |
|
||||||
http.Error(w, "", http.StatusOK) |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
For more details see [xormstore godoc documentation](https://godoc.org/github.com/lafriks/xormstore). |
|
||||||
|
|
||||||
#### Testing |
|
||||||
|
|
||||||
Just sqlite3 tests: |
|
||||||
|
|
||||||
go test |
|
||||||
|
|
||||||
All databases using docker: |
|
||||||
|
|
||||||
./test |
|
||||||
|
|
||||||
If docker is not local (docker-machine etc): |
|
||||||
|
|
||||||
DOCKER_IP=$(docker-machine ip dev) ./test |
|
||||||
|
|
||||||
#### License |
|
||||||
|
|
||||||
xormstore is licensed under the MIT license. See [LICENSE](LICENSE) for the full license text. |
|
@ -1,19 +0,0 @@ |
|||||||
module github.com/lafriks/xormstore |
|
||||||
|
|
||||||
go 1.13 |
|
||||||
|
|
||||||
require ( |
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc |
|
||||||
github.com/go-sql-driver/mysql v1.5.0 |
|
||||||
github.com/golang/protobuf v1.3.1 // indirect |
|
||||||
github.com/gorilla/context v1.1.1 |
|
||||||
github.com/gorilla/securecookie v1.1.1 |
|
||||||
github.com/gorilla/sessions v1.2.0 |
|
||||||
github.com/kr/pretty v0.2.1 // indirect |
|
||||||
github.com/lib/pq v1.7.0 |
|
||||||
github.com/mattn/go-sqlite3 v1.14.0 |
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad // indirect |
|
||||||
golang.org/x/text v0.3.2 // indirect |
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect |
|
||||||
xorm.io/xorm v1.0.6 |
|
||||||
) |
|
@ -1,85 +0,0 @@ |
|||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= |
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= |
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= |
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= |
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= |
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= |
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= |
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= |
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= |
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= |
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= |
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= |
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= |
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= |
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= |
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= |
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= |
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= |
|
||||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= |
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= |
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= |
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= |
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= |
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= |
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
|
||||||
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= |
|
||||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= |
|
||||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= |
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= |
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
|
||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= |
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
|
||||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= |
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= |
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= |
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= |
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= |
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= |
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= |
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ= |
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= |
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= |
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= |
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= |
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= |
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= |
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= |
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= |
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= |
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= |
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= |
|
||||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= |
|
||||||
xorm.io/xorm v1.0.6 h1:7eco1c8QUpGz+3dztpLDj9gU1bTiQdFC/KtmPaLxUJk= |
|
||||||
xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= |
|
@ -1,70 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
DOCKER_IP=${DOCKER_IP:-127.0.0.1} |
|
||||||
|
|
||||||
sqlite3() { |
|
||||||
DATABASE_URI="sqlite3://file:dummy?mode=memory&cache=shared" go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic |
|
||||||
return $? |
|
||||||
} |
|
||||||
|
|
||||||
postgres10() { |
|
||||||
ID=$(docker run -p 5432 -d postgres:10-alpine) |
|
||||||
PORT=$(docker port "$ID" 5432 | cut -d : -f 2) |
|
||||||
DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover |
|
||||||
S=$? |
|
||||||
docker rm -vf "$ID" > /dev/null |
|
||||||
return $S |
|
||||||
} |
|
||||||
|
|
||||||
postgres96() { |
|
||||||
ID=$(docker run -p 5432 -d postgres:9.6-alpine) |
|
||||||
PORT=$(docker port "$ID" 5432 | cut -d : -f 2) |
|
||||||
DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover |
|
||||||
S=$? |
|
||||||
docker rm -vf "$ID" > /dev/null |
|
||||||
return $S |
|
||||||
} |
|
||||||
|
|
||||||
postgres94() { |
|
||||||
ID=$(docker run -p 5432 -d postgres:9.4-alpine) |
|
||||||
PORT=$(docker port "$ID" 5432 | cut -d : -f 2) |
|
||||||
DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover |
|
||||||
S=$? |
|
||||||
docker rm -vf "$ID" > /dev/null |
|
||||||
return $S |
|
||||||
} |
|
||||||
|
|
||||||
mysql57() { |
|
||||||
ID=$(docker run \ |
|
||||||
-e MYSQL_ROOT_PASSWORD=root \ |
|
||||||
-e MYSQL_USER=mysql \ |
|
||||||
-e MYSQL_PASSWORD=mysql \ |
|
||||||
-e MYSQL_DATABASE=mysql \ |
|
||||||
-p 3306 -d mysql:5.7) |
|
||||||
PORT=$(docker port "$ID" 3306 | cut -d : -f 2) |
|
||||||
DATABASE_URI="mysql://mysql:mysql@tcp($DOCKER_IP:$PORT)/mysql?charset=utf8&parseTime=True" go test -v -race -cover |
|
||||||
S=$? |
|
||||||
docker rm -vf "$ID" > /dev/null |
|
||||||
return $S |
|
||||||
} |
|
||||||
|
|
||||||
mariadb10() { |
|
||||||
ID=$(docker run \ |
|
||||||
-e MYSQL_ROOT_PASSWORD=root \ |
|
||||||
-e MYSQL_USER=mysql \ |
|
||||||
-e MYSQL_PASSWORD=mysql \ |
|
||||||
-e MYSQL_DATABASE=mysql \ |
|
||||||
-p 3306 -d mariadb:10) |
|
||||||
PORT=$(docker port "$ID" 3306 | cut -d : -f 2) |
|
||||||
DATABASE_URI="mysql://mysql:mysql@tcp($DOCKER_IP:$PORT)/mysql?charset=utf8&parseTime=True" go test -v -race -cover |
|
||||||
S=$? |
|
||||||
docker rm -vf "$ID" > /dev/null |
|
||||||
return $S |
|
||||||
} |
|
||||||
|
|
||||||
sqlite3 || exit 1 |
|
||||||
postgres94 || exit 1 |
|
||||||
postgres96 || exit 1 |
|
||||||
postgres10 || exit 1 |
|
||||||
mysql57 || exit 1 |
|
||||||
mariadb10 || exit 1 |
|
@ -1,60 +0,0 @@ |
|||||||
package util |
|
||||||
|
|
||||||
import ( |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// TimeStamp defines a timestamp
|
|
||||||
type TimeStamp int64 |
|
||||||
|
|
||||||
// TimeStampNow returns now int64
|
|
||||||
func TimeStampNow() TimeStamp { |
|
||||||
return TimeStamp(time.Now().Unix()) |
|
||||||
} |
|
||||||
|
|
||||||
// Add adds seconds and return sum
|
|
||||||
func (ts TimeStamp) Add(seconds int64) TimeStamp { |
|
||||||
return ts + TimeStamp(seconds) |
|
||||||
} |
|
||||||
|
|
||||||
// AddDuration adds time.Duration and return sum
|
|
||||||
func (ts TimeStamp) AddDuration(interval time.Duration) TimeStamp { |
|
||||||
return ts + TimeStamp(interval/time.Second) |
|
||||||
} |
|
||||||
|
|
||||||
// Year returns the time's year
|
|
||||||
func (ts TimeStamp) Year() int { |
|
||||||
return ts.AsTime().Year() |
|
||||||
} |
|
||||||
|
|
||||||
// AsTime convert timestamp as time.Time in Local locale
|
|
||||||
func (ts TimeStamp) AsTime() (tm time.Time) { |
|
||||||
tm = time.Unix(int64(ts), 0).Local() |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// AsTimePtr convert timestamp as *time.Time in Local locale
|
|
||||||
func (ts TimeStamp) AsTimePtr() *time.Time { |
|
||||||
tm := time.Unix(int64(ts), 0).Local() |
|
||||||
return &tm |
|
||||||
} |
|
||||||
|
|
||||||
// Format formats timestamp as
|
|
||||||
func (ts TimeStamp) Format(f string) string { |
|
||||||
return ts.AsTime().Format(f) |
|
||||||
} |
|
||||||
|
|
||||||
// FormatLong formats as RFC1123Z
|
|
||||||
func (ts TimeStamp) FormatLong() string { |
|
||||||
return ts.Format(time.RFC1123Z) |
|
||||||
} |
|
||||||
|
|
||||||
// FormatShort formats as short
|
|
||||||
func (ts TimeStamp) FormatShort() string { |
|
||||||
return ts.Format("Jan 02, 2006") |
|
||||||
} |
|
||||||
|
|
||||||
// IsZero is zero time
|
|
||||||
func (ts TimeStamp) IsZero() bool { |
|
||||||
return ts.AsTime().IsZero() |
|
||||||
} |
|
@ -1,251 +0,0 @@ |
|||||||
/* |
|
||||||
Package xormstore is a XORM backend for gorilla sessions |
|
||||||
|
|
||||||
Simplest form: |
|
||||||
|
|
||||||
store, err := xormstore.New(engine, []byte("secret-hash-key")) |
|
||||||
|
|
||||||
All options: |
|
||||||
|
|
||||||
store, err := xormstore.NewOptions( |
|
||||||
engine, // *xorm.Engine
|
|
||||||
xormstore.Options{ |
|
||||||
TableName: "sessions", // "sessions" is default
|
|
||||||
SkipCreateTable: false, // false is default
|
|
||||||
}, |
|
||||||
[]byte("secret-hash-key"), // 32 or 64 bytes recommended, required
|
|
||||||
[]byte("secret-encyption-key")) // nil, 16, 24 or 32 bytes, optional
|
|
||||||
|
|
||||||
if err != nil { |
|
||||||
// xormstore can not be initialized
|
|
||||||
} |
|
||||||
|
|
||||||
// some more settings, see sessions.Options
|
|
||||||
store.SessionOpts.Secure = true |
|
||||||
store.SessionOpts.HttpOnly = true |
|
||||||
store.SessionOpts.MaxAge = 60 * 60 * 24 * 60 |
|
||||||
|
|
||||||
If you want periodic cleanup of expired sessions: |
|
||||||
|
|
||||||
quit := make(chan struct{}) |
|
||||||
go store.PeriodicCleanup(1*time.Hour, quit) |
|
||||||
|
|
||||||
For more information about the keys see https://github.com/gorilla/securecookie
|
|
||||||
|
|
||||||
For API to use in HTTP handlers see https://github.com/gorilla/sessions
|
|
||||||
*/ |
|
||||||
package xormstore |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/base32" |
|
||||||
"net/http" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/lafriks/xormstore/util" |
|
||||||
|
|
||||||
"xorm.io/xorm" |
|
||||||
"github.com/gorilla/context" |
|
||||||
"github.com/gorilla/securecookie" |
|
||||||
"github.com/gorilla/sessions" |
|
||||||
) |
|
||||||
|
|
||||||
const sessionIDLen = 32 |
|
||||||
const defaultTableName = "sessions" |
|
||||||
const defaultMaxAge = 60 * 60 * 24 * 30 // 30 days
|
|
||||||
const defaultPath = "/" |
|
||||||
|
|
||||||
// Options for xormstore
|
|
||||||
type Options struct { |
|
||||||
TableName string |
|
||||||
SkipCreateTable bool |
|
||||||
} |
|
||||||
|
|
||||||
// Store represent a xormstore
|
|
||||||
type Store struct { |
|
||||||
e *xorm.Engine |
|
||||||
opts Options |
|
||||||
Codecs []securecookie.Codec |
|
||||||
SessionOpts *sessions.Options |
|
||||||
} |
|
||||||
|
|
||||||
type xormSession struct { |
|
||||||
ID string `xorm:"VARCHAR(100) PK NAME 'id'"` |
|
||||||
Data string `xorm:"TEXT"` |
|
||||||
CreatedUnix util.TimeStamp `xorm:"created"` |
|
||||||
UpdatedUnix util.TimeStamp `xorm:"updated"` |
|
||||||
ExpiresUnix util.TimeStamp `xorm:"INDEX"` |
|
||||||
|
|
||||||
tableName string `xorm:"-"` // just to store table name for easier access
|
|
||||||
} |
|
||||||
|
|
||||||
// Define a type for context keys so that they can't clash with anything else stored in context
|
|
||||||
type contextKey string |
|
||||||
|
|
||||||
func (xs *xormSession) TableName() string { |
|
||||||
return xs.tableName |
|
||||||
} |
|
||||||
|
|
||||||
// New creates a new xormstore session
|
|
||||||
func New(e *xorm.Engine, keyPairs ...[]byte) (*Store, error) { |
|
||||||
return NewOptions(e, Options{}, keyPairs...) |
|
||||||
} |
|
||||||
|
|
||||||
// NewOptions creates a new xormstore session with options
|
|
||||||
func NewOptions(e *xorm.Engine, opts Options, keyPairs ...[]byte) (*Store, error) { |
|
||||||
st := &Store{ |
|
||||||
e: e, |
|
||||||
opts: opts, |
|
||||||
Codecs: securecookie.CodecsFromPairs(keyPairs...), |
|
||||||
SessionOpts: &sessions.Options{ |
|
||||||
Path: defaultPath, |
|
||||||
MaxAge: defaultMaxAge, |
|
||||||
}, |
|
||||||
} |
|
||||||
if st.opts.TableName == "" { |
|
||||||
st.opts.TableName = defaultTableName |
|
||||||
} |
|
||||||
|
|
||||||
if !st.opts.SkipCreateTable { |
|
||||||
if err := st.e.Sync2(&xormSession{tableName: st.opts.TableName}); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return st, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Get returns a session for the given name after adding it to the registry.
|
|
||||||
func (st *Store) Get(r *http.Request, name string) (*sessions.Session, error) { |
|
||||||
return sessions.GetRegistry(r).Get(st, name) |
|
||||||
} |
|
||||||
|
|
||||||
// New creates a session with name without adding it to the registry.
|
|
||||||
func (st *Store) New(r *http.Request, name string) (*sessions.Session, error) { |
|
||||||
session := sessions.NewSession(st, name) |
|
||||||
opts := *st.SessionOpts |
|
||||||
session.Options = &opts |
|
||||||
|
|
||||||
st.MaxAge(st.SessionOpts.MaxAge) |
|
||||||
|
|
||||||
// try fetch from db if there is a cookie
|
|
||||||
if cookie, err := r.Cookie(name); err == nil { |
|
||||||
if err := securecookie.DecodeMulti(name, cookie.Value, &session.ID, st.Codecs...); err != nil { |
|
||||||
return session, nil |
|
||||||
} |
|
||||||
s := &xormSession{tableName: st.opts.TableName} |
|
||||||
if has, err := st.e.Where("id = ? AND expires_unix >= ?", session.ID, util.TimeStampNow()).Get(s); !has || err != nil { |
|
||||||
return session, nil |
|
||||||
} |
|
||||||
if err := securecookie.DecodeMulti(session.Name(), s.Data, &session.Values, st.Codecs...); err != nil { |
|
||||||
return session, nil |
|
||||||
} |
|
||||||
|
|
||||||
context.Set(r, contextKey(name), s) |
|
||||||
} |
|
||||||
|
|
||||||
return session, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Save session and set cookie header
|
|
||||||
func (st *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { |
|
||||||
s, _ := context.Get(r, contextKey(session.Name())).(*xormSession) |
|
||||||
|
|
||||||
// delete if max age is < 0
|
|
||||||
if session.Options.MaxAge < 0 { |
|
||||||
if s != nil { |
|
||||||
if _, err := st.e.Delete(&xormSession{ |
|
||||||
ID: session.ID, |
|
||||||
tableName: st.opts.TableName, |
|
||||||
}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options)) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
data, err := securecookie.EncodeMulti(session.Name(), session.Values, st.Codecs...) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
now := util.TimeStampNow() |
|
||||||
expire := now.AddDuration(time.Second * time.Duration(session.Options.MaxAge)) |
|
||||||
|
|
||||||
if s == nil { |
|
||||||
// generate random session ID key suitable for storage in the db
|
|
||||||
session.ID = strings.TrimRight( |
|
||||||
base32.StdEncoding.EncodeToString( |
|
||||||
securecookie.GenerateRandomKey(sessionIDLen)), "=") |
|
||||||
s = &xormSession{ |
|
||||||
ID: session.ID, |
|
||||||
Data: data, |
|
||||||
CreatedUnix: now, |
|
||||||
UpdatedUnix: now, |
|
||||||
ExpiresUnix: expire, |
|
||||||
tableName: st.opts.TableName, |
|
||||||
} |
|
||||||
if _, err := st.e.Insert(s); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
context.Set(r, contextKey(session.Name()), s) |
|
||||||
} else { |
|
||||||
s.Data = data |
|
||||||
s.UpdatedUnix = now |
|
||||||
s.ExpiresUnix = expire |
|
||||||
if _, err := st.e.ID(s.ID).Cols("data", "updated_unix", "expires_unix").Update(s); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// set session id cookie
|
|
||||||
id, err := securecookie.EncodeMulti(session.Name(), session.ID, st.Codecs...) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
http.SetCookie(w, sessions.NewCookie(session.Name(), id, session.Options)) |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
|
||||||
// implementation. Individual sessions can be deleted by setting
|
|
||||||
// Options.MaxAge = -1 for that session.
|
|
||||||
func (st *Store) MaxAge(age int) { |
|
||||||
st.SessionOpts.MaxAge = age |
|
||||||
for _, codec := range st.Codecs { |
|
||||||
if sc, ok := codec.(*securecookie.SecureCookie); ok { |
|
||||||
sc.MaxAge(age) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// MaxLength restricts the maximum length of new sessions to l.
|
|
||||||
// If l is 0 there is no limit to the size of a session, use with caution.
|
|
||||||
// The default is 4096 (default for securecookie)
|
|
||||||
func (st *Store) MaxLength(l int) { |
|
||||||
for _, c := range st.Codecs { |
|
||||||
if codec, ok := c.(*securecookie.SecureCookie); ok { |
|
||||||
codec.MaxLength(l) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Cleanup deletes expired sessions
|
|
||||||
func (st *Store) Cleanup() { |
|
||||||
st.e.Where("expires_unix < ?", util.TimeStampNow()).Delete(&xormSession{tableName: st.opts.TableName}) |
|
||||||
} |
|
||||||
|
|
||||||
// PeriodicCleanup runs Cleanup every interval. Close quit channel to stop.
|
|
||||||
func (st *Store) PeriodicCleanup(interval time.Duration, quit <-chan struct{}) { |
|
||||||
t := time.NewTicker(interval) |
|
||||||
defer t.Stop() |
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-t.C: |
|
||||||
st.Cleanup() |
|
||||||
case <-quit: |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue