~mna/webparts-sessions

ref: 30542a33f7566f3aae1dfeb112c546f06e067526 webparts-sessions/sessions.go -rw-r--r-- 3.6 KiB
30542a33Martin Angers initial commit 1 year, 7 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package sessions

import (
	"bytes"
	"io"
	"net/http"
	"os"
	"time"

	"git.sr.ht/~mna/webparts/http/httpssn"
	"github.com/gorilla/sessions"
)

// Config configures the cookie session store.
type Config struct {
	// KeyPairs is a slice of authentication+encryption key pairs, newest first.
	// The encryption key is optional and may be set to nil.
	KeyPairs [][]byte

	// The following fields are the same as for http.Cookie.

	Path   string
	Domain string
	// MaxAge is the maximum age of the cookie. 0 means the cookie is deleted with
	// the browser session, < 0 means delete cookie immediately (should not be used
	// on the session store), > 0 is the max age, only full seconds are considered.
	MaxAge   time.Duration
	Secure   bool
	HTTPOnly bool
	SameSite http.SameSite
}

// New returns a configured cookie-based session store.
func New(conf *Config) httpssn.Store {
	store := sessions.NewCookieStore(conf.KeyPairs...)
	if conf.Path != "" {
		store.Options.Path = conf.Path
	}
	if conf.Domain != "" {
		store.Options.Domain = conf.Domain
	}
	store.Options.Secure = conf.Secure
	store.Options.HttpOnly = conf.HTTPOnly
	store.Options.SameSite = conf.SameSite
	store.Options.MaxAge = int(conf.MaxAge / time.Second)

	return &sessionStore{store}
}

type sessionStore struct {
	store *sessions.CookieStore
}

func (s *sessionStore) Get(r *http.Request, name string) (httpssn.Session, error) {
	ssn, err := s.store.Get(r, name)
	if err != nil {
		return nil, err
	}
	return &session{ssn}, nil
}

func (s *sessionStore) Save(w http.ResponseWriter, r *http.Request, ssn httpssn.Session) error {
	return s.store.Save(r, w, ssn.(*session).ssn)
}

type session struct {
	ssn *sessions.Session
}

func (s *session) IsNew() bool {
	return s.ssn.IsNew
}

func (s *session) Get(key string) interface{} {
	return s.ssn.Values[key]
}

func (s *session) Set(key string, val interface{}) {
	s.ssn.Values[key] = val
}

func (s *session) Unset(key string) {
	delete(s.ssn.Values, key)
}

func (s *session) Delete() {
	s.ssn.Options.MaxAge = -1
}

// KeyPairsFromFile reads the authentication and encryption key pairs from the
// specified file, and returns a [][]byte ready-to-use as the KeyPairs value
// of the Config struct. Both the authentication and encryption key values must
// be 32 bytes. The last pair in the file is the current pair (i.e. the key pairs
// order from the file is inverted in the returned slice so that the last pair
// in the file is the first pair in the slice).
//
// Encryption keys may be skipped (left nil) by setting the full 32 bytes they
// occupy to ' ' (a space).
func KeyPairsFromFile(file string) ([][]byte, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return KeyPairsFromReader(f)
}

// KeyPairsFromReader behaves the same as KeyPairsFromFile, but it reads from
// any io.Reader.
func KeyPairsFromReader(r io.Reader) ([][]byte, error) {
	const size = 32
	return keyPairs(r, size)
}

func keyPairs(r io.Reader, size int) ([][]byte, error) {
	var pairs [][]byte

	empty := bytes.Repeat([]byte{' '}, size)
	for {
		chunk := make([]byte, size)
		_, err := io.ReadFull(r, chunk)
		if err != nil {
			if err == io.EOF {
				if len(pairs)%2 != 0 {
					pairs = append(pairs, nil)
				}
				// switch back the last pair to first, and so on (reverse pair order)
				// so that the last one in the file is the current, active one.
				reversed := make([][]byte, len(pairs))
				for i := 0; i < len(pairs); i += 2 {
					reversed[i] = pairs[len(pairs)-(i+2)]
					reversed[i+1] = pairs[len(pairs)-(i+1)]
				}
				return reversed, nil
			}
			return nil, err
		}

		if bytes.Equal(chunk, empty) {
			chunk = nil
		}
		pairs = append(pairs, chunk)
	}
}