├── .busted
├── .github
└── workflows
│ ├── integration_tests.yaml
│ └── unit_tests.yaml
├── .gitignore
├── .luacheckrc
├── .luacov
├── Changes.md
├── LICENSE
├── Makefile
├── README.md
├── config.ld
├── dev
└── Dockerfile
├── dist.ini
├── docs
├── classes
│ ├── resty.session.html
│ └── session.html
├── index.html
├── ldoc.css
└── modules
│ ├── resty.session.dshm.html
│ ├── resty.session.file-thread.html
│ ├── resty.session.file.html
│ ├── resty.session.file.thread.html
│ ├── resty.session.file.utils.html
│ ├── resty.session.html
│ ├── resty.session.memcached.html
│ ├── resty.session.mysql.html
│ ├── resty.session.postgres.html
│ ├── resty.session.redis-cluster.html
│ ├── resty.session.redis-sentinel.html
│ ├── resty.session.redis.cluster.html
│ ├── resty.session.redis.common.html
│ ├── resty.session.redis.html
│ ├── resty.session.redis.sentinel.html
│ ├── resty.session.shm.html
│ └── resty.session.utils.html
├── lib
└── resty
│ ├── session.lua
│ └── session
│ ├── dshm.lua
│ ├── file.lua
│ ├── file
│ ├── thread.lua
│ └── utils.lua
│ ├── memcached.lua
│ ├── mysql.lua
│ ├── postgres.lua
│ ├── redis.lua
│ ├── redis
│ ├── cluster.lua
│ ├── common.lua
│ └── sentinel.lua
│ ├── shm.lua
│ └── utils.lua
├── lua-resty-session-4.1.1-1.rockspec
├── spec
├── 01-utils_spec.lua
├── 02-file-utils_spec.lua
├── 03-session_spec.lua
├── 04-storage-1_spec.lua
└── 05-storage-2_spec.lua
└── t
└── 01-cookies.t
/.busted:
--------------------------------------------------------------------------------
1 | return {
2 | default = {
3 | lua = "resty --shdict 'sessions 1m' --main-conf 'thread_pool default threads=32 max_queue=65536;'",
4 | verbose = true,
5 | coverage = true,
6 | output = "gtest",
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.github/workflows/integration_tests.yaml:
--------------------------------------------------------------------------------
1 | name: integration_tests
2 | on: [pull_request]
3 |
4 | jobs:
5 | test:
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | luaVersion: ["luajit-openresty"]
10 |
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - name: Setup environment
16 | run: docker build dev/ -t resty-session
17 |
18 | - name: Run tests
19 | run: docker run -v $PWD:/test -w /test resty-session bash -c "luarocks make && make prove"
20 |
--------------------------------------------------------------------------------
/.github/workflows/unit_tests.yaml:
--------------------------------------------------------------------------------
1 | name: unit_tests
2 | on: [pull_request]
3 |
4 | jobs:
5 | test:
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | luaVersion: ["luajit-openresty"]
10 |
11 | services:
12 | redis:
13 | image: bitnami/redis
14 | env:
15 | REDIS_PASSWORD: password
16 | ports:
17 | - 6379:6379
18 | options: >-
19 | --health-cmd "redis-cli ping"
20 | --health-interval 10s
21 | --health-timeout 5s
22 | --health-retries 5
23 | memcached:
24 | image: memcached
25 | ports:
26 | - 11211:11211
27 |
28 | runs-on: ubuntu-latest
29 | steps:
30 | - uses: actions/checkout@v4
31 |
32 | - name: Setup environment
33 | run: docker build dev/ -t resty-session
34 |
35 | - name: Run tests
36 | run: docker run --network=host -v $PWD:/test -w /test resty-session bash -c "luarocks make && make unit"
37 |
38 | - name: Generate report
39 | run: docker run --network=host -v $PWD:/test -w /test resty-session bash -c "luarocks make && luacov"
40 |
41 | - name: Print report summary
42 | run: docker run --network=host -v $PWD:/test -w /test resty-session sed -n '/Summary/,$p' luacov.report.out
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.project
2 |
--------------------------------------------------------------------------------
/.luacheckrc:
--------------------------------------------------------------------------------
1 | std = "ngx_lua"
2 | redefined = false
3 | max_line_length = false
4 | files["lib/resty/session/file.lua"] = { ignore = {"143"}}
--------------------------------------------------------------------------------
/.luacov:
--------------------------------------------------------------------------------
1 | return {
2 |
3 | -- filename to store stats collected
4 | ["statsfile"] = "luacov.stats.out",
5 |
6 | -- filename to store report
7 | ["reportfile"] = "luacov.report.out",
8 |
9 | -- Run reporter on completion? (won't work for ticks)
10 | runreport = true,
11 |
12 | -- Patterns for files to include when reporting
13 | -- all will be included if nothing is listed
14 | -- (exclude overrules include, do not include
15 | -- the .lua extension, path separator is always '/')
16 | ["include"] = { 'resty/session' },
17 |
18 | -- Patterns for files to exclude when reporting
19 | -- all will be included if nothing is listed
20 | -- (exclude overrules include, do not include
21 | -- the .lua extension, path separator is always '/')
22 | ["exclude"] = {
23 | "luacov$",
24 | "luacov/reporter$",
25 | "luacov/defaults$",
26 | "luacov/runner$",
27 | "luacov/stats$",
28 | "luacov/tick$",
29 | },
30 |
31 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 – 2025 Aapo Talvensaari, 2022 – 2025 Samuele Illuminati
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: lint test docs
2 |
3 | lint:
4 | @luacheck -q ./lib
5 |
6 | unit:
7 | busted --exclude-tags=noci --coverage
8 |
9 | unit-all:
10 | busted --coverage
11 |
12 | prove:
13 | prove
14 |
15 | docs:
16 | ldoc .
17 |
--------------------------------------------------------------------------------
/config.ld:
--------------------------------------------------------------------------------
1 | project = "resty.session"
2 | description = "Session Library for OpenResty"
3 | full_description = "`lua-resty-session` is a secure, and flexible session library for OpenResty"
4 | title = "Session Library for OpenResty Documentation"
5 | dir = "docs"
6 | use_markdown_titles = true
7 | package = "session"
8 | format = "discount"
9 | sort_modules=true
10 | file = "./lib/resty"
11 |
--------------------------------------------------------------------------------
/dev/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openresty/openresty:1.27.1.2-0-noble
2 |
3 | ENV DEBIAN_FRONTEND noninteractive
4 | ENV TEST_NGINX_BINARY openresty
5 |
6 | USER root
7 | RUN apt-get update && apt-get install -y gcc git cpanminus
8 |
9 | RUN git clone https://github.com/Olivine-Labs/busted
10 | RUN cd busted && luarocks make
11 |
12 | RUN luarocks install pgmoon
13 | RUN luarocks install lua-resty-rsa
14 | RUN luarocks install lua-resty-redis-connector
15 | RUN luarocks install lua-resty-redis-cluster
16 | RUN luarocks install inspect
17 | RUN luarocks install lua_pack
18 | RUN luarocks install LuaCov
19 |
20 | RUN cpanm --notest Test::Nginx
21 |
--------------------------------------------------------------------------------
/dist.ini:
--------------------------------------------------------------------------------
1 | name = lua-resty-session
2 | abstract = Session Library for OpenResty - Flexible and Secure
3 | author = Aapo Talvensaari (@bungle), Samuele Illuminati (@samugi)
4 | is_original = yes
5 | license = 2bsd
6 | repo_link = https://github.com/bungle/lua-resty-session
7 | requires = luajit, nginx, ngx_http_lua, fffonion/lua-resty-openssl >= 1.5.0, hamishforbes/lua-ffi-zlib >= 0.5
8 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
30 |
31 |
32 |
33 |
Modules
34 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
Session Library for OpenResty
57 |
lua-resty-session
is a secure, and flexible session library for OpenResty
58 |
59 |
Modules
60 |
119 |
120 |
121 |
122 |
123 |
generated by LDoc 1.5.0
124 |
Last updated 2025-04-16 06:31:49
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/docs/ldoc.css:
--------------------------------------------------------------------------------
1 | /* BEGIN RESET
2 |
3 | Copyright (c) 2010, Yahoo! Inc. All rights reserved.
4 | Code licensed under the BSD License:
5 | http://developer.yahoo.com/yui/license.html
6 | version: 2.8.2r1
7 | */
8 | html {
9 | color: #000;
10 | background: #FFF;
11 | }
12 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td {
13 | margin: 0;
14 | padding: 0;
15 | }
16 | table {
17 | border-collapse: collapse;
18 | border-spacing: 0;
19 | }
20 | fieldset,img {
21 | border: 0;
22 | }
23 | address,caption,cite,code,dfn,em,strong,th,var,optgroup {
24 | font-style: inherit;
25 | font-weight: inherit;
26 | }
27 | del,ins {
28 | text-decoration: none;
29 | }
30 | li {
31 | margin-left: 20px;
32 | }
33 | caption,th {
34 | text-align: left;
35 | }
36 | h1,h2,h3,h4,h5,h6 {
37 | font-size: 100%;
38 | font-weight: bold;
39 | }
40 | q:before,q:after {
41 | content: '';
42 | }
43 | abbr,acronym {
44 | border: 0;
45 | font-variant: normal;
46 | }
47 | sup {
48 | vertical-align: baseline;
49 | }
50 | sub {
51 | vertical-align: baseline;
52 | }
53 | legend {
54 | color: #000;
55 | }
56 | input,button,textarea,select,optgroup,option {
57 | font-family: inherit;
58 | font-size: inherit;
59 | font-style: inherit;
60 | font-weight: inherit;
61 | }
62 | input,button,textarea,select {*font-size:100%;
63 | }
64 | /* END RESET */
65 |
66 | body {
67 | margin-left: 1em;
68 | margin-right: 1em;
69 | font-family: arial, helvetica, geneva, sans-serif;
70 | background-color: #ffffff; margin: 0px;
71 | }
72 |
73 | code, tt { font-family: monospace; font-size: 1.1em; }
74 | span.parameter { font-family:monospace; }
75 | span.parameter:after { content:":"; }
76 | span.types:before { content:"("; }
77 | span.types:after { content:")"; }
78 | .type { font-weight: bold; font-style:italic }
79 |
80 | body, p, td, th { font-size: .95em; line-height: 1.2em;}
81 |
82 | p, ul { margin: 10px 0 0 0px;}
83 |
84 | strong { font-weight: bold;}
85 |
86 | em { font-style: italic;}
87 |
88 | h1 {
89 | font-size: 1.5em;
90 | margin: 20px 0 20px 0;
91 | }
92 | h2, h3, h4 { margin: 15px 0 10px 0; }
93 | h2 { font-size: 1.25em; }
94 | h3 { font-size: 1.15em; }
95 | h4 { font-size: 1.06em; }
96 |
97 | a:link { font-weight: bold; color: #004080; text-decoration: none; }
98 | a:visited { font-weight: bold; color: #006699; text-decoration: none; }
99 | a:link:hover { text-decoration: underline; }
100 |
101 | hr {
102 | color:#cccccc;
103 | background: #00007f;
104 | height: 1px;
105 | }
106 |
107 | blockquote { margin-left: 3em; }
108 |
109 | ul { list-style-type: disc; }
110 |
111 | p.name {
112 | font-family: "Andale Mono", monospace;
113 | padding-top: 1em;
114 | }
115 |
116 | pre {
117 | background-color: rgb(245, 245, 245);
118 | border: 1px solid #C0C0C0; /* silver */
119 | padding: 10px;
120 | margin: 10px 0 10px 0;
121 | overflow: auto;
122 | font-family: "Andale Mono", monospace;
123 | }
124 |
125 | pre.example {
126 | font-size: .85em;
127 | }
128 |
129 | table.index { border: 1px #00007f; }
130 | table.index td { text-align: left; vertical-align: top; }
131 |
132 | #container {
133 | margin-left: 1em;
134 | margin-right: 1em;
135 | background-color: #f0f0f0;
136 | }
137 |
138 | #product {
139 | text-align: center;
140 | border-bottom: 1px solid #cccccc;
141 | background-color: #ffffff;
142 | }
143 |
144 | #product big {
145 | font-size: 2em;
146 | }
147 |
148 | #main {
149 | background-color: #f0f0f0;
150 | border-left: 2px solid #cccccc;
151 | }
152 |
153 | #navigation {
154 | float: left;
155 | width: 14em;
156 | vertical-align: top;
157 | background-color: #f0f0f0;
158 | overflow: visible;
159 | }
160 |
161 | #navigation h2 {
162 | background-color:#e7e7e7;
163 | font-size:1.1em;
164 | color:#000000;
165 | text-align: left;
166 | padding:0.2em;
167 | border-top:1px solid #dddddd;
168 | border-bottom:1px solid #dddddd;
169 | }
170 |
171 | #navigation ul
172 | {
173 | font-size:1em;
174 | list-style-type: none;
175 | margin: 1px 1px 10px 1px;
176 | }
177 |
178 | #navigation li {
179 | text-indent: -1em;
180 | display: block;
181 | margin: 3px 0px 0px 22px;
182 | }
183 |
184 | #navigation li li a {
185 | margin: 0px 3px 0px -1em;
186 | }
187 |
188 | #content {
189 | margin-left: 14em;
190 | padding: 1em;
191 | width: 700px;
192 | border-left: 2px solid #cccccc;
193 | border-right: 2px solid #cccccc;
194 | background-color: #ffffff;
195 | }
196 |
197 | #about {
198 | clear: both;
199 | padding: 5px;
200 | border-top: 2px solid #cccccc;
201 | background-color: #ffffff;
202 | }
203 |
204 | @media print {
205 | body {
206 | font: 12pt "Times New Roman", "TimeNR", Times, serif;
207 | }
208 | a { font-weight: bold; color: #004080; text-decoration: underline; }
209 |
210 | #main {
211 | background-color: #ffffff;
212 | border-left: 0px;
213 | }
214 |
215 | #container {
216 | margin-left: 2%;
217 | margin-right: 2%;
218 | background-color: #ffffff;
219 | }
220 |
221 | #content {
222 | padding: 1em;
223 | background-color: #ffffff;
224 | }
225 |
226 | #navigation {
227 | display: none;
228 | }
229 | pre.example {
230 | font-family: "Andale Mono", monospace;
231 | font-size: 10pt;
232 | page-break-inside: avoid;
233 | }
234 | }
235 |
236 | table.module_list {
237 | border-width: 1px;
238 | border-style: solid;
239 | border-color: #cccccc;
240 | border-collapse: collapse;
241 | }
242 | table.module_list td {
243 | border-width: 1px;
244 | padding: 3px;
245 | border-style: solid;
246 | border-color: #cccccc;
247 | }
248 | table.module_list td.name { background-color: #f0f0f0; min-width: 200px; }
249 | table.module_list td.summary { width: 100%; }
250 |
251 |
252 | table.function_list {
253 | border-width: 1px;
254 | border-style: solid;
255 | border-color: #cccccc;
256 | border-collapse: collapse;
257 | }
258 | table.function_list td {
259 | border-width: 1px;
260 | padding: 3px;
261 | border-style: solid;
262 | border-color: #cccccc;
263 | }
264 | table.function_list td.name { background-color: #f0f0f0; min-width: 200px; }
265 | table.function_list td.summary { width: 100%; }
266 |
267 | ul.nowrap {
268 | overflow:auto;
269 | white-space:nowrap;
270 | }
271 |
272 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
273 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
274 | dl.table h3, dl.function h3 {font-size: .95em;}
275 |
276 | /* stop sublists from having initial vertical space */
277 | ul ul { margin-top: 0px; }
278 | ol ul { margin-top: 0px; }
279 | ol ol { margin-top: 0px; }
280 | ul ol { margin-top: 0px; }
281 |
282 | /* make the target distinct; helps when we're navigating to a function */
283 | a:target + * {
284 | background-color: #FF9;
285 | }
286 |
287 |
288 | /* styles for prettification of source */
289 | pre .comment { color: #558817; }
290 | pre .constant { color: #a8660d; }
291 | pre .escape { color: #844631; }
292 | pre .keyword { color: #aa5050; font-weight: bold; }
293 | pre .library { color: #0e7c6b; }
294 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
295 | pre .string { color: #8080ff; }
296 | pre .number { color: #f8660d; }
297 | pre .function-name { color: #60447f; }
298 | pre .operator { color: #2239a8; font-weight: bold; }
299 | pre .preprocessor, pre .prepro { color: #a33243; }
300 | pre .global { color: #800080; }
301 | pre .user-keyword { color: #800080; }
302 | pre .prompt { color: #558817; }
303 | pre .url { color: #272fc2; text-decoration: underline; }
304 |
305 |
--------------------------------------------------------------------------------
/docs/modules/resty.session.file-thread.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
54 |
55 |
56 |
57 |
58 |
59 |
Module resty.session.file-thread
60 |
File storage backend worker thread module
61 |
62 |
63 |
64 |
65 |
66 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | -
89 |
90 | set (path, content)
91 |
92 | -
93 | Store data in file.
94 |
95 |
96 |
Parameters:
97 |
98 | - path
99 | string
100 |
file path
101 |
102 |
103 |
104 | - content
105 | string
106 | file content
107 |
108 |
109 |
110 | Returns:
111 |
112 | -
113 | true or nil
114 | ok
115 | -
116 | string
117 | error message
118 |
119 |
120 |
121 |
122 |
123 |
124 | -
125 |
126 | get (path)
127 |
128 | -
129 | Read data from a file.
130 |
131 |
132 |
Parameters:
133 |
134 | - path
135 | string
136 | file to read
137 |
138 |
139 |
140 | Returns:
141 |
142 | -
143 | string or nil
144 | content
145 | -
146 | string
147 | error message
148 |
149 |
150 |
151 |
152 |
153 |
154 | -
155 |
156 | delete (path)
157 |
158 | -
159 | Delete a file.
160 |
161 |
162 |
Parameters:
163 |
164 | - path
165 | string
166 | file to read
167 |
168 |
169 |
170 | Returns:
171 |
172 | -
173 | string or nil
174 | ok
175 | -
176 | string
177 | error message
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
generated by LDoc 1.4.6
191 |
Last updated 2022-12-23 14:06:58
192 |
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/docs/modules/resty.session.file.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
30 |
33 |
34 |
Contents
35 |
40 |
41 |
42 |
Modules
43 |
59 |
60 |
61 |
62 |
63 |
64 |
Module resty.session.file
65 |
File storage backend for session library.
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | configuration |
75 | File storage backend configuration |
76 |
77 |
78 |
79 |
85 |
86 |
87 |
88 | instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember) |
89 | Store session data. |
90 |
91 |
92 | instance:get (name, key) |
93 | Retrieve session data. |
94 |
95 |
96 | instance:delete (name, key[, metadata]) |
97 | Delete session data. |
98 |
99 |
100 | instance:read_metadata (name, audience, subject, current_time) |
101 | Read session metadata. |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | -
113 |
114 | configuration
115 |
116 | -
117 | File storage backend configuration
118 |
119 |
120 |
Fields:
121 |
122 | - prefix
123 | File prefix for session file.
124 |
125 | - suffix
126 | File suffix (or extension without
.
) for session file.
127 |
128 | - pool
129 | Name of the thread pool under which file writing happens (available on Linux only).
130 |
131 | - path
132 | Path (or directory) under which session files are created.
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | -
146 |
147 | module.new ([configuration])
148 |
149 | -
150 | Create a file storage.
151 |
152 |
This creates a new file storage instance.
153 |
154 |
155 |
Parameters:
156 |
157 | - configuration
158 | table
159 | file storage configuration
160 | (optional)
161 |
162 |
163 |
164 | Returns:
165 |
166 |
167 | table
168 | file storage instance
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | -
180 |
181 | instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)
182 |
183 | -
184 | Store session data.
185 |
186 |
187 |
Parameters:
188 |
189 | - name
190 | string
191 | cookie name
192 |
193 | - key
194 | string
195 | session key
196 |
197 | - value
198 | string
199 | session value
200 |
201 | - ttl
202 | number
203 | session ttl
204 |
205 | - current_time
206 | number
207 | current time
208 |
209 | - old_key
210 | string
211 | old session id
212 | (optional)
213 |
214 | - stale_ttl
215 | string
216 | stale ttl
217 |
218 | - metadata
219 | table
220 | table of metadata
221 | (optional)
222 |
223 | - remember
224 | boolean
225 | whether storing persistent session or not
226 |
227 |
228 |
229 | Returns:
230 |
231 | -
232 | true or nil
233 | ok
234 | -
235 | string
236 | error message
237 |
238 |
239 |
240 |
241 |
242 |
243 | -
244 |
245 | instance:get (name, key)
246 |
247 | -
248 | Retrieve session data.
249 |
250 |
251 |
Parameters:
252 |
253 | - name
254 | string
255 | cookie name
256 |
257 | - key
258 | string
259 | session key
260 |
261 |
262 |
263 | Returns:
264 |
265 | -
266 | string or nil
267 | session data
268 | -
269 | string
270 | error message
271 |
272 |
273 |
274 |
275 |
276 |
277 | -
278 |
279 | instance:delete (name, key[, metadata])
280 |
281 | -
282 | Delete session data.
283 |
284 |
285 |
Parameters:
286 |
287 | - name
288 | string
289 | cookie name
290 |
291 | - key
292 | string
293 | session key
294 |
295 | - metadata
296 | table
297 | session meta data
298 | (optional)
299 |
300 |
301 |
302 | Returns:
303 |
304 | -
305 | boolean or nil
306 | session data
307 | -
308 | string
309 | error message
310 |
311 |
312 |
313 |
314 |
315 |
316 | -
317 |
318 | instance:read_metadata (name, audience, subject, current_time)
319 |
320 | -
321 | Read session metadata.
322 |
323 |
324 |
Parameters:
325 |
326 | - name
327 | string
328 | cookie name
329 |
330 | - audience
331 | string
332 | session key
333 |
334 | - subject
335 | string
336 | session key
337 |
338 | - current_time
339 | number
340 | current time
341 |
342 |
343 |
344 | Returns:
345 |
346 | -
347 | table or nil
348 | session metadata
349 | -
350 | string
351 | error message
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
generated by LDoc 1.5.0
365 |
Last updated 2025-04-16 06:31:49
366 |
367 |
368 |
369 |
370 |
--------------------------------------------------------------------------------
/docs/modules/resty.session.file.thread.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
30 |
33 |
34 |
Contents
35 |
38 |
39 |
40 |
Modules
41 |
57 |
58 |
59 |
60 |
61 |
62 |
Module resty.session.file.thread
63 |
File storage backend worker thread module
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember) |
73 | Store session data. |
74 |
75 |
76 | module.GET (path, prefix, suffix, name, key) |
77 | Retrieve session data. |
78 |
79 |
80 | module.delete (path, prefix, suffix, name, key, current_time) |
81 | Delete session data. |
82 |
83 |
84 | module.read_metadata (path, prefix, suffix, name, audience, subject, current_time) |
85 | Read session metadata. |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | -
97 |
98 | module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)
99 |
100 | -
101 | Store session data.
102 |
103 |
104 |
Parameters:
105 |
106 | - path
107 | string
108 | the path where sessions are stored
109 |
110 | - prefix
111 | string
112 | the prefix for session files
113 |
114 | - suffix
115 | string
116 | the suffix for session files
117 |
118 | - name
119 | string
120 | the cookie name
121 |
122 | - key
123 | string
124 | session key
125 |
126 | - value
127 | string
128 | session value
129 |
130 | - ttl
131 | number
132 | session ttl
133 |
134 | - current_time
135 | number
136 | current time
137 |
138 | - old_key
139 | string
140 | old session id
141 | (optional)
142 |
143 | - stale_ttl
144 | string
145 | stale ttl
146 |
147 | - metadata
148 | table
149 | table of metadata
150 | (optional)
151 |
152 | - remember
153 | table
154 | whether storing persistent session or not
155 |
156 |
157 |
158 | Returns:
159 |
160 | -
161 | table or nil
162 | session metadata
163 | -
164 | string
165 | error message
166 |
167 |
168 |
169 |
170 |
171 |
172 | -
173 |
174 | module.GET (path, prefix, suffix, name, key)
175 |
176 | -
177 | Retrieve session data.
178 |
179 |
180 |
Parameters:
181 |
182 | - path
183 | string
184 | the path where sessions are stored
185 |
186 | - prefix
187 | string
188 | the prefix for session files
189 |
190 | - suffix
191 | string
192 | the suffix for session files
193 |
194 | - name
195 | string
196 | cookie name
197 |
198 | - key
199 | string
200 | session key
201 |
202 |
203 |
204 | Returns:
205 |
206 | -
207 | string or nil
208 | session data
209 | -
210 | string
211 | error message
212 |
213 |
214 |
215 |
216 |
217 |
218 | -
219 |
220 | module.delete (path, prefix, suffix, name, key, current_time)
221 |
222 | -
223 | Delete session data.
224 |
225 |
226 |
Parameters:
227 |
228 | - path
229 | string
230 | the path where sessions are stored
231 |
232 | - prefix
233 | string
234 | the prefix for session files
235 |
236 | - suffix
237 | string
238 | the suffix for session files
239 |
240 | - name
241 | string
242 | the cookie name
243 |
244 | - key
245 | string
246 | session key
247 |
248 | - current_time
249 | number
250 | current time
251 |
252 |
253 |
254 | Returns:
255 |
256 | -
257 | table or nil
258 | session metadata
259 | -
260 | string
261 | error message
262 |
263 |
264 |
265 |
266 |
267 |
268 | -
269 |
270 | module.read_metadata (path, prefix, suffix, name, audience, subject, current_time)
271 |
272 | -
273 | Read session metadata.
274 |
275 |
276 |
Parameters:
277 |
278 | - path
279 | string
280 | the path where sessions are stored
281 |
282 | - prefix
283 | string
284 | the prefix for session files
285 |
286 | - suffix
287 | string
288 | the suffix for session files
289 |
290 | - name
291 | string
292 | the cookie name
293 |
294 | - audience
295 | string
296 | session audience
297 |
298 | - subject
299 | string
300 | session subject
301 |
302 | - current_time
303 | number
304 | current time
305 |
306 |
307 |
308 | Returns:
309 |
310 | -
311 | table or nil
312 | session metadata
313 | -
314 | string
315 | error message
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
generated by LDoc 1.5.0
329 |
Last updated 2025-04-16 06:31:49
330 |
331 |
332 |
333 |
334 |
--------------------------------------------------------------------------------
/docs/modules/resty.session.file.utils.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
30 |
33 |
34 |
Contents
35 |
38 |
39 |
40 |
Modules
41 |
57 |
58 |
59 |
60 |
61 |
62 |
Module resty.session.file.utils
63 |
File storage utilities
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | file_create (path, content) |
73 | Store data in file. |
74 |
75 |
76 | file_append (path, data) |
77 | Append data in file. |
78 |
79 |
80 | file_read (path) |
81 | Read data from a file. |
82 |
83 |
84 | get_modification (path) |
85 | Get the value modification time of a file. |
86 |
87 |
88 | meta_get_key (audience, subject) |
89 | Given an audience and a subject, generate a metadata key. |
90 |
91 |
92 | validate_file_name (prefix, suffix, name, filename) |
93 | Validate a file name. |
94 |
95 |
96 | cleanup (path, prefix, suffix, name, current_time) |
97 | Clean up expired session and metadata files. |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | -
109 |
110 | file_create (path, content)
111 |
112 | -
113 | Store data in file.
114 |
115 |
116 |
Parameters:
117 |
118 | - path
119 | string
120 | file path
121 |
122 | - content
123 | string
124 | file content
125 |
126 |
127 |
128 | Returns:
129 |
130 | -
131 | true or nil
132 | ok
133 | -
134 | string
135 | error message
136 |
137 |
138 |
139 |
140 |
141 |
142 | -
143 |
144 | file_append (path, data)
145 |
146 | -
147 | Append data in file.
148 |
149 |
150 |
Parameters:
151 |
152 | - path
153 | string
154 | file path
155 |
156 | - data
157 | string
158 | file data
159 |
160 |
161 |
162 | Returns:
163 |
164 | -
165 | true or nil
166 | ok
167 | -
168 | string
169 | error message
170 |
171 |
172 |
173 |
174 |
175 |
176 | -
177 |
178 | file_read (path)
179 |
180 | -
181 | Read data from a file.
182 |
183 |
184 |
Parameters:
185 |
186 | - path
187 | string
188 | file to read
189 |
190 |
191 |
192 | Returns:
193 |
194 | -
195 | string or nil
196 | content
197 | -
198 | string
199 | error message
200 |
201 |
202 |
203 |
204 |
205 |
206 | -
207 |
208 | get_modification (path)
209 |
210 | -
211 | Get the value modification time of a file.
212 |
213 |
214 |
Parameters:
215 |
216 | - path
217 | string
218 | the path to the file
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | -
228 |
229 | meta_get_key (audience, subject)
230 |
231 | -
232 | Given an audience and a subject, generate a metadata key.
233 |
234 |
235 |
Parameters:
236 |
237 | - audience
238 | string
239 | session audience
240 |
241 | - subject
242 | string
243 | session subject
244 |
245 |
246 |
247 | Returns:
248 |
249 |
250 | string
251 | metadata key
252 |
253 |
254 |
255 |
256 |
257 |
258 | -
259 |
260 | validate_file_name (prefix, suffix, name, filename)
261 |
262 | -
263 | Validate a file name.
264 | Run a few checks to try to determine if the file is managed by this library
265 |
266 |
267 |
Parameters:
268 |
269 | - prefix
270 | string
271 | the prefix for session files
272 |
273 | - suffix
274 | string
275 | the suffix for session files
276 |
277 | - name
278 | string
279 | cookie name
280 |
281 | - filename
282 | string
283 | the name of the file
284 |
285 |
286 |
287 | Returns:
288 |
289 |
290 | true or false
291 | whether the file is managed by the library or not
292 |
293 |
294 |
295 |
296 |
297 |
298 | -
299 |
300 | cleanup (path, prefix, suffix, name, current_time)
301 |
302 | -
303 | Clean up expired session and metadata files.
304 |
305 |
306 |
Parameters:
307 |
308 | - path
309 | string
310 | the path where sessions are stored
311 |
312 | - prefix
313 | string
314 | the prefix for session files
315 |
316 | - suffix
317 | string
318 | the suffix for session files
319 |
320 | - name
321 | string
322 | cookie name
323 |
324 | - current_time
325 | number
326 | current time
327 |
328 |
329 |
330 | Returns:
331 |
332 |
333 | true or false
334 | whether clean up completed
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
generated by LDoc 1.5.0
348 |
Last updated 2025-04-16 06:31:49
349 |
350 |
351 |
352 |
353 |
--------------------------------------------------------------------------------
/docs/modules/resty.session.redis.common.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
30 |
33 |
34 |
Contents
35 |
38 |
39 |
40 |
Modules
41 |
57 |
58 |
59 |
60 |
61 |
62 |
Module resty.session.redis.common
63 |
Common Redis functions shared between Redis,
64 | Redis Cluster and Redis Sentinel implementations.
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember) |
74 | Store session data. |
75 |
76 |
77 | module.GET (storage, red, name, key) |
78 | Retrieve session data. |
79 |
80 |
81 | module.UNLINK (storage, red, name, key, current_time[, metadata]) |
82 | Delete session data. |
83 |
84 |
85 | module.READ_METADATA (storage, red, name, audience, subject, current_time) |
86 | Read session metadata. |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | -
98 |
99 | module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)
100 |
101 | -
102 | Store session data.
103 |
104 |
105 |
Parameters:
106 |
107 | - storage
108 | table
109 | the storage
110 |
111 | - red
112 | table
113 | the redis instance
114 |
115 | - name
116 | string
117 | the cookie name
118 |
119 | - key
120 | string
121 | session key
122 |
123 | - value
124 | string
125 | session value
126 |
127 | - ttl
128 | number
129 | session ttl
130 |
131 | - current_time
132 | number
133 | current time
134 |
135 | - old_key
136 | string
137 | old session id
138 | (optional)
139 |
140 | - stale_ttl
141 | string
142 | stale ttl
143 |
144 | - metadata
145 | table
146 | table of metadata
147 | (optional)
148 |
149 | - remember
150 | table
151 | whether storing persistent session or not
152 |
153 |
154 |
155 | Returns:
156 |
157 | -
158 | true or nil
159 | ok
160 | -
161 | string
162 | error message
163 |
164 |
165 |
166 |
167 |
168 |
169 | -
170 |
171 | module.GET (storage, red, name, key)
172 |
173 | -
174 | Retrieve session data.
175 |
176 |
177 |
Parameters:
178 |
179 | - storage
180 | table
181 | the storage
182 |
183 | - red
184 | table
185 | the redis instance
186 |
187 | - name
188 | string
189 | cookie name
190 |
191 | - key
192 | string
193 | session key
194 |
195 |
196 |
197 | Returns:
198 |
199 | -
200 | string or nil
201 | session data
202 | -
203 | string
204 | error message
205 |
206 |
207 |
208 |
209 |
210 |
211 | -
212 |
213 | module.UNLINK (storage, red, name, key, current_time[, metadata])
214 |
215 | -
216 | Delete session data.
217 |
218 |
219 |
Parameters:
220 |
221 | - storage
222 | table
223 | the storage
224 |
225 | - red
226 | table
227 | the redis instance
228 |
229 | - name
230 | string
231 | cookie name
232 |
233 | - key
234 | string
235 | session key
236 |
237 | - current_time
238 | number
239 | current time
240 |
241 | - metadata
242 | table
243 | session meta data
244 | (optional)
245 |
246 |
247 |
248 | Returns:
249 |
250 | -
251 | boolean or nil
252 | session data
253 | -
254 | string
255 | error message
256 |
257 |
258 |
259 |
260 |
261 |
262 | -
263 |
264 | module.READ_METADATA (storage, red, name, audience, subject, current_time)
265 |
266 | -
267 | Read session metadata.
268 |
269 |
270 |
Parameters:
271 |
272 | - storage
273 | table
274 | the storage
275 |
276 | - red
277 | table
278 | the redis instance
279 |
280 | - name
281 | string
282 | cookie name
283 |
284 | - audience
285 | string
286 | session key
287 |
288 | - subject
289 | string
290 | session key
291 |
292 | - current_time
293 | number
294 | current time
295 |
296 |
297 |
298 | Returns:
299 |
300 | -
301 | table or nil
302 | session metadata
303 | -
304 | string
305 | error message
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
generated by LDoc 1.5.0
319 |
Last updated 2025-04-16 06:31:49
320 |
321 |
322 |
323 |
324 |
--------------------------------------------------------------------------------
/docs/modules/resty.session.shm.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Session Library for OpenResty Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
resty.session
28 |
29 |
30 |
33 |
34 |
Contents
35 |
40 |
41 |
42 |
Modules
43 |
59 |
60 |
61 |
62 |
63 |
64 |
Module resty.session.shm
65 |
Shared Memory (SHM) backend for session library
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | configuration |
75 | Shared memory storage backend configuration |
76 |
77 |
78 |
79 |
85 |
86 |
87 |
88 | instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember) |
89 | Store session data. |
90 |
91 |
92 | instance:get (name, key) |
93 | Retrieve session data. |
94 |
95 |
96 | instance:delete (name, key[, metadata]) |
97 | Delete session data. |
98 |
99 |
100 | instance:read_metadata (name, audience, subject, current_time) |
101 | Read session metadata. |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | -
113 |
114 | configuration
115 |
116 | -
117 | Shared memory storage backend configuration
118 |
119 |
120 |
Fields:
121 |
122 | - prefix
123 | Prefix for the keys stored in SHM.
124 |
125 | - suffix
126 | Suffix for the keys stored in SHM.
127 |
128 | - zone
129 | A name of shared memory zone (defaults to
sessions
).
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | -
143 |
144 | module.new ([configuration])
145 |
146 | -
147 | Create a SHM storage.
148 |
149 |
This creates a new shared memory storage instance.
150 |
151 |
152 |
Parameters:
153 |
154 | - configuration
155 | table
156 | shm storage configuration
157 | (optional)
158 |
159 |
160 |
161 | Returns:
162 |
163 |
164 | table
165 | shm storage instance
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | -
177 |
178 | instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)
179 |
180 | -
181 | Store session data.
182 |
183 |
184 |
Parameters:
185 |
186 | - name
187 | string
188 | cookie name
189 |
190 | - key
191 | string
192 | session key
193 |
194 | - value
195 | string
196 | session value
197 |
198 | - ttl
199 | number
200 | session ttl
201 |
202 | - current_time
203 | number
204 | current time
205 |
206 | - old_key
207 | string
208 | old session id
209 | (optional)
210 |
211 | - stale_ttl
212 | string
213 | stale ttl
214 |
215 | - metadata
216 | table
217 | table of metadata
218 | (optional)
219 |
220 | - remember
221 | boolean
222 | whether storing persistent session or not
223 |
224 |
225 |
226 | Returns:
227 |
228 | -
229 | true or nil
230 | ok
231 | -
232 | string
233 | error message
234 |
235 |
236 |
237 |
238 |
239 |
240 | -
241 |
242 | instance:get (name, key)
243 |
244 | -
245 | Retrieve session data.
246 |
247 |
248 |
Parameters:
249 |
250 | - name
251 | string
252 | cookie name
253 |
254 | - key
255 | string
256 | session key
257 |
258 |
259 |
260 | Returns:
261 |
262 | -
263 | string or nil
264 | session data
265 | -
266 | string
267 | error message
268 |
269 |
270 |
271 |
272 |
273 |
274 | -
275 |
276 | instance:delete (name, key[, metadata])
277 |
278 | -
279 | Delete session data.
280 |
281 |
282 |
Parameters:
283 |
284 | - name
285 | string
286 | cookie name
287 |
288 | - key
289 | string
290 | session key
291 |
292 | - metadata
293 | table
294 | session meta data
295 | (optional)
296 |
297 |
298 |
299 | Returns:
300 |
301 | -
302 | boolean or nil
303 | session data
304 | -
305 | string
306 | error message
307 |
308 |
309 |
310 |
311 |
312 |
313 | -
314 |
315 | instance:read_metadata (name, audience, subject, current_time)
316 |
317 | -
318 | Read session metadata.
319 |
320 |
321 |
Parameters:
322 |
323 | - name
324 | string
325 | cookie name
326 |
327 | - audience
328 | string
329 | session key
330 |
331 | - subject
332 | string
333 | session key
334 |
335 | - current_time
336 | number
337 | current time
338 |
339 |
340 |
341 | Returns:
342 |
343 | -
344 | table or nil
345 | session metadata
346 | -
347 | string
348 | error message
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
generated by LDoc 1.5.0
362 |
Last updated 2025-04-16 06:31:49
363 |
364 |
365 |
366 |
367 |
--------------------------------------------------------------------------------
/lib/resty/session/dshm.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Distributed Shared Memory (DSHM) backend for session library
3 | --
4 | -- @module resty.session.dshm
5 |
6 |
7 | local buffer = require "string.buffer"
8 | local utils = require "resty.session.utils"
9 | local dshm = require "resty.dshm"
10 |
11 |
12 | local meta_get_latest = utils.meta_get_latest
13 | local meta_get_value = utils.meta_get_value
14 | local get_name = utils.get_name
15 |
16 |
17 | local setmetatable = setmetatable
18 | local error = error
19 | local pairs = pairs
20 | local null = ngx.null
21 | local max = math.max
22 |
23 |
24 | local DEFAULT_HOST = "127.0.0.1"
25 | local DEFAULT_PORT = 4321
26 |
27 |
28 | local SESSIONS_BUFFER = buffer.new(128)
29 |
30 |
31 | -- not safe for concurrent access
32 | local function update_meta(dshmc, meta_key, key, exp, current_time)
33 | local metadata = dshmc:get(meta_key)
34 | local sessions = metadata and meta_get_latest(metadata, current_time) or {}
35 |
36 | SESSIONS_BUFFER:reset()
37 |
38 | sessions[key] = exp > 0 and exp or nil
39 |
40 | local max_expiry = current_time
41 | for s, e in pairs(sessions) do
42 | SESSIONS_BUFFER:put(meta_get_value(s, e))
43 | max_expiry = max(max_expiry, e)
44 | end
45 |
46 | local ser = SESSIONS_BUFFER:get()
47 |
48 | if #ser > 0 then
49 | return dshmc:set(meta_key, ser, max_expiry - current_time)
50 | end
51 |
52 | return dshmc:delete(meta_key)
53 | end
54 |
55 |
56 | local function READ_METADATA(self, dshmc, name, audience, subject, current_time)
57 | local meta_key = get_name(self, name, audience, subject)
58 | local res = dshmc:get(meta_key)
59 | if not res then
60 | return nil, "not found"
61 | end
62 |
63 | return meta_get_latest(res, current_time)
64 | end
65 |
66 |
67 | local function SET(self, dshmc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
68 | local inferred_key = get_name(self, name, key)
69 |
70 | if not metadata and not old_key then
71 | return dshmc:set(inferred_key, value, ttl)
72 | end
73 |
74 | local ok, err = dshmc:set(inferred_key, value, ttl)
75 | if err then
76 | return nil, err
77 | end
78 |
79 | local old_name = old_key and get_name(self, name, old_key)
80 | if old_name then
81 | if remember then
82 | dshmc:delete(old_name)
83 | else
84 | dshmc:touch(old_name, stale_ttl)
85 | end
86 | end
87 |
88 | if metadata then
89 | local audiences = metadata.audiences
90 | local subjects = metadata.subjects
91 | local count = #audiences
92 | for i = 1, count do
93 | local meta_key = get_name(self, name, audiences[i], subjects[i])
94 | update_meta(dshmc, meta_key, key, current_time + ttl, current_time)
95 |
96 | if old_key then
97 | update_meta(dshmc, meta_key, old_key, 0, current_time)
98 | end
99 | end
100 | end
101 | return ok
102 | end
103 |
104 |
105 | local function GET(self, dshmc, name, key)
106 | local res, err = dshmc:get(get_name(self, name, key))
107 | if err then
108 | return nil, err
109 | end
110 | return res
111 | end
112 |
113 |
114 | local function DELETE(self, dshmc, name, key, current_time, metadata)
115 | local key_name = get_name(self, name, key)
116 | local ok, err = dshmc:delete(key_name)
117 |
118 | if not metadata then
119 | return ok, err
120 | end
121 |
122 | local audiences = metadata.audiences
123 | local subjects = metadata.subjects
124 | local count = #audiences
125 | for i = 1, count do
126 | local meta_key = get_name(self, name, audiences[i], subjects[i])
127 | update_meta(dshmc, meta_key, key, 0, current_time)
128 | end
129 |
130 | return ok, err
131 | end
132 |
133 |
134 | local function exec(self, func, ...)
135 | local dshmc = dshm:new()
136 | local connect_timeout = self.connect_timeout
137 | local send_timeout = self.send_timeout
138 | local read_timeout = self.read_timeout
139 | if connect_timeout or send_timeout or read_timeout then
140 | dshmc.sock:settimeouts(connect_timeout, send_timeout, read_timeout)
141 | end
142 |
143 | local ok, err = dshmc:connect(self.host, self.port, self.options)
144 | if not ok then
145 | return nil, err
146 | end
147 |
148 | if self.ssl and dshmc:get_reused_times() == 0 then
149 | ok, err = dshmc.sock:sslhandshake(false, self.server_name, self.ssl_verify)
150 | if not ok then
151 | dshmc:close()
152 | return nil, err
153 | end
154 | end
155 |
156 | ok, err = func(self, dshmc, ...)
157 | if err then
158 | dshmc:close()
159 | return nil, err
160 | end
161 |
162 | if not dshmc:set_keepalive(self.keepalive_timeout) then
163 | dshmc:close()
164 | end
165 |
166 | if ok == null then
167 | ok = nil
168 | end
169 |
170 | return ok, err
171 | end
172 |
173 |
174 | ---
175 | -- Storage
176 | -- @section instance
177 |
178 |
179 | local metatable = {}
180 |
181 |
182 | metatable.__index = metatable
183 |
184 |
185 | function metatable.__newindex()
186 | error("attempt to update a read-only table", 2)
187 | end
188 |
189 |
190 | ---
191 | -- Store session data.
192 | --
193 | -- @function instance:set
194 | -- @tparam string name cookie name
195 | -- @tparam string key session key
196 | -- @tparam string value session value
197 | -- @tparam number ttl session ttl
198 | -- @tparam number current_time current time
199 | -- @tparam[opt] string old_key old session id
200 | -- @tparam string stale_ttl stale ttl
201 | -- @tparam[opt] table metadata table of metadata
202 | -- @tparam boolean remember whether storing persistent session or not
203 | -- @treturn true|nil ok
204 | -- @treturn string error message
205 | function metatable:set(...)
206 | return exec(self, SET, ...)
207 | end
208 |
209 |
210 | ---
211 | -- Retrieve session data.
212 | --
213 | -- @function instance:get
214 | -- @tparam string name cookie name
215 | -- @tparam string key session key
216 | -- @treturn string|nil session data
217 | -- @treturn string error message
218 | function metatable:get(...)
219 | return exec(self, GET, ...)
220 | end
221 |
222 |
223 | ---
224 | -- Delete session data.
225 | --
226 | -- @function instance:delete
227 | -- @tparam string name cookie name
228 | -- @tparam string key session key
229 | -- @tparam[opt] table metadata session meta data
230 | -- @treturn boolean|nil session data
231 | -- @treturn string error message
232 | function metatable:delete(...)
233 | return exec(self, DELETE, ...)
234 | end
235 |
236 |
237 | ---
238 | -- Read session metadata.
239 | --
240 | -- @function instance:read_metadata
241 | -- @tparam string name cookie name
242 | -- @tparam string audience session key
243 | -- @tparam string subject session key
244 | -- @tparam number current_time current time
245 | -- @treturn table|nil session metadata
246 | -- @treturn string error message
247 | function metatable:read_metadata(...)
248 | return exec(self, READ_METADATA, ...)
249 | end
250 |
251 |
252 | local storage = {}
253 |
254 |
255 | ---
256 | -- Configuration
257 | -- @section configuration
258 |
259 |
260 | ---
261 | -- Distributed shared memory storage backend configuration
262 | -- @field prefix The prefix for the keys stored in DSHM.
263 | -- @field suffix The suffix for the keys stored in DSHM.
264 | -- @field host The host to connect (defaults to `"127.0.0.1"`).
265 | -- @field port The port to connect (defaults to `4321`).
266 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
267 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
268 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
269 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
270 | -- @field pool A custom name for the connection pool being used.
271 | -- @field pool_size The size of the connection pool.
272 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
273 | -- @field ssl Enable SSL (defaults to `false`).
274 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
275 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI).
276 | -- @table configuration
277 |
278 |
279 | ---
280 | -- Constructors
281 | -- @section constructors
282 |
283 |
284 | ---
285 | -- Create a distributed shared memory storage.
286 | --
287 | -- This creates a new distributed shared memory storage instance.
288 | --
289 | -- @function module.new
290 | -- @tparam[opt] table configuration DSHM storage @{configuration}
291 | -- @treturn table DSHM storage instance
292 | function storage.new(configuration)
293 | local prefix = configuration and configuration.prefix
294 | local suffix = configuration and configuration.suffix
295 |
296 | local host = configuration and configuration.host or DEFAULT_HOST
297 | local port = configuration and configuration.port or DEFAULT_PORT
298 |
299 | local connect_timeout = configuration and configuration.connect_timeout
300 | local send_timeout = configuration and configuration.send_timeout
301 | local read_timeout = configuration and configuration.read_timeout
302 | local keepalive_timeout = configuration and configuration.keepalive_timeout
303 |
304 | local pool = configuration and configuration.pool
305 | local pool_size = configuration and configuration.pool_size
306 | local backlog = configuration and configuration.backlog
307 | local ssl = configuration and configuration.ssl
308 | local ssl_verify = configuration and configuration.ssl_verify
309 | local server_name = configuration and configuration.server_name
310 |
311 | if pool or pool_size or backlog then
312 | return setmetatable({
313 | prefix = prefix,
314 | suffix = suffix,
315 | host = host,
316 | port = port,
317 | connect_timeout = connect_timeout,
318 | send_timeout = send_timeout,
319 | read_timeout = read_timeout,
320 | keepalive_timeout = keepalive_timeout,
321 | ssl = ssl,
322 | ssl_verify = ssl_verify,
323 | server_name = server_name,
324 | options = {
325 | pool = pool,
326 | pool_size = pool_size,
327 | backlog = backlog,
328 | }
329 | }, metatable)
330 | end
331 |
332 | return setmetatable({
333 | prefix = prefix,
334 | suffix = suffix,
335 | host = host,
336 | port = port,
337 | connect_timeout = connect_timeout,
338 | send_timeout = send_timeout,
339 | read_timeout = read_timeout,
340 | keepalive_timeout = keepalive_timeout,
341 | ssl = ssl,
342 | ssl_verify = ssl_verify,
343 | server_name = server_name,
344 | }, metatable)
345 | end
346 |
347 |
348 | return storage
349 |
--------------------------------------------------------------------------------
/lib/resty/session/file.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- File storage backend for session library.
3 | --
4 | -- @module resty.session.file
5 |
6 |
7 | local file_utils = require "resty.session.file.utils"
8 |
9 |
10 | local run_worker_thread = file_utils.run_worker_thread
11 |
12 |
13 | local setmetatable = setmetatable
14 | local error = error
15 | local byte = string.byte
16 |
17 |
18 | local SLASH_BYTE = byte("/")
19 |
20 |
21 | local THREAD_MODULE = "resty.session.file.thread"
22 |
23 |
24 | local DEFAULT_POOL = "default"
25 | local DEFAULT_PATH do
26 | local path = os.tmpname()
27 | local pos
28 | for i = #path, 1, -1 do
29 | if byte(path, i) == SLASH_BYTE then
30 | pos = i
31 | break
32 | end
33 | end
34 |
35 | DEFAULT_PATH = path:sub(1, pos)
36 | end
37 |
38 |
39 | local function run_thread(self, func, ...)
40 | local ok, res, err = run_worker_thread(self.pool, THREAD_MODULE, func, self.path, self.prefix, self.suffix, ...)
41 | if not ok then
42 | return nil, res
43 | end
44 |
45 | return res, err
46 | end
47 |
48 |
49 | ---
50 | -- Storage
51 | -- @section instance
52 |
53 |
54 | local metatable = {}
55 |
56 |
57 | metatable.__index = metatable
58 |
59 |
60 | function metatable.__newindex()
61 | error("attempt to update a read-only table", 2)
62 | end
63 |
64 |
65 | ---
66 | -- Store session data.
67 | --
68 | -- @function instance:set
69 | -- @tparam string name cookie name
70 | -- @tparam string key session key
71 | -- @tparam string value session value
72 | -- @tparam number ttl session ttl
73 | -- @tparam number current_time current time
74 | -- @tparam[opt] string old_key old session id
75 | -- @tparam string stale_ttl stale ttl
76 | -- @tparam[opt] table metadata table of metadata
77 | -- @tparam boolean remember whether storing persistent session or not
78 | -- @treturn true|nil ok
79 | -- @treturn string error message
80 | function metatable:set(...)
81 | return run_thread(self, "set", ...)
82 | end
83 |
84 |
85 | ---
86 | -- Retrieve session data.
87 | --
88 | -- @function instance:get
89 | -- @tparam string name cookie name
90 | -- @tparam string key session key
91 | -- @treturn string|nil session data
92 | -- @treturn string error message
93 | function metatable:get(...)
94 | return run_thread(self, "get", ...)
95 | end
96 |
97 |
98 | ---
99 | -- Delete session data.
100 | --
101 | -- @function instance:delete
102 | -- @tparam string name cookie name
103 | -- @tparam string key session key
104 | -- @tparam[opt] table metadata session meta data
105 | -- @treturn boolean|nil session data
106 | -- @treturn string error message
107 | function metatable:delete(...)
108 | return run_thread(self, "delete", ...)
109 | end
110 |
111 |
112 | ---
113 | -- Read session metadata.
114 | --
115 | -- @function instance:read_metadata
116 | -- @tparam string name cookie name
117 | -- @tparam string audience session key
118 | -- @tparam string subject session key
119 | -- @tparam number current_time current time
120 | -- @treturn table|nil session metadata
121 | -- @treturn string error message
122 | function metatable:read_metadata(...)
123 | return run_thread(self, "read_metadata", ...)
124 | end
125 |
126 |
127 | local storage = {}
128 |
129 |
130 | ---
131 | -- Configuration
132 | -- @section configuration
133 |
134 |
135 | ---
136 | -- File storage backend configuration
137 | -- @field prefix File prefix for session file.
138 | -- @field suffix File suffix (or extension without `.`) for session file.
139 | -- @field pool Name of the thread pool under which file writing happens (available on Linux only).
140 | -- @field path Path (or directory) under which session files are created.
141 | -- @table configuration
142 |
143 |
144 | ---
145 | -- Constructors
146 | -- @section constructors
147 |
148 |
149 | ---
150 | -- Create a file storage.
151 | --
152 | -- This creates a new file storage instance.
153 | --
154 | -- @function module.new
155 | -- @tparam[opt] table configuration file storage @{configuration}
156 | -- @treturn table file storage instance
157 | function storage.new(configuration)
158 | local prefix = configuration and configuration.prefix
159 | local suffix = configuration and configuration.suffix
160 |
161 | local pool = configuration and configuration.pool or DEFAULT_POOL
162 | local path = configuration and configuration.path or DEFAULT_PATH
163 |
164 | if byte(path, -1) ~= SLASH_BYTE then
165 | path = path .. "/"
166 | end
167 |
168 | return setmetatable({
169 | prefix = prefix ~= "" and prefix or nil,
170 | suffix = suffix ~= "" and suffix or nil,
171 | pool = pool,
172 | path = path,
173 | }, metatable)
174 | end
175 |
176 |
177 | return storage
178 |
--------------------------------------------------------------------------------
/lib/resty/session/file/thread.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- File storage backend worker thread module
3 | --
4 | -- @module resty.session.file.thread
5 |
6 |
7 | local file_utils = require "resty.session.file.utils"
8 | local utils = require "resty.session.utils"
9 |
10 |
11 | local get_modification = file_utils.get_modification
12 | local meta_get_key = file_utils.meta_get_key
13 | local file_create = file_utils.file_create
14 | local file_append = file_utils.file_append
15 | local file_delete = file_utils.file_delete
16 | local file_touch = file_utils.file_touch
17 | local file_read = file_utils.file_read
18 | local get_path = file_utils.get_path
19 | local cleanup = file_utils.cleanup
20 |
21 |
22 | local meta_get_latest = utils.meta_get_latest
23 | local meta_get_value = utils.meta_get_value
24 |
25 |
26 | local max = math.max
27 |
28 |
29 | local function update_meta(path, prefix, suffix, name, meta_key, key, exp)
30 | local meta_value = meta_get_value(key, exp)
31 | if not meta_value then
32 | return
33 | end
34 |
35 | local file_path = get_path(path, prefix, suffix, name, meta_key)
36 | file_append(file_path, meta_value)
37 | local current_expiry = get_modification(file_path) or 0
38 |
39 | local new_expiry = max(current_expiry, exp)
40 | file_touch(file_path, new_expiry)
41 | end
42 |
43 |
44 | ---
45 | -- Store session data.
46 | --
47 | -- @function module.set
48 | -- @tparam string path the path where sessions are stored
49 | -- @tparam string prefix the prefix for session files
50 | -- @tparam string suffix the suffix for session files
51 | -- @tparam string name the cookie name
52 | -- @tparam string key session key
53 | -- @tparam string value session value
54 | -- @tparam number ttl session ttl
55 | -- @tparam number current_time current time
56 | -- @tparam[opt] string old_key old session id
57 | -- @tparam string stale_ttl stale ttl
58 | -- @tparam[opt] table metadata table of metadata
59 | -- @tparam table remember whether storing persistent session or not
60 | -- @treturn table|nil session metadata
61 | -- @treturn string error message
62 | local function set(path, prefix, suffix, name, key, value, ttl,
63 | current_time, old_key, stale_ttl, metadata, remember)
64 | local file_path = get_path(path, prefix, suffix, name, key)
65 | if not metadata and not old_key then
66 | local ok, err = file_create(file_path, value)
67 |
68 | if ok and current_time and ttl then
69 | file_touch(file_path, current_time + ttl)
70 | end
71 |
72 | cleanup(path, prefix, suffix, name, current_time)
73 |
74 | return ok, err
75 | end
76 |
77 | local old_ttl, old_file_path
78 | if old_key then
79 | old_file_path = get_path(path, prefix, suffix, name, old_key)
80 | if not remember then
81 | local exp = get_modification(old_file_path)
82 | if exp then
83 | old_ttl = exp - current_time
84 | end
85 | end
86 | end
87 |
88 | local ok, err = file_create(file_path, value)
89 | if ok and current_time and ttl then
90 | file_touch(file_path, current_time + ttl)
91 | end
92 |
93 | if old_file_path then
94 | if remember then
95 | file_delete(old_file_path)
96 |
97 | elseif not old_ttl or old_ttl > stale_ttl then
98 | file_touch(old_file_path, current_time + stale_ttl)
99 | end
100 | end
101 |
102 | if metadata then
103 | local audiences = metadata.audiences
104 | local subjects = metadata.subjects
105 | local count = #audiences
106 | for i = 1, count do
107 | local meta_key = meta_get_key(audiences[i], subjects[i])
108 | update_meta(path, prefix, suffix, name, meta_key, key, current_time + ttl)
109 |
110 | if old_key then
111 | update_meta(path, prefix, suffix, name, meta_key, old_key, 0)
112 | end
113 | end
114 | end
115 |
116 | cleanup(path, prefix, suffix, name, current_time)
117 |
118 | return ok, err
119 | end
120 |
121 |
122 | ---
123 | -- Retrieve session data.
124 | --
125 | -- @function module.GET
126 | -- @tparam string path the path where sessions are stored
127 | -- @tparam string prefix the prefix for session files
128 | -- @tparam string suffix the suffix for session files
129 | -- @tparam string name cookie name
130 | -- @tparam string key session key
131 | -- @treturn string|nil session data
132 | -- @treturn string error message
133 | local function get(path, prefix, suffix, name, key, current_time)
134 | local file_path = get_path(path, prefix, suffix, name, key)
135 |
136 | -- TODO: do we want to check expiry here?
137 | -- The cookie header already has the info and has a MAC too.
138 | local exp = get_modification(file_path)
139 | if exp and exp < current_time then
140 | return nil, "expired"
141 | end
142 |
143 | return file_read(file_path)
144 | end
145 |
146 |
147 | ---
148 | -- Delete session data.
149 | --
150 | -- @function module.delete
151 | -- @tparam string path the path where sessions are stored
152 | -- @tparam string prefix the prefix for session files
153 | -- @tparam string suffix the suffix for session files
154 | -- @tparam string name the cookie name
155 | -- @tparam string key session key
156 | -- @tparam number current_time current time
157 | -- @treturn table|nil session metadata
158 | -- @treturn string error message
159 | local function delete(path, prefix, suffix, name, key, current_time, metadata)
160 | local file_path = get_path(path, prefix, suffix, name, key)
161 | file_delete(file_path)
162 |
163 | if metadata then
164 | local audiences = metadata.audiences
165 | local subjects = metadata.subjects
166 | local count = #audiences
167 | for i = 1, count do
168 | local meta_key = meta_get_key(audiences[i], subjects[i])
169 | update_meta(path, prefix, suffix, name, meta_key, key, 0)
170 | end
171 | end
172 |
173 | cleanup(path, prefix, suffix, name, current_time)
174 |
175 | return true
176 | end
177 |
178 |
179 | ---
180 | -- Read session metadata.
181 | --
182 | -- @function module.read_metadata
183 | -- @tparam string path the path where sessions are stored
184 | -- @tparam string prefix the prefix for session files
185 | -- @tparam string suffix the suffix for session files
186 | -- @tparam string name the cookie name
187 | -- @tparam string audience session audience
188 | -- @tparam string subject session subject
189 | -- @tparam number current_time current time
190 | -- @treturn table|nil session metadata
191 | -- @treturn string error message
192 | local function read_metadata(path, prefix, suffix, name, audience, subject, current_time)
193 | local meta_key = meta_get_key(audience, subject)
194 | local file_path = get_path(path, prefix, suffix, name, meta_key)
195 | local metadata, err = file_read(file_path)
196 | if not metadata then
197 | return nil, err
198 | end
199 |
200 | return meta_get_latest(metadata, current_time)
201 | end
202 |
203 |
204 | return {
205 | set = set,
206 | get = get,
207 | delete = delete,
208 | read_metadata = read_metadata,
209 | }
210 |
--------------------------------------------------------------------------------
/lib/resty/session/file/utils.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- File storage utilities
3 | --
4 | -- @module resty.session.file.utils
5 |
6 |
7 | local lfs = require "lfs"
8 |
9 |
10 | local attributes = lfs.attributes
11 | local touch = lfs.touch
12 | local dir = lfs.dir
13 |
14 |
15 |
16 | local file_delete = os.remove
17 | local random = math.random
18 | local pcall = pcall
19 | local open = io.open
20 | local fmt = string.format
21 |
22 |
23 | local CLEANUP_PROBABILITY = 0.0005 -- 1 / 2000
24 |
25 |
26 | local run_worker_thread do
27 | run_worker_thread = ngx.run_worker_thread -- luacheck: ignore
28 | if not run_worker_thread then
29 | local require = require
30 | run_worker_thread = function(_, module, func, ...)
31 | local m = require(module)
32 | return pcall(m[func], ...)
33 | end
34 | end
35 | end
36 |
37 |
38 | local function file_touch(path, mtime)
39 | return touch(path, nil, mtime)
40 | end
41 |
42 |
43 | ---
44 | -- Store data in file.
45 | --
46 | -- @function file_create
47 | -- @tparam string path file path
48 | -- @tparam string content file content
49 | -- @treturn true|nil ok
50 | -- @treturn string error message
51 | local function file_create(path, content)
52 | local file, err = open(path, "wb")
53 | if not file then
54 | return nil, err
55 | end
56 |
57 | local ok, err = file:write(content)
58 |
59 | file:close()
60 |
61 | if not ok then
62 | file_delete(path)
63 | return nil, err
64 | end
65 |
66 | return true
67 | end
68 |
69 |
70 | ---
71 | -- Append data in file.
72 | --
73 | -- @function file_append
74 | -- @tparam string path file path
75 | -- @tparam string data file data
76 | -- @treturn true|nil ok
77 | -- @treturn string error message
78 | local function file_append(path, data)
79 | local file, err = open(path, "a")
80 | if not file then
81 | return nil, err
82 | end
83 |
84 | local ok, err = file:write(data)
85 |
86 | file:close()
87 |
88 | if not ok then
89 | file_delete(path)
90 | return nil, err
91 | end
92 |
93 | return true
94 | end
95 |
96 |
97 | ---
98 | -- Read data from a file.
99 | --
100 | -- @function file_read
101 | -- @tparam string path file to read
102 | -- @treturn string|nil content
103 | -- @treturn string error message
104 | local function file_read(path)
105 | local file, err = open(path, "rb")
106 | if not file then
107 | return nil, err
108 | end
109 |
110 | local content, err = file:read("*a")
111 |
112 | file:close()
113 |
114 | if not content then
115 | return nil, err
116 | end
117 |
118 | return content
119 | end
120 |
121 |
122 | ---
123 | -- Generate the path for a file to be stored at.
124 | --
125 | -- @tparam string path the path where sessions are stored
126 | -- @tparam string prefix the prefix for session files
127 | -- @tparam string suffix the suffix for session files
128 | -- @tparam string name the cookie name
129 | -- @tparam string key session key
130 | -- @treturn string path
131 | local function get_path(path, prefix, suffix, name, key)
132 | if prefix and suffix then
133 | return fmt("%s%s_%s_%s.%s", path, prefix, name, key, suffix)
134 | elseif prefix then
135 | return fmt("%s%s_%s_%s", path, prefix, name, key)
136 | elseif suffix then
137 | return fmt("%s%s_%s.%s", path, name, key, suffix)
138 | else
139 | return fmt("%s%s_%s", path, name, key)
140 | end
141 | end
142 |
143 |
144 | ---
145 | -- Get the value modification time of a file.
146 | --
147 | -- @function utils.get_modification
148 | -- @tparam string path the path to the file
149 | local function get_modification(path)
150 | local attr = attributes(path)
151 | if not attr or attr.mode ~= "file" then
152 | return
153 | end
154 |
155 | return attr.modification or nil
156 | end
157 |
158 |
159 | ---
160 | -- Given an audience and a subject, generate a metadata key.
161 | --
162 | -- @function utils.meta_get_key
163 | -- @tparam string audience session audience
164 | -- @tparam string subject session subject
165 | -- @treturn string metadata key
166 | local function meta_get_key(audience, subject)
167 | return fmt("%s:%s", audience, subject)
168 | end
169 |
170 |
171 | ---
172 | -- Validate a file name.
173 | -- Run a few checks to try to determine if the file is managed by this library
174 | --
175 | -- @function utils.validate_file_name
176 | -- @tparam string prefix the prefix for session files
177 | -- @tparam string suffix the suffix for session files
178 | -- @tparam string name cookie name
179 | -- @tparam string filename the name of the file
180 | -- @treturn true|false whether the file is managed by the library or not
181 | local validate_file_name do
182 | local byte = string.byte
183 | local sub = string.sub
184 | local find = string.find
185 |
186 | local UNDERSCORE = byte("_")
187 | local DOT = byte(".")
188 |
189 | validate_file_name = function(prefix, suffix, name, filename)
190 | if filename == "." or filename == ".." then
191 | return false
192 | end
193 |
194 | local plen = 0
195 | if prefix then
196 | plen = #prefix
197 | if byte(filename, plen + 1) ~= UNDERSCORE or
198 | (plen > 0 and sub(filename, 1, plen) ~= prefix) then
199 | return false
200 | end
201 | end
202 |
203 | local slen = 0
204 | if suffix then
205 | slen = #suffix
206 |
207 | if byte(filename, -1 - slen) ~= DOT or
208 | (slen > 0 and sub(filename, -slen) ~= suffix)
209 | then
210 | return false
211 | end
212 | end
213 |
214 | local nlen = #name
215 | local name_start = plen == 0 and 1 or plen + 2
216 | local name_end = name_start + nlen - 1
217 |
218 | if byte(filename, name_end + 1) ~= UNDERSCORE or
219 | sub(filename, name_start, name_end) ~= name
220 | then
221 | return false
222 | end
223 |
224 | local rest
225 | if slen == 0 then
226 | rest = sub(filename, name_end + 2)
227 | else
228 | rest = sub(filename, name_end + 2, -2 - slen)
229 | end
230 |
231 | local rlen = #rest
232 | if rlen < 3 then
233 | return false
234 | end
235 |
236 | if rlen ~= 43 then
237 | local colon_pos = find(rest, ":", 2, true)
238 | if not colon_pos or colon_pos == 43 then
239 | return false
240 | end
241 | end
242 |
243 | return true
244 | end
245 | end
246 |
247 |
248 | ---
249 | -- Clean up expired session and metadata files.
250 | --
251 | -- @function utils.cleanup
252 | -- @tparam string path the path where sessions are stored
253 | -- @tparam string prefix the prefix for session files
254 | -- @tparam string suffix the suffix for session files
255 | -- @tparam string name cookie name
256 | -- @tparam number current_time current time
257 | -- @treturn true|false whether clean up completed
258 | local function cleanup(path, prefix, suffix, name, current_time)
259 | if random() > CLEANUP_PROBABILITY then
260 | return false
261 | end
262 |
263 | local deleted = 0
264 |
265 | for file in dir(path) do
266 | if validate_file_name(prefix, suffix, name, file) then
267 | local exp = get_modification(path .. file)
268 | if exp and exp < current_time then
269 | file_delete(path .. file)
270 | deleted = deleted + 1
271 | end
272 | end
273 | end
274 | return true
275 | end
276 |
277 |
278 | return {
279 | validate_file_name = validate_file_name,
280 | run_worker_thread = run_worker_thread,
281 | get_modification = get_modification,
282 | meta_get_key = meta_get_key,
283 | file_create = file_create,
284 | file_append = file_append,
285 | file_delete = file_delete,
286 | file_touch = file_touch,
287 | file_read = file_read,
288 | get_path = get_path,
289 | cleanup = cleanup,
290 | }
291 |
--------------------------------------------------------------------------------
/lib/resty/session/memcached.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Memcached backend for session library
3 | --
4 | -- @module resty.session.memcached
5 |
6 |
7 | local memcached = require "resty.memcached"
8 | local buffer = require "string.buffer"
9 | local utils = require "resty.session.utils"
10 |
11 |
12 | local meta_get_latest = utils.meta_get_latest
13 | local meta_get_value = utils.meta_get_value
14 | local get_name = utils.get_name
15 | local errmsg = utils.errmsg
16 |
17 |
18 | local setmetatable = setmetatable
19 | local random = math.random
20 | local error = error
21 | local pairs = pairs
22 | local null = ngx.null
23 | local log = ngx.log
24 | local max = math.max
25 |
26 |
27 | local WARN = ngx.WARN
28 |
29 |
30 | local CLEANUP_PROBABILITY = 0.1 -- 1 / 10
31 | local SESSIONS_BUFFER = buffer.new(128)
32 |
33 |
34 | local function cleanup(memc, meta_key, current_time)
35 | local res, _, cas_unique, err = memc:gets(meta_key)
36 | if not res then
37 | return nil, err
38 | end
39 |
40 | local sessions = meta_get_latest(res, current_time)
41 |
42 | SESSIONS_BUFFER:reset()
43 |
44 | local max_expiry = current_time
45 | for key, exp in pairs(sessions) do
46 | SESSIONS_BUFFER:put(meta_get_value(key, exp))
47 | max_expiry = max(max_expiry, exp)
48 | end
49 |
50 | local exp = max_expiry - current_time
51 | if exp > 0 then
52 | return memc:cas(meta_key, SESSIONS_BUFFER:get(), cas_unique, exp)
53 | end
54 |
55 | return memc:delete(meta_key)
56 | end
57 |
58 |
59 | local function read_metadata(self, memc, name, audience, subject, current_time)
60 | local meta_key = get_name(self, name, audience, subject)
61 | local res, _, err = memc:get(meta_key)
62 | if not res then
63 | return nil, err
64 | end
65 |
66 | return meta_get_latest(res, current_time)
67 | end
68 |
69 |
70 | -- TODO possible improvement: when available in the lib, use pipelines
71 | local function SET(self, memc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
72 | local inferred_key = get_name(self, name, key)
73 |
74 | if not metadata and not old_key then
75 | return memc:set(inferred_key, value, ttl)
76 | end
77 |
78 | local ok, err = memc:set(inferred_key, value, ttl)
79 | if not ok then
80 | return nil, err
81 | end
82 |
83 | local old_name = old_key and get_name(self, name, old_key)
84 | if old_name then
85 | if remember then
86 | memc:delete(old_name)
87 | else
88 | memc:touch(old_name, stale_ttl)
89 | end
90 | end
91 |
92 | if not metadata then
93 | return true
94 | end
95 |
96 | local audiences = metadata.audiences
97 | local subjects = metadata.subjects
98 | local count = #audiences
99 | for i = 1, count do
100 | local meta_key = get_name(self, name, audiences[i], subjects[i])
101 | local meta_value = meta_get_value(key, current_time + ttl)
102 |
103 | local added, err = memc:add(meta_key, meta_value)
104 | if not added then
105 | local appended, err2 = memc:append(meta_key, meta_value)
106 | if not appended then
107 | log(WARN, "[session] ", errmsg(err2 or err, "failed to store metadata"))
108 | end
109 | end
110 |
111 | if old_key then
112 | meta_value = meta_get_value(old_key, 0)
113 | local ok, err = memc:append(meta_key, meta_value)
114 | if not ok then
115 | log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
116 | end
117 | end
118 |
119 | -- no need to clean up every time we write
120 | -- it is just beneficial when a key is used a lot
121 | if random() < CLEANUP_PROBABILITY then
122 | cleanup(memc, meta_key, current_time)
123 | end
124 | end
125 |
126 | return true
127 | end
128 |
129 |
130 | local function GET(self, memc, name, key)
131 | local res, _, err = memc:get(get_name(self, name, key))
132 | if err then
133 | return nil, err
134 | end
135 | return res
136 | end
137 |
138 |
139 | local function DELETE(self, memc, name, key, current_time, metadata)
140 | local key_name = get_name(self, name, key)
141 | local ok, err = memc:delete(key_name)
142 | if not metadata then
143 | return ok, err
144 | end
145 |
146 | local audiences = metadata.audiences
147 | local subjects = metadata.subjects
148 | local count = #audiences
149 | for i = 1, count do
150 | local meta_key = get_name(self, name, audiences[i], subjects[i])
151 | local meta_value = meta_get_value(key, 0)
152 | local ok, err = memc:append(meta_key, meta_value)
153 | if not ok and err ~= "NOT_STORED" then
154 | log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
155 | end
156 |
157 | cleanup(memc, meta_key, current_time)
158 | end
159 |
160 | return ok, err
161 | end
162 |
163 |
164 | local DEFAULT_HOST = "127.0.0.1"
165 | local DEFAULT_PORT = 11211
166 |
167 |
168 | local function exec(self, func, ...)
169 | local memc = memcached:new()
170 |
171 | local connect_timeout = self.connect_timeout
172 | local send_timeout = self.send_timeout
173 | local read_timeout = self.read_timeout
174 | if connect_timeout or send_timeout or read_timeout then
175 | memc:set_timeouts(connect_timeout, send_timeout, read_timeout)
176 | end
177 |
178 | local ok, err do
179 | local socket = self.socket
180 | if socket then
181 | ok, err = memc:connect(socket, self.options)
182 | else
183 | ok, err = memc:connect(self.host, self.port, self.options)
184 | end
185 | end
186 | if not ok then
187 | return nil, err
188 | end
189 |
190 | if self.ssl and memc:get_reused_times() == 0 then
191 | ok, err = memc:sslhandshake(false, self.server_name, self.ssl_verify)
192 | if not ok then
193 | memc:close()
194 | return nil, err
195 | end
196 | end
197 |
198 | ok, err = func(self, memc, ...)
199 |
200 | if err then
201 | memc:close()
202 | return nil, err
203 | end
204 |
205 | if not memc:set_keepalive(self.keepalive_timeout) then
206 | memc:close()
207 | end
208 |
209 | if ok == null then
210 | ok = nil
211 | end
212 |
213 | return ok, err
214 | end
215 |
216 |
217 | ---
218 | -- Storage
219 | -- @section instance
220 |
221 |
222 | local metatable = {}
223 |
224 |
225 | metatable.__index = metatable
226 |
227 |
228 | function metatable.__newindex()
229 | error("attempt to update a read-only table", 2)
230 | end
231 |
232 |
233 | ---
234 | -- Store session data.
235 | --
236 | -- @function instance:set
237 | -- @tparam string name cookie name
238 | -- @tparam string key session key
239 | -- @tparam string value session value
240 | -- @tparam number ttl session ttl
241 | -- @tparam number current_time current time
242 | -- @tparam[opt] string old_key old session id
243 | -- @tparam string stale_ttl stale ttl
244 | -- @tparam[opt] table metadata table of metadata
245 | -- @tparam boolean remember whether storing persistent session or not
246 | -- @treturn true|nil ok
247 | -- @treturn string error message
248 | function metatable:set(...)
249 | return exec(self, SET, ...)
250 | end
251 |
252 |
253 | ---
254 | -- Retrieve session data.
255 | --
256 | -- @function instance:get
257 | -- @tparam string name cookie name
258 | -- @tparam string key session key
259 | -- @treturn string|nil session data
260 | -- @treturn string error message
261 | function metatable:get(...)
262 | return exec(self, GET, ...)
263 | end
264 |
265 |
266 | ---
267 | -- Delete session data.
268 | --
269 | -- @function instance:delete
270 | -- @tparam string name cookie name
271 | -- @tparam string key session key
272 | -- @tparam[opt] table metadata session meta data
273 | -- @treturn boolean|nil session data
274 | -- @treturn string error message
275 | function metatable:delete(...)
276 | return exec(self, DELETE, ...)
277 | end
278 |
279 |
280 | ---
281 | -- Read session metadata.
282 | --
283 | -- @function instance:read_metadata
284 | -- @tparam string name cookie name
285 | -- @tparam string audience session key
286 | -- @tparam string subject session key
287 | -- @tparam number current_time current time
288 | -- @treturn table|nil session metadata
289 | -- @treturn string error message
290 | function metatable:read_metadata(...)
291 | return exec(self, read_metadata, ...)
292 | end
293 |
294 |
295 | local storage = {}
296 |
297 |
298 | ---
299 | -- Configuration
300 | -- @section configuration
301 |
302 |
303 | ---
304 | -- Distributed shared memory storage backend configuration
305 | -- @field prefix Prefix for the keys stored in memcached.
306 | -- @field suffix Suffix for the keys stored in memcached.
307 | -- @field host The host to connect (defaults to `"127.0.0.1"`).
308 | -- @field port The port to connect (defaults to `11211`).
309 | -- @field socket The socket file to connect to (defaults to `nil`).
310 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
311 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
312 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
313 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
314 | -- @field pool A custom name for the connection pool being used.
315 | -- @field pool_size The size of the connection pool.
316 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
317 | -- @field ssl Enable SSL (defaults to `false`).
318 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
319 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI).
320 | -- @table configuration
321 |
322 |
323 | ---
324 | -- Constructors
325 | -- @section constructors
326 |
327 |
328 | ---
329 | -- Create a memcached storage.
330 | --
331 | -- This creates a new memcached storage instance.
332 | --
333 | -- @function module.new
334 | -- @tparam[opt] table configuration memcached storage @{configuration}
335 | -- @treturn table memcached storage instance
336 | function storage.new(configuration)
337 | local prefix = configuration and configuration.prefix
338 | local suffix = configuration and configuration.suffix
339 |
340 | local host = configuration and configuration.host or DEFAULT_HOST
341 | local port = configuration and configuration.port or DEFAULT_PORT
342 | local socket = configuration and configuration.socket
343 |
344 | local connect_timeout = configuration and configuration.connect_timeout
345 | local send_timeout = configuration and configuration.send_timeout
346 | local read_timeout = configuration and configuration.read_timeout
347 | local keepalive_timeout = configuration and configuration.keepalive_timeout
348 |
349 | local pool = configuration and configuration.pool
350 | local pool_size = configuration and configuration.pool_size
351 | local backlog = configuration and configuration.backlog
352 | local ssl = configuration and configuration.ssl
353 | local ssl_verify = configuration and configuration.ssl_verify
354 | local server_name = configuration and configuration.server_name
355 |
356 | if pool or pool_size or backlog then
357 | return setmetatable({
358 | prefix = prefix,
359 | suffix = suffix,
360 | host = host,
361 | port = port,
362 | socket = socket,
363 | connect_timeout = connect_timeout,
364 | send_timeout = send_timeout,
365 | read_timeout = read_timeout,
366 | keepalive_timeout = keepalive_timeout,
367 | ssl = ssl,
368 | ssl_verify = ssl_verify,
369 | server_name = server_name,
370 | options = {
371 | pool = pool,
372 | pool_size = pool_size,
373 | backlog = backlog,
374 | }
375 | }, metatable)
376 | end
377 |
378 | return setmetatable({
379 | prefix = prefix,
380 | suffix = suffix,
381 | host = host,
382 | port = port,
383 | socket = socket,
384 | connect_timeout = connect_timeout,
385 | send_timeout = send_timeout,
386 | read_timeout = read_timeout,
387 | keepalive_timeout = keepalive_timeout,
388 | ssl = ssl,
389 | ssl_verify = ssl_verify,
390 | server_name = server_name,
391 | }, metatable)
392 | end
393 |
394 |
395 | return storage
396 |
--------------------------------------------------------------------------------
/lib/resty/session/mysql.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- MySQL / MariaDB backend for session library
3 | --
4 | -- @module resty.session.mysql
5 |
6 |
7 | ---
8 | -- Database
9 | -- @section database
10 |
11 |
12 | ---
13 | -- Sessions table.
14 | --
15 | -- Database table that stores session data.
16 | --
17 | -- @usage
18 | -- CREATE TABLE IF NOT EXISTS sessions (
19 | -- sid CHAR(43) PRIMARY KEY,
20 | -- name VARCHAR(255),
21 | -- data MEDIUMTEXT,
22 | -- exp DATETIME,
23 | -- INDEX (exp)
24 | -- ) CHARACTER SET ascii;
25 | -- @table sessions
26 |
27 |
28 | ---
29 | -- Sessions metadata table.
30 | --
31 | -- This is only needed if you want to store session metadata.
32 | --
33 | -- @usage
34 | -- CREATE TABLE IF NOT EXISTS sessions_meta (
35 | -- aud VARCHAR(255),
36 | -- sub VARCHAR(255),
37 | -- sid CHAR(43),
38 | -- PRIMARY KEY (aud, sub, sid),
39 | -- CONSTRAINT FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE ON UPDATE CASCADE
40 | -- ) CHARACTER SET ascii;
41 | -- @table metadata
42 |
43 |
44 | local buffer = require "string.buffer"
45 | local mysql = require "resty.mysql"
46 |
47 |
48 | local setmetatable = setmetatable
49 | local random = math.random
50 | local ipairs = ipairs
51 | local error = error
52 | local fmt = string.format
53 |
54 |
55 | local DEFAULT_HOST = "127.0.0.1"
56 | local DEFAULT_PORT = 3306
57 | local DEFAULT_TABLE = "sessions"
58 | local DEFAULT_CHARSET = "ascii"
59 |
60 |
61 | local SET = "INSERT INTO %s (sid, name, data, exp) VALUES ('%s', '%s', '%s', FROM_UNIXTIME(%d)) AS new ON DUPLICATE KEY UPDATE data = new.data"
62 | local SET_META_PREFIX = "INSERT INTO %s (aud, sub, sid) VALUES "
63 | local SET_META_VALUES = "('%s', '%s', '%s')"
64 | local SET_META_SUFFIX = " ON DUPLICATE KEY UPDATE sid = sid"
65 | local GET_META = "SELECT sid, exp FROM %s JOIN %s USING (sid) WHERE aud = '%s' AND sub = '%s' AND exp >= FROM_UNIXTIME(%d)"
66 | local GET = "SELECT data FROM %s WHERE sid = '%s' AND exp >= FROM_UNIXTIME(%d)"
67 | local EXPIRE = "UPDATE %s SET exp = FROM_UNIXTIME(%d) WHERE sid = '%s' AND exp > FROM_UNIXTIME(%d)"
68 | local DELETE = "DELETE FROM %s WHERE sid = '%s'"
69 | local CLEANUP = "DELETE FROM %s WHERE exp < FROM_UNIXTIME(%d)"
70 |
71 |
72 | local SQL = buffer.new()
73 | local STM_DELIM = ";\n"
74 | local VAL_DELIM = ", "
75 |
76 |
77 | local CLEANUP_PROBABILITY = 0.001 -- 1 / 1000
78 |
79 |
80 | local function exec(self, query)
81 | local my = mysql:new()
82 |
83 | local connect_timeout = self.connect_timeout
84 | local send_timeout = self.send_timeout
85 | local read_timeout = self.read_timeout
86 | if connect_timeout or send_timeout or read_timeout then
87 | if my.sock and my.sock.settimeouts then
88 | my.sock:settimeouts(connect_timeout, send_timeout, read_timeout)
89 | else
90 | my:set_timeout(connect_timeout)
91 | end
92 | end
93 |
94 | local ok, err = my:connect(self.options)
95 | if not ok then
96 | return nil, err
97 | end
98 |
99 | ok, err = my:query(query)
100 |
101 | if not my:set_keepalive(self.keepalive_timeout) then
102 | my:close()
103 | end
104 |
105 | return ok, err
106 | end
107 |
108 |
109 | ---
110 | -- Storage
111 | -- @section instance
112 |
113 |
114 | local metatable = {}
115 |
116 |
117 | metatable.__index = metatable
118 |
119 |
120 | function metatable.__newindex()
121 | error("attempt to update a read-only table", 2)
122 | end
123 |
124 |
125 | ---
126 | -- Store session data.
127 | --
128 | -- @function instance:set
129 | -- @tparam string name cookie name
130 | -- @tparam string key session key
131 | -- @tparam string value session value
132 | -- @tparam number ttl session ttl
133 | -- @tparam number current_time current time
134 | -- @tparam[opt] string old_key old session id
135 | -- @tparam string stale_ttl stale ttl
136 | -- @tparam[opt] table metadata table of metadata
137 | -- @tparam boolean remember whether storing persistent session or not
138 | -- @treturn true|nil ok
139 | -- @treturn string error message
140 | function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
141 | local table = self.table
142 | local exp = ttl + current_time
143 |
144 | if not metadata and not old_key then
145 | return exec(self, fmt(SET, table, key, name, value, exp))
146 | end
147 |
148 | SQL:reset():putf(SET, table, key, name, value, exp)
149 |
150 | if old_key then
151 | if remember then
152 | SQL:put(STM_DELIM):putf(DELETE, table, old_key)
153 | else
154 | local stale_exp = stale_ttl + current_time
155 | SQL:put(STM_DELIM):putf(EXPIRE, table, stale_exp, old_key, stale_exp)
156 | end
157 | end
158 |
159 | local table_meta = self.table_meta
160 | if metadata then
161 | local audiences = metadata.audiences
162 | local subjects = metadata.subjects
163 | local count = #audiences
164 |
165 | SQL:put(STM_DELIM):putf(SET_META_PREFIX, table_meta)
166 |
167 | for i = 1, count do
168 | if i > 1 then
169 | SQL:put(VAL_DELIM)
170 | end
171 | SQL:putf(SET_META_VALUES, audiences[i], subjects[i], key)
172 | end
173 |
174 | SQL:putf(SET_META_SUFFIX)
175 | end
176 |
177 | if random() < CLEANUP_PROBABILITY then
178 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time)
179 | end
180 |
181 | return exec(self, SQL:get())
182 | end
183 |
184 |
185 | ---
186 | -- Retrieve session data.
187 | --
188 | -- @function instance:get
189 | -- @tparam string name cookie name
190 | -- @tparam string key session key
191 | -- @treturn string|nil session data
192 | -- @treturn string error message
193 | function metatable:get(name, key, current_time) -- luacheck: ignore
194 | local res, err = exec(self, fmt(GET, self.table, key, current_time))
195 | if not res then
196 | return nil, err
197 | end
198 |
199 | local row = res[1]
200 | if not row then
201 | return nil, "session not found"
202 | end
203 |
204 | local data = row.data
205 | if not row.data then
206 | return nil, "session not found"
207 | end
208 |
209 | return data
210 | end
211 |
212 |
213 | ---
214 | -- Delete session data.
215 | --
216 | -- @function instance:delete
217 | -- @tparam string name cookie name
218 | -- @tparam string key session key
219 | -- @tparam[opt] table metadata session meta data
220 | -- @treturn boolean|nil session data
221 | -- @treturn string error message
222 | function metatable:delete(name, key, current_time, metadata) -- luacheck: ignore
223 | SQL:reset():putf(DELETE, self.table, key)
224 |
225 | if random() < CLEANUP_PROBABILITY then
226 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time)
227 | end
228 |
229 | return exec(self, SQL:get())
230 | end
231 |
232 |
233 | ---
234 | -- Read session metadata.
235 | --
236 | -- @function instance:read_metadata
237 | -- @tparam string name cookie name
238 | -- @tparam string audience session key
239 | -- @tparam string subject session key
240 | -- @tparam number current_time current time
241 | -- @treturn table|nil session metadata
242 | -- @treturn string error message
243 | function metatable:read_metadata(name, audience, subject, current_time) -- luacheck: ignore
244 | local res = {}
245 | local t = exec(self, fmt(GET_META, self.table_meta, self.table, audience, subject, current_time))
246 | if not t then
247 | return nil, "not found"
248 | end
249 |
250 | for _, v in ipairs(t) do
251 | local key = v.sid
252 | if key then
253 | res[key] = v.exp
254 | end
255 | end
256 |
257 | return res
258 | end
259 |
260 | local storage = {}
261 |
262 |
263 | ---
264 | -- Configuration
265 | -- @section configuration
266 |
267 |
268 | ---
269 | -- Postgres storage backend configuration
270 | -- @field host The host to connect (defaults to `"127.0.0.1"`).
271 | -- @field port The port to connect (defaults to `3306`).
272 | -- @field socket The socket file to connect to (defaults to `nil`).
273 | -- @field username The database username to authenticate (defaults to `nil`).
274 | -- @field password Password for authentication, may be required depending on server configuration.
275 | -- @field charset The character set used on the MySQL connection (defaults to `"ascii"`).
276 | -- @field database The database name to connect.
277 | -- @field table_name Name of database table to which to store session data (defaults to `"sessions"`).
278 | -- @field table_name_meta Name of database meta data table to which to store session meta data (defaults to `"sessions_meta"`).
279 | -- @field max_packet_size The upper limit for the reply packets sent from the MySQL server (defaults to 1 MB).
280 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
281 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
282 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
283 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
284 | -- @field pool A custom name for the connection pool being used.
285 | -- @field pool_size The size of the connection pool.
286 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
287 | -- @field ssl Enable SSL (defaults to `false`).
288 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
289 | -- @table configuration
290 |
291 |
292 | ---
293 | -- Constructors
294 | -- @section constructors
295 |
296 |
297 | ---
298 | -- Create a MySQL / MariaDB storage.
299 | --
300 | -- This creates a new MySQL / MariaDB storage instance.
301 | --
302 | -- @function module.new
303 | -- @tparam[opt] table configuration mysql/mariadb storage @{configuration}
304 | -- @treturn table mysql/mariadb storage instance
305 | function storage.new(configuration)
306 | local host = configuration and configuration.host or DEFAULT_HOST
307 | local port = configuration and configuration.port or DEFAULT_PORT
308 | local socket = configuration and configuration.socket
309 |
310 | local username = configuration and configuration.username
311 | local password = configuration and configuration.password
312 | local charset = configuration and configuration.charset or DEFAULT_CHARSET
313 | local database = configuration and configuration.database
314 | local max_packet_size = configuration and configuration.max_packet_size
315 |
316 | local table_name = configuration and configuration.table or DEFAULT_TABLE
317 | local table_name_meta = configuration and configuration.table_meta
318 |
319 | local connect_timeout = configuration and configuration.connect_timeout
320 | local send_timeout = configuration and configuration.send_timeout
321 | local read_timeout = configuration and configuration.read_timeout
322 | local keepalive_timeout = configuration and configuration.keepalive_timeout
323 |
324 | local pool = configuration and configuration.pool
325 | local pool_size = configuration and configuration.pool_size
326 | local backlog = configuration and configuration.backlog
327 | local ssl = configuration and configuration.ssl
328 | local ssl_verify = configuration and configuration.ssl_verify
329 |
330 | if socket then
331 | return setmetatable({
332 | table = table_name,
333 | table_meta = table_name_meta or (table_name .. "_meta"),
334 | connect_timeout = connect_timeout,
335 | send_timeout = send_timeout,
336 | read_timeout = read_timeout,
337 | keepalive_timeout = keepalive_timeout,
338 | options = {
339 | path = socket,
340 | user = username,
341 | password = password,
342 | charset = charset,
343 | database = database,
344 | max_packet_size = max_packet_size,
345 | pool = pool,
346 | pool_size = pool_size,
347 | backlog = backlog,
348 | ssl = ssl,
349 | ssl_verify = ssl_verify,
350 | }
351 | }, metatable)
352 | end
353 |
354 | return setmetatable({
355 | table = table_name,
356 | table_meta = table_name_meta or (table_name .. "_meta"),
357 | connect_timeout = connect_timeout,
358 | send_timeout = send_timeout,
359 | read_timeout = read_timeout,
360 | keepalive_timeout = keepalive_timeout,
361 | options = {
362 | host = host,
363 | port = port,
364 | user = username,
365 | password = password,
366 | charset = charset,
367 | database = database,
368 | max_packet_size = max_packet_size,
369 | pool = pool,
370 | pool_size = pool_size,
371 | backlog = backlog,
372 | ssl = ssl,
373 | ssl_verify = ssl_verify,
374 | }
375 | }, metatable)
376 | end
377 |
378 |
379 | return storage
380 |
--------------------------------------------------------------------------------
/lib/resty/session/postgres.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Postgres backend for session library.
3 | --
4 | -- @module resty.session.postgres
5 |
6 |
7 | ---
8 | -- Database
9 | -- @section database
10 |
11 |
12 | ---
13 | -- Sessions table.
14 | --
15 | -- Database table that stores session data.
16 | --
17 | -- @usage
18 | -- CREATE TABLE IF NOT EXISTS sessions (
19 | -- sid TEXT PRIMARY KEY,
20 | -- name TEXT,
21 | -- data TEXT,
22 | -- exp TIMESTAMP WITH TIME ZONE
23 | -- );
24 | -- CREATE INDEX ON sessions (exp);
25 | -- @table sessions
26 |
27 |
28 | ---
29 | -- Sessions metadata table.
30 | --
31 | -- This is only needed if you want to store session metadata.
32 | --
33 | -- @usage
34 | -- CREATE TABLE IF NOT EXISTS sessions_meta (
35 | -- aud TEXT,
36 | -- sub TEXT,
37 | -- sid TEXT REFERENCES sessions (sid) ON DELETE CASCADE ON UPDATE CASCADE,
38 | -- PRIMARY KEY (aud, sub, sid)
39 | -- );
40 | -- @table metadata
41 |
42 |
43 | local buffer = require "string.buffer"
44 | local pgmoon = require "pgmoon"
45 |
46 |
47 | local setmetatable = setmetatable
48 | local random = math.random
49 | local ipairs = ipairs
50 | local error = error
51 | local fmt = string.format
52 |
53 |
54 | local DEFAULT_HOST = "127.0.0.1"
55 | local DEFAULT_PORT = 5432
56 | local DEFAULT_TABLE = "sessions"
57 |
58 |
59 | local SET = "INSERT INTO %s (sid, name, data, exp) VALUES ('%s', '%s', '%s', TO_TIMESTAMP(%d) AT TIME ZONE 'UTC') ON CONFLICT (sid) DO UPDATE SET data = EXCLUDED.data, exp = EXCLUDED.exp"
60 | local SET_META_PREFIX = "INSERT INTO %s (aud, sub, sid) VALUES "
61 | local SET_META_VALUES = "('%s', '%s', '%s')"
62 | local SET_META_SUFFIX = " ON CONFLICT DO NOTHING"
63 | local GET_META = "SELECT sid, exp FROM %s JOIN %s USING (sid) WHERE aud = '%s' AND sub = '%s' AND exp >= TO_TIMESTAMP(%d)"
64 | local GET = "SELECT data FROM %s WHERE sid = '%s' AND exp >= TO_TIMESTAMP(%d) AT TIME ZONE 'UTC'"
65 | local EXPIRE = "UPDATE %s SET exp = TO_TIMESTAMP(%d) AT TIME ZONE 'UTC' WHERE sid = '%s' AND exp > TO_TIMESTAMP(%d) AT TIME ZONE 'UTC'"
66 | local DELETE = "DELETE FROM %s WHERE sid = '%s'"
67 | local CLEANUP = "DELETE FROM %s WHERE exp < TO_TIMESTAMP(%d)"
68 |
69 |
70 | local SQL = buffer.new()
71 | local STM_DELIM = ";\n"
72 | local VAL_DELIM = ", "
73 |
74 |
75 | local CLEANUP_PROBABILITY = 0.001 -- 1 / 1000
76 |
77 |
78 | local function exec(self, query)
79 | local pg = pgmoon.new(self.options)
80 |
81 | local connect_timeout = self.connect_timeout
82 | local send_timeout = self.send_timeout
83 | local read_timeout = self.read_timeout
84 | if connect_timeout or send_timeout or read_timeout then
85 | if pg.sock and pg.sock.settimeouts then
86 | pg.sock:settimeouts(connect_timeout, send_timeout, read_timeout)
87 | else
88 | pg:settimeout(connect_timeout)
89 | end
90 | end
91 |
92 | local ok, err = pg:connect()
93 | if not ok then
94 | return nil, err
95 | end
96 |
97 | ok, err = pg:query(query)
98 |
99 | if not pg:keepalive(self.keepalive_timeout) then
100 | pg:close()
101 | end
102 |
103 | return ok, err
104 | end
105 |
106 |
107 | ---
108 | -- Storage
109 | -- @section instance
110 |
111 |
112 | local metatable = {}
113 |
114 |
115 | metatable.__index = metatable
116 |
117 |
118 | function metatable.__newindex()
119 | error("attempt to update a read-only table", 2)
120 | end
121 |
122 |
123 | ---
124 | -- Store session data.
125 | --
126 | -- @function instance:set
127 | -- @tparam string name cookie name
128 | -- @tparam string key session key
129 | -- @tparam string value session value
130 | -- @tparam number ttl session ttl
131 | -- @tparam number current_time current time
132 | -- @tparam[opt] string old_key old session id
133 | -- @tparam string stale_ttl stale ttl
134 | -- @tparam[opt] table metadata table of metadata
135 | -- @tparam boolean remember whether storing persistent session or not
136 | -- @treturn true|nil ok
137 | -- @treturn string error message
138 | function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
139 | local table = self.table
140 | local exp = ttl + current_time
141 |
142 | if not metadata and not old_key then
143 | return exec(self, fmt(SET, table, key, name, value, exp))
144 | end
145 |
146 | SQL:reset():putf(SET, table, key, name, value, exp)
147 |
148 | if old_key then
149 | if remember then
150 | SQL:put(STM_DELIM):putf(DELETE, table, old_key)
151 | else
152 | local stale_exp = stale_ttl + current_time
153 | SQL:put(STM_DELIM):putf(EXPIRE, table, stale_exp, old_key, stale_exp)
154 | end
155 | end
156 |
157 | local table_meta = self.table_meta
158 | if metadata then
159 | local audiences = metadata.audiences
160 | local subjects = metadata.subjects
161 | local count = #audiences
162 |
163 | SQL:put(STM_DELIM):putf(SET_META_PREFIX, table_meta)
164 |
165 | for i = 1, count do
166 | if i > 1 then
167 | SQL:put(VAL_DELIM)
168 | end
169 | SQL:putf(SET_META_VALUES, audiences[i], subjects[i], key)
170 | end
171 |
172 | SQL:putf(SET_META_SUFFIX)
173 | end
174 |
175 | if random() < CLEANUP_PROBABILITY then
176 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time)
177 | end
178 |
179 | return exec(self, SQL:get())
180 | end
181 |
182 |
183 | ---
184 | -- Retrieve session data.
185 | --
186 | -- @function instance:get
187 | -- @tparam string name cookie name
188 | -- @tparam string key session key
189 | -- @treturn string|nil session data
190 | -- @treturn string error message
191 | function metatable:get(name, key, current_time) -- luacheck: ignore
192 | local res, err = exec(self, fmt(GET, self.table, key, current_time))
193 | if not res then
194 | return nil, err
195 | end
196 |
197 | local row = res[1]
198 | if not row then
199 | return nil
200 | end
201 |
202 | local data = row.data
203 | if not row.data then
204 | return nil
205 | end
206 |
207 | return data
208 | end
209 |
210 |
211 | ---
212 | -- Delete session data.
213 | --
214 | -- @function instance:delete
215 | -- @tparam string name cookie name
216 | -- @tparam string key session key
217 | -- @tparam[opt] table metadata session meta data
218 | -- @treturn boolean|nil session data
219 | -- @treturn string error message
220 | function metatable:delete(name, key, current_time, metadata) -- luacheck: ignore
221 | SQL:reset():putf(DELETE, self.table, key)
222 |
223 | if random() < CLEANUP_PROBABILITY then
224 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time)
225 | end
226 |
227 | return exec(self, SQL:get())
228 | end
229 |
230 |
231 | ---
232 | -- Read session metadata.
233 | --
234 | -- @function instance:read_metadata
235 | -- @tparam string name cookie name
236 | -- @tparam string audience session key
237 | -- @tparam string subject session key
238 | -- @tparam number current_time current time
239 | -- @treturn table|nil session metadata
240 | -- @treturn string error message
241 | function metatable:read_metadata(name, audience, subject, current_time) -- luacheck: ignore
242 | local t = exec(self, fmt(GET_META, self.table_meta, self.table, audience, subject, current_time))
243 | if not t then
244 | return nil, "not found"
245 | end
246 |
247 | local res = {}
248 | for _, v in ipairs(t) do
249 | local key = v.sid
250 | if key then
251 | res[key] = v.exp
252 | end
253 | end
254 | return res
255 | end
256 |
257 |
258 | local storage = {}
259 |
260 |
261 | ---
262 | -- Configuration
263 | -- @section configuration
264 |
265 |
266 | ---
267 | -- Postgres storage backend configuration
268 | -- @field host The host to connect (defaults to `"127.0.0.1"`).
269 | -- @field port The port to connect (defaults to `5432`).
270 | -- @field application Set the name of the connection as displayed in pg_stat_activity (defaults to `"pgmoon"`).
271 | -- @field username The database username to authenticate (defaults to `"postgres"`).
272 | -- @field password Password for authentication, may be required depending on server configuration.
273 | -- @field database The database name to connect.
274 | -- @field table_name Name of database table to which to store session data (can be `database schema` prefixed) (defaults to `"sessions"`).
275 | -- @field table_name_meta Name of database meta data table to which to store session meta data (can be `database schema` prefixed) (defaults to `"sessions_meta"`).
276 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
277 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
278 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
279 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
280 | -- @field pool A custom name for the connection pool being used.
281 | -- @field pool_size The size of the connection pool.
282 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
283 | -- @field ssl Enable SSL (defaults to `false`).
284 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
285 | -- @field ssl_required Abort the connection if the server does not support SSL connections (defaults to `nil`).
286 | -- @table configuration
287 |
288 |
289 | ---
290 | -- Constructors
291 | -- @section constructors
292 |
293 |
294 | ---
295 | -- Create a Postgres storage.
296 | --
297 | -- This creates a new Postgres storage instance.
298 | --
299 | -- @function module.new
300 | -- @tparam[opt] table configuration postgres storage @{configuration}
301 | -- @treturn table postgres storage instance
302 | function storage.new(configuration)
303 | local host = configuration and configuration.host or DEFAULT_HOST
304 | local port = configuration and configuration.port or DEFAULT_PORT
305 |
306 | local application = configuration and configuration.application
307 | local username = configuration and configuration.username
308 | local password = configuration and configuration.password
309 | local database = configuration and configuration.database
310 |
311 | local table_name = configuration and configuration.table or DEFAULT_TABLE
312 | local table_name_meta = configuration and configuration.table_meta
313 |
314 | local connect_timeout = configuration and configuration.connect_timeout
315 | local send_timeout = configuration and configuration.send_timeout
316 | local read_timeout = configuration and configuration.read_timeout
317 | local keepalive_timeout = configuration and configuration.keepalive_timeout
318 |
319 | local pool = configuration and configuration.pool
320 | local pool_size = configuration and configuration.pool_size
321 | local backlog = configuration and configuration.backlog
322 | local ssl = configuration and configuration.ssl
323 | local ssl_verify = configuration and configuration.ssl_verify
324 | local ssl_required = configuration and configuration.ssl_required
325 |
326 | return setmetatable({
327 | table = table_name,
328 | table_meta = table_name_meta or (table_name .. "_meta"),
329 | connect_timeout = connect_timeout,
330 | send_timeout = send_timeout,
331 | read_timeout = read_timeout,
332 | keepalive_timeout = keepalive_timeout,
333 | options = {
334 | host = host,
335 | port = port,
336 | application_name = application,
337 | user = username,
338 | password = password,
339 | database = database,
340 | socket_type = "nginx",
341 | pool = pool,
342 | pool_size = pool_size,
343 | backlog = backlog,
344 | ssl = ssl,
345 | ssl_verify = ssl_verify,
346 | ssl_required = ssl_required,
347 | }
348 | }, metatable)
349 | end
350 |
351 |
352 | return storage
353 |
--------------------------------------------------------------------------------
/lib/resty/session/redis.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Redis backend for session library
3 | --
4 | -- @module resty.session.redis
5 |
6 |
7 | local common = require "resty.session.redis.common"
8 | local redis = require "resty.redis"
9 |
10 |
11 | local setmetatable = setmetatable
12 | local error = error
13 | local null = ngx.null
14 |
15 |
16 | local DEFAULT_HOST = "127.0.0.1"
17 | local DEFAULT_PORT = 6379
18 |
19 |
20 | local SET = common.SET
21 | local GET = common.GET
22 | local UNLINK = common.UNLINK
23 | local READ_METADATA = common.READ_METADATA
24 |
25 |
26 | local function exec(self, func, ...)
27 | local red = redis:new()
28 |
29 | local connect_timeout = self.connect_timeout
30 | local send_timeout = self.send_timeout
31 | local read_timeout = self.read_timeout
32 | if connect_timeout or send_timeout or read_timeout then
33 | red:set_timeouts(connect_timeout, send_timeout, read_timeout)
34 | end
35 |
36 | local ok, err do
37 | local socket = self.socket
38 | if socket then
39 | ok, err = red:connect(socket, self.options)
40 | else
41 | ok, err = red:connect(self.host, self.port, self.options)
42 | end
43 | end
44 | if not ok then
45 | return nil, err
46 | end
47 |
48 | if red:get_reused_times() == 0 then
49 | local password = self.password
50 | if password then
51 | local username = self.username
52 | if username then
53 | ok, err = red:auth(username, password)
54 | else
55 | ok, err = red:auth(password)
56 | end
57 |
58 | if not ok then
59 | red:close()
60 | return nil, err
61 | end
62 | end
63 | end
64 |
65 | local database = self.database
66 | if database then
67 | ok, err = red:select(database)
68 | if not ok then
69 | return nil, err
70 | end
71 | end
72 |
73 | ok, err = func(self, red, ...)
74 | if err then
75 | red:close()
76 | return nil, err
77 | end
78 |
79 | if not red:set_keepalive(self.keepalive_timeout) then
80 | red:close()
81 | end
82 |
83 | if ok == null then
84 | ok = nil
85 | end
86 |
87 | return ok, err
88 | end
89 |
90 |
91 | ---
92 | -- Storage
93 | -- @section instance
94 |
95 |
96 | local metatable = {}
97 |
98 |
99 | metatable.__index = metatable
100 |
101 |
102 | function metatable.__newindex()
103 | error("attempt to update a read-only table", 2)
104 | end
105 |
106 |
107 | ---
108 | -- Store session data.
109 | --
110 | -- @function instance:set
111 | -- @tparam string name cookie name
112 | -- @tparam string key session key
113 | -- @tparam string value session value
114 | -- @tparam number ttl session ttl
115 | -- @tparam number current_time current time
116 | -- @tparam[opt] string old_key old session id
117 | -- @tparam string stale_ttl stale ttl
118 | -- @tparam[opt] table metadata table of metadata
119 | -- @tparam boolean remember whether storing persistent session or not
120 | -- @treturn true|nil ok
121 | -- @treturn string error message
122 | function metatable:set(...)
123 | return exec(self, SET, ...)
124 | end
125 |
126 |
127 | ---
128 | -- Retrieve session data.
129 | --
130 | -- @function instance:get
131 | -- @tparam string name cookie name
132 | -- @tparam string key session key
133 | -- @treturn string|nil session data
134 | -- @treturn string error message
135 | function metatable:get(...)
136 | return exec(self, GET, ...)
137 | end
138 |
139 |
140 | ---
141 | -- Delete session data.
142 | --
143 | -- @function instance:delete
144 | -- @tparam string name cookie name
145 | -- @tparam string key session key
146 | -- @tparam[opt] table metadata session meta data
147 | -- @treturn boolean|nil session data
148 | -- @treturn string error message
149 | function metatable:delete(...)
150 | return exec(self, UNLINK, ...)
151 | end
152 |
153 |
154 | ---
155 | -- Read session metadata.
156 | --
157 | -- @function instance:read_metadata
158 | -- @tparam string name cookie name
159 | -- @tparam string audience session key
160 | -- @tparam string subject session key
161 | -- @tparam number current_time current time
162 | -- @treturn table|nil session metadata
163 | -- @treturn string error message
164 | function metatable:read_metadata(...)
165 | return exec(self, READ_METADATA, ...)
166 | end
167 |
168 |
169 | local storage = {}
170 |
171 |
172 | ---
173 | -- Configuration
174 | -- @section configuration
175 |
176 |
177 | ---
178 | -- Redis storage backend configuration
179 | -- @field prefix Prefix for the keys stored in Redis.
180 | -- @field suffix Suffix for the keys stored in Redis.
181 | -- @field host The host to connect (defaults to `"127.0.0.1"`).
182 | -- @field port The port to connect (defaults to `6379`).
183 | -- @field socket The socket file to connect to (defaults to `nil`).
184 | -- @field username The database username to authenticate.
185 | -- @field password Password for authentication.
186 | -- @field database The database to connect.
187 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
188 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
189 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
190 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
191 | -- @field pool A custom name for the connection pool being used.
192 | -- @field pool_size The size of the connection pool.
193 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
194 | -- @field ssl Enable SSL (defaults to `false`).
195 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
196 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI).
197 | -- @table configuration
198 |
199 |
200 | ---
201 | -- Constructors
202 | -- @section constructors
203 |
204 |
205 | ---
206 | -- Create a Redis storage.
207 | --
208 | -- This creates a new Redis storage instance.
209 | --
210 | -- @function module.new
211 | -- @tparam[opt] table configuration redis storage @{configuration}
212 | -- @treturn table redis storage instance
213 | function storage.new(configuration)
214 | local prefix = configuration and configuration.prefix
215 | local suffix = configuration and configuration.suffix
216 |
217 | local host = configuration and configuration.host or DEFAULT_HOST
218 | local port = configuration and configuration.port or DEFAULT_PORT
219 | local socket = configuration and configuration.socket
220 |
221 | local username = configuration and configuration.username
222 | local password = configuration and configuration.password
223 | local database = configuration and configuration.database
224 |
225 | local connect_timeout = configuration and configuration.connect_timeout
226 | local send_timeout = configuration and configuration.send_timeout
227 | local read_timeout = configuration and configuration.read_timeout
228 | local keepalive_timeout = configuration and configuration.keepalive_timeout
229 |
230 | local pool = configuration and configuration.pool
231 | local pool_size = configuration and configuration.pool_size
232 | local backlog = configuration and configuration.backlog
233 | local ssl = configuration and configuration.ssl
234 | local ssl_verify = configuration and configuration.ssl_verify
235 | local server_name = configuration and configuration.server_name
236 |
237 | if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then
238 | return setmetatable({
239 | prefix = prefix,
240 | suffix = suffix,
241 | host = host,
242 | port = port,
243 | socket = socket,
244 | username = username,
245 | password = password,
246 | database = database,
247 | connect_timeout = connect_timeout,
248 | send_timeout = send_timeout,
249 | read_timeout = read_timeout,
250 | keepalive_timeout = keepalive_timeout,
251 | options = {
252 | ssl = ssl,
253 | ssl_verify = ssl_verify,
254 | server_name = server_name,
255 | pool = pool,
256 | pool_size = pool_size,
257 | backlog = backlog,
258 | }
259 | }, metatable)
260 | end
261 |
262 | return setmetatable({
263 | prefix = prefix,
264 | suffix = suffix,
265 | host = host,
266 | port = port,
267 | socket = socket,
268 | username = username,
269 | password = password,
270 | database = database,
271 | connect_timeout = connect_timeout,
272 | send_timeout = send_timeout,
273 | read_timeout = read_timeout,
274 | keepalive_timeout = keepalive_timeout,
275 | }, metatable)
276 | end
277 |
278 |
279 | return storage
280 |
--------------------------------------------------------------------------------
/lib/resty/session/redis/cluster.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Redis Cluster backend for session library
3 | --
4 | -- @module resty.session.redis.cluster
5 |
6 |
7 | local common = require "resty.session.redis.common"
8 | local redis = require "resty.rediscluster"
9 |
10 |
11 | local setmetatable = setmetatable
12 | local error = error
13 | local null = ngx.null
14 |
15 |
16 | local SET = common.SET
17 | local GET = common.GET
18 | local UNLINK = common.UNLINK
19 | local READ_METADATA = common.READ_METADATA
20 |
21 |
22 | local function exec(self, func, ...)
23 | local red, err = redis:new(self.options)
24 | if err then
25 | return nil, err
26 | end
27 |
28 | local ok, err = func(self, red, ...)
29 | if err then
30 | red:close()
31 | return nil, err
32 | end
33 |
34 | if ok == null then
35 | ok = nil
36 | end
37 |
38 | return ok, err
39 | end
40 |
41 |
42 | ---
43 | -- Storage
44 | -- @section instance
45 |
46 |
47 | local metatable = {}
48 |
49 |
50 | metatable.__index = metatable
51 |
52 |
53 | function metatable.__newindex()
54 | error("attempt to update a read-only table", 2)
55 | end
56 |
57 |
58 | ---
59 | -- Store session data.
60 | --
61 | -- @function instance:set
62 | -- @tparam string name cookie name
63 | -- @tparam string key session key
64 | -- @tparam string value session value
65 | -- @tparam number ttl session ttl
66 | -- @tparam number current_time current time
67 | -- @tparam[opt] string old_key old session id
68 | -- @tparam string stale_ttl stale ttl
69 | -- @tparam[opt] table metadata table of metadata
70 | -- @tparam boolean remember whether storing persistent session or not
71 | -- @treturn true|nil ok
72 | -- @treturn string error message
73 | function metatable:set(...)
74 | return exec(self, SET, ...)
75 | end
76 |
77 |
78 | ---
79 | -- Retrieve session data.
80 | --
81 | -- @function instance:get
82 | -- @tparam string name cookie name
83 | -- @tparam string key session key
84 | -- @treturn string|nil session data
85 | -- @treturn string error message
86 | function metatable:get(...)
87 | return exec(self, GET, ...)
88 | end
89 |
90 |
91 | ---
92 | -- Delete session data.
93 | --
94 | -- @function instance:delete
95 | -- @tparam string name cookie name
96 | -- @tparam string key session key
97 | -- @tparam[opt] table metadata session meta data
98 | -- @treturn boolean|nil session data
99 | -- @treturn string error message
100 | function metatable:delete(...)
101 | return exec(self, UNLINK, ...)
102 | end
103 |
104 |
105 | ---
106 | -- Read session metadata.
107 | --
108 | -- @function instance:read_metadata
109 | -- @tparam string name cookie name
110 | -- @tparam string audience session key
111 | -- @tparam string subject session key
112 | -- @tparam number current_time current time
113 | -- @treturn table|nil session metadata
114 | -- @treturn string error message
115 | function metatable:read_metadata(...)
116 | return exec(self, READ_METADATA, ...)
117 | end
118 |
119 |
120 | local storage = {}
121 |
122 |
123 | ---
124 | -- Configuration
125 | -- @section configuration
126 |
127 |
128 | ---
129 | -- Redis Cluster storage backend configuration
130 | -- @field prefix Prefix for the keys stored in redis.
131 | -- @field suffix Suffix for the keys stored in redis.
132 | -- @field name Redis cluster name.
133 | -- @field nodes Redis cluster nodes.
134 | -- @field lock_zone Shared dictionary name for locks.
135 | -- @field lock_prefix Shared dictionary name prefix for lock.
136 | -- @field max_redirections Maximum retry attempts for redirection.
137 | -- @field max_connection_attempts Maximum retry attempts for connection.
138 | -- @field max_connection_timeout Maximum connection timeout in total among the retries.
139 | -- @field username The database username to authenticate.
140 | -- @field password Password for authentication.
141 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
142 | -- @field send_timeout controls The default timeout value used in TCP/unix-domain socket object's `send` method.
143 | -- @field read_timeout controls The default timeout value used in TCP/unix-domain socket object's `receive` method.
144 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
145 | -- @field pool A custom name for the connection pool being used.
146 | -- @field pool_size The size of the connection pool.
147 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
148 | -- @field ssl Enable SSL (defaults to `false`).
149 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
150 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI).
151 | -- @table configuration
152 |
153 |
154 | ---
155 | -- Cluster Nodes
156 | --
157 | -- An array of cluster nodes.
158 | --
159 | -- @table nodes
160 |
161 |
162 | ---
163 | -- Cluster Node
164 | -- @field ip The IP address to connect (defaults to `"127.0.0.1"`).
165 | -- @field port The port to connect (defaults to `6379`).
166 | -- @table node
167 |
168 |
169 | ---
170 | -- Constructors
171 | -- @section constructors
172 |
173 |
174 | ---
175 | -- Create a Redis Cluster storage.
176 | --
177 | -- This creates a new Redis Cluster storage instance.
178 | --
179 | -- @function module.new
180 | -- @tparam[opt] table configuration redis cluster storage @{configuration}
181 | -- @treturn table redis cluster storage instance
182 | function storage.new(configuration)
183 | local prefix = configuration and configuration.prefix
184 | local suffix = configuration and configuration.suffix
185 |
186 | local name = configuration and configuration.name
187 | local nodes = configuration and configuration.nodes
188 |
189 | local lock_zone = configuration and configuration.lock_zone
190 | local lock_prefix = configuration and configuration.lock_prefix
191 | local max_redirections = configuration and configuration.max_redirections
192 | local max_connection_attempts = configuration and configuration.max_connection_attempts
193 | local max_connection_timeout = configuration and configuration.max_connection_timeout
194 |
195 | local username = configuration and configuration.username
196 | local password = configuration and configuration.password
197 |
198 | local connect_timeout = configuration and configuration.connect_timeout
199 | local send_timeout = configuration and configuration.send_timeout
200 | local read_timeout = configuration and configuration.read_timeout
201 | local keepalive_timeout = configuration and configuration.keepalive_timeout
202 |
203 | local pool = configuration and configuration.pool
204 | local pool_size = configuration and configuration.pool_size
205 | local backlog = configuration and configuration.backlog
206 | local ssl = configuration and configuration.ssl
207 | local ssl_verify = configuration and configuration.ssl_verify
208 | local server_name = configuration and configuration.server_name
209 |
210 | local auth
211 | if password then
212 | if username then
213 | auth = username .. " " .. password
214 | else
215 | auth = password
216 | end
217 | end
218 |
219 | if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then
220 | return setmetatable({
221 | prefix = prefix,
222 | suffix = suffix,
223 | options = {
224 | name = name,
225 | dict_name = lock_zone,
226 | refresh_lock_key = lock_prefix,
227 | serv_list = nodes,
228 | connect_timeout = connect_timeout,
229 | send_timeout = send_timeout,
230 | read_timeout = read_timeout,
231 | keepalive_timeout = keepalive_timeout,
232 | keepalive_cons = pool_size,
233 | max_redirection = max_redirections,
234 | max_connection_attempts = max_connection_attempts,
235 | max_connection_timeout = max_connection_timeout,
236 | auth = auth,
237 | connect_opts = {
238 | ssl = ssl,
239 | ssl_verify = ssl_verify,
240 | server_name = server_name,
241 | pool = pool,
242 | pool_size = pool_size,
243 | backlog = backlog,
244 | },
245 | },
246 | }, metatable)
247 | end
248 |
249 | return setmetatable({
250 | prefix = prefix,
251 | suffix = suffix,
252 | options = {
253 | name = name,
254 | dict_name = lock_zone,
255 | refresh_lock_key = lock_prefix,
256 | serv_list = nodes,
257 | connect_timeout = connect_timeout,
258 | send_timeout = send_timeout,
259 | read_timeout = read_timeout,
260 | keepalive_timeout = keepalive_timeout,
261 | keepalive_cons = pool_size,
262 | max_redirection = max_redirections,
263 | max_connection_attempts = max_connection_attempts,
264 | max_connection_timeout = max_connection_timeout,
265 | auth = auth,
266 | },
267 | }, metatable)
268 | end
269 |
270 |
271 | return storage
272 |
--------------------------------------------------------------------------------
/lib/resty/session/redis/common.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Common Redis functions shared between Redis,
3 | -- Redis Cluster and Redis Sentinel implementations.
4 | --
5 | -- @module resty.session.redis.common
6 |
7 |
8 | local utils = require "resty.session.utils"
9 |
10 |
11 | local get_name = utils.get_name
12 | local ipairs = ipairs
13 |
14 |
15 | ---
16 | -- Store session data.
17 | --
18 | -- @function module.SET
19 | -- @tparam table storage the storage
20 | -- @tparam table red the redis instance
21 | -- @tparam string name the cookie name
22 | -- @tparam string key session key
23 | -- @tparam string value session value
24 | -- @tparam number ttl session ttl
25 | -- @tparam number current_time current time
26 | -- @tparam[opt] string old_key old session id
27 | -- @tparam string stale_ttl stale ttl
28 | -- @tparam[opt] table metadata table of metadata
29 | -- @tparam table remember whether storing persistent session or not
30 | -- @treturn true|nil ok
31 | -- @treturn string error message
32 | local function SET(storage, red, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
33 | if not metadata and not old_key then
34 | return red:set(get_name(storage, name, key), value, "EX", ttl)
35 | end
36 |
37 | local old_name
38 | local old_ttl
39 | if old_key then
40 | old_name = get_name(storage, name, old_key)
41 | if not remember then
42 | -- redis < 7.0
43 | old_ttl = red:ttl(old_name)
44 | end
45 | end
46 |
47 | red:init_pipeline()
48 | red:set(get_name(storage, name, key), value, "EX", ttl)
49 |
50 | -- redis < 7.0
51 | if old_name then
52 | if remember then
53 | red:unlink(old_name)
54 | elseif not old_ttl or old_ttl > stale_ttl then
55 | red:expire(old_name, stale_ttl)
56 | end
57 | end
58 |
59 | -- redis >= 7.0
60 | --if old_key then
61 | -- if remember then
62 | -- red:unlink(get_name(storage, name, old_key))
63 | -- else
64 | -- red:expire(get_name(storage, name, old_key), stale_ttl, "LT")
65 | -- end
66 | --end
67 |
68 | if metadata then
69 | local audiences = metadata.audiences
70 | local subjects = metadata.subjects
71 | local score = current_time - 1
72 | local new_score = current_time + ttl
73 | local count = #audiences
74 | for i = 1, count do
75 | local meta_key = get_name(storage, name, audiences[i], subjects[i])
76 | red:zremrangebyscore(meta_key, 0, score)
77 | red:zadd(meta_key, new_score, key)
78 | if old_key then
79 | red:zrem(meta_key, old_key)
80 | end
81 | red:expire(meta_key, ttl)
82 | end
83 | end
84 |
85 | return red:commit_pipeline()
86 | end
87 |
88 |
89 | ---
90 | -- Retrieve session data.
91 | --
92 | -- @function module.GET
93 | -- @tparam table storage the storage
94 | -- @tparam table red the redis instance
95 | -- @tparam string name cookie name
96 | -- @tparam string key session key
97 | -- @treturn string|nil session data
98 | -- @treturn string error message
99 | local function GET(storage, red, name, key)
100 | return red:get(get_name(storage, name, key))
101 | end
102 |
103 |
104 | ---
105 | -- Delete session data.
106 | --
107 | -- @function module.UNLINK
108 | -- @tparam table storage the storage
109 | -- @tparam table red the redis instance
110 | -- @tparam string name cookie name
111 | -- @tparam string key session key
112 | -- @tparam number current_time current time
113 | -- @tparam[opt] table metadata session meta data
114 | -- @treturn boolean|nil session data
115 | -- @treturn string error message
116 | local function UNLINK(storage, red, name, key, current_time, metadata)
117 | if not metadata then
118 | return red:unlink(get_name(storage, name, key))
119 | end
120 |
121 | red:init_pipeline()
122 | red:unlink(get_name(storage, name, key))
123 | local audiences = metadata.audiences
124 | local subjects = metadata.subjects
125 | local score = current_time - 1
126 | local count = #audiences
127 | for i = 1, count do
128 | local meta_key = get_name(storage, name, audiences[i], subjects[i])
129 | red:zremrangebyscore(meta_key, 0, score)
130 | red:zrem(meta_key, key)
131 | end
132 |
133 | return red:commit_pipeline()
134 | end
135 |
136 |
137 | ---
138 | -- Read session metadata.
139 | --
140 | -- @function module.READ_METADATA
141 | -- @tparam table storage the storage
142 | -- @tparam table red the redis instance
143 | -- @tparam string name cookie name
144 | -- @tparam string audience session key
145 | -- @tparam string subject session key
146 | -- @tparam number current_time current time
147 | -- @treturn table|nil session metadata
148 | -- @treturn string error message
149 | local function READ_METADATA(storage, red, name, audience, subject, current_time)
150 | local meta_key = get_name(storage, name, audience, subject)
151 | local res, err = red:zrange(meta_key, current_time, "+inf", "BYSCORE", "WITHSCORES")
152 | if not res then
153 | return nil, err
154 | end
155 |
156 | local sessions = {}
157 | for i, v in ipairs(res) do
158 | if i % 2 ~= 0 then
159 | sessions[v] = res[i + 1]
160 | end
161 | end
162 | return sessions
163 | end
164 |
165 |
166 | return {
167 | SET = SET,
168 | GET = GET,
169 | UNLINK = UNLINK,
170 | READ_METADATA = READ_METADATA,
171 | }
172 |
--------------------------------------------------------------------------------
/lib/resty/session/redis/sentinel.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Redis Sentinel backend for session library
3 | --
4 | -- @module resty.session.redis.sentinel
5 |
6 |
7 | local common = require "resty.session.redis.common"
8 | local redis = require "resty.redis.connector"
9 |
10 |
11 | local setmetatable = setmetatable
12 | local error = error
13 | local null = ngx.null
14 |
15 |
16 | local SET = common.SET
17 | local GET = common.GET
18 | local UNLINK = common.UNLINK
19 | local READ_METADATA = common.READ_METADATA
20 |
21 |
22 | local function exec(self, func, ...)
23 | local red, err = self.connector:connect()
24 | if not red then
25 | return nil, err
26 | end
27 |
28 | local ok, err = func(self, red, ...)
29 | if err then
30 | red:close()
31 | return nil, err
32 | end
33 |
34 | if ok == null then
35 | ok = nil
36 | end
37 |
38 | self.connector:set_keepalive(red)
39 |
40 | return ok, err
41 | end
42 |
43 |
44 | ---
45 | -- Storage
46 | -- @section instance
47 |
48 |
49 | local metatable = {}
50 |
51 |
52 | metatable.__index = metatable
53 |
54 |
55 | function metatable.__newindex()
56 | error("attempt to update a read-only table", 2)
57 | end
58 |
59 |
60 | ---
61 | -- Store session data.
62 | --
63 | -- @function instance:set
64 | -- @tparam string name cookie name
65 | -- @tparam string key session key
66 | -- @tparam string value session value
67 | -- @tparam number ttl session ttl
68 | -- @tparam number current_time current time
69 | -- @tparam[opt] string old_key old session id
70 | -- @tparam string stale_ttl stale ttl
71 | -- @tparam[opt] table metadata table of metadata
72 | -- @tparam table remember whether storing persistent session or not
73 | -- @treturn true|nil ok
74 | -- @treturn string error message
75 | function metatable:set(...)
76 | return exec(self, SET, ...)
77 | end
78 |
79 |
80 | ---
81 | -- Retrieve session data.
82 | --
83 | -- @function instance:get
84 | -- @tparam string name cookie name
85 | -- @tparam string key session key
86 | -- @treturn string|nil session data
87 | -- @treturn string error message
88 | function metatable:get(...)
89 | return exec(self, GET, ...)
90 | end
91 |
92 |
93 | ---
94 | -- Delete session data.
95 | --
96 | -- @function instance:delete
97 | -- @tparam string name cookie name
98 | -- @tparam string key session key
99 | -- @tparam[opt] table metadata session meta data
100 | -- @treturn boolean|nil session data
101 | -- @treturn string error message
102 | function metatable:delete(...)
103 | return exec(self, UNLINK, ...)
104 | end
105 |
106 |
107 | ---
108 | -- Read session metadata.
109 | --
110 | -- @function instance:read_metadata
111 | -- @tparam string name cookie name
112 | -- @tparam string audience session key
113 | -- @tparam string subject session key
114 | -- @tparam number current_time current time
115 | -- @treturn table|nil session metadata
116 | -- @treturn string error message
117 | function metatable:read_metadata(...)
118 | return exec(self, READ_METADATA, ...)
119 | end
120 |
121 |
122 | local storage = {}
123 |
124 |
125 | ---
126 | -- Configuration
127 | -- @section configuration
128 |
129 |
130 | ---
131 | -- Redis Sentinel storage backend configuration
132 | -- @field prefix Prefix for the keys stored in redis.
133 | -- @field suffix Suffix for the keys stored in redis.
134 | -- @field master Name of master.
135 | -- @field role `"master"` or `"slave"`.
136 | -- @field sentinels Redis Sentinels.
137 | -- @field sentinel_username Optional sentinel username.
138 | -- @field sentinel_password Optional sentinel password.
139 | -- @field username The database username to authenticate.
140 | -- @field password Password for authentication.
141 | -- @field database The database to connect.
142 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
143 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
144 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
145 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
146 | -- @field pool A custom name for the connection pool being used.
147 | -- @field pool_size The size of the connection pool.
148 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
149 | -- @field ssl Enable SSK (defaults to `false`).
150 | -- @field ssl_verify Verify server certificate (defaults to `nil`).
151 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI).
152 | -- @table configuration
153 |
154 |
155 | ---
156 | -- Sentinels
157 | --
158 | -- An array of sentinels.
159 | --
160 | -- @table sentinels
161 |
162 |
163 | ---
164 | -- Sentinel
165 | -- @field host The host to connect.
166 | -- @field port The port to connect.
167 | -- @table sentinel
168 |
169 |
170 | ---
171 | -- Constructors
172 | -- @section constructors
173 |
174 |
175 | ---
176 | -- Create a Redis Sentinel storage.
177 | --
178 | -- This creates a new Redis Sentinel storage instance.
179 | --
180 | -- @function module.new
181 | -- @tparam[opt] table configuration redis sentinel storage @{configuration}
182 | -- @treturn table redis sentinel storage instance
183 | function storage.new(configuration)
184 | local prefix = configuration and configuration.prefix
185 | local suffix = configuration and configuration.suffix
186 |
187 | local master = configuration and configuration.master
188 | local role = configuration and configuration.role
189 | local sentinels = configuration and configuration.sentinels
190 | local sentinel_username = configuration and configuration.sentinel_username
191 | local sentinel_password = configuration and configuration.sentinel_password
192 |
193 | local username = configuration and configuration.username
194 | local password = configuration and configuration.password
195 | local database = configuration and configuration.database
196 |
197 | local connect_timeout = configuration and configuration.connect_timeout
198 | local send_timeout = configuration and configuration.send_timeout
199 | local read_timeout = configuration and configuration.read_timeout
200 | local keepalive_timeout = configuration and configuration.keepalive_timeout
201 |
202 | local pool = configuration and configuration.pool
203 | local pool_size = configuration and configuration.pool_size
204 | local backlog = configuration and configuration.backlog
205 | local ssl = configuration and configuration.ssl
206 | local ssl_verify = configuration and configuration.ssl_verify
207 | local server_name = configuration and configuration.server_name
208 |
209 | local connector
210 | if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then
211 | connector = redis.new({
212 | master_name = master,
213 | role = role,
214 | sentinels = sentinels,
215 | sentinel_username = sentinel_username,
216 | sentinel_password = sentinel_password,
217 | username = username,
218 | password = password,
219 | db = database,
220 | connect_timeout = connect_timeout,
221 | send_timeout = send_timeout,
222 | read_timeout = read_timeout,
223 | keepalive_timeout = keepalive_timeout,
224 | keepalive_poolsize = pool_size,
225 | connection_options = {
226 | ssl = ssl,
227 | ssl_verify = ssl_verify,
228 | server_name = server_name,
229 | pool = pool,
230 | pool_size = pool_size,
231 | backlog = backlog,
232 | }
233 | })
234 | else
235 | connector = redis.new({
236 | master_name = master,
237 | role = role,
238 | sentinels = sentinels,
239 | sentinel_username = sentinel_username,
240 | sentinel_password = sentinel_password,
241 | username = username,
242 | password = password,
243 | db = database,
244 | connect_timeout = connect_timeout,
245 | send_timeout = send_timeout,
246 | read_timeout = read_timeout,
247 | keepalive_timeout = keepalive_timeout,
248 | keepalive_poolsize = pool_size,
249 | })
250 | end
251 |
252 | return setmetatable({
253 | prefix = prefix,
254 | suffix = suffix,
255 | connector = connector,
256 | }, metatable)
257 | end
258 |
259 |
260 | return storage
261 |
--------------------------------------------------------------------------------
/lib/resty/session/shm.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Shared Memory (SHM) backend for session library
3 | --
4 | -- @module resty.session.shm
5 |
6 |
7 | local utils = require "resty.session.utils"
8 |
9 |
10 | local meta_get_value = utils.meta_get_value
11 | local meta_get_next = utils.meta_get_next
12 | local table_new = utils.table_new
13 | local get_name = utils.get_name
14 | local errmsg = utils.errmsg
15 |
16 |
17 | local setmetatable = setmetatable
18 | local shared = ngx.shared
19 | local random = math.random
20 | local assert = assert
21 | local error = error
22 | local pairs = pairs
23 | local max = math.max
24 | local log = ngx.log
25 |
26 |
27 | local WARN = ngx.WARN
28 |
29 |
30 | local DEFAULT_ZONE = "sessions"
31 | local CLEANUP_PROBABILITY = 0.1 -- 1 / 10
32 |
33 |
34 | local function get_and_clean_metadata(dict, meta_key, current_time)
35 | local size = dict:llen(meta_key)
36 | if not size or size == 0 then
37 | return
38 | end
39 |
40 | local max_expiry = current_time
41 | local sessions = table_new(0, size)
42 |
43 | for _ = 1, size do
44 | local meta_value, err = dict:lpop(meta_key)
45 | if not meta_value then
46 | log(WARN, "[session] ", errmsg(err, "failed read meta value"))
47 | break
48 | end
49 |
50 | local key, err, exp = meta_get_next(meta_value, 1)
51 | if err then
52 | return nil, err
53 | end
54 |
55 | if exp and exp > current_time then
56 | sessions[key] = exp
57 | max_expiry = max(max_expiry, exp)
58 |
59 | else
60 | sessions[key] = nil
61 | end
62 | end
63 |
64 | for key, exp in pairs(sessions) do
65 | local meta_value = meta_get_value(key, exp)
66 | local ok, err = dict:rpush(meta_key, meta_value)
67 | if not ok then
68 | log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
69 | end
70 | end
71 |
72 | local exp = max_expiry - current_time
73 | if exp > 0 then
74 | local ok, err = dict:expire(meta_key, max_expiry - current_time)
75 | if not ok and err ~= "not found" then
76 | log(WARN, "[session] ", errmsg(err, "failed to touch metadata"))
77 | end
78 |
79 | else
80 | dict:delete(meta_key)
81 | end
82 |
83 | return sessions
84 | end
85 |
86 |
87 | local function cleanup(dict, meta_key, current_time)
88 | get_and_clean_metadata(dict, meta_key, current_time)
89 | end
90 |
91 |
92 | local function read_metadata(self, meta_key, current_time)
93 | return get_and_clean_metadata(self.dict, meta_key, current_time)
94 | end
95 |
96 | ---
97 | -- Storage
98 | -- @section instance
99 |
100 |
101 | local metatable = {}
102 |
103 |
104 | metatable.__index = metatable
105 |
106 |
107 | function metatable.__newindex()
108 | error("attempt to update a read-only table", 2)
109 | end
110 |
111 |
112 | ---
113 | -- Store session data.
114 | --
115 | -- @function instance:set
116 | -- @tparam string name cookie name
117 | -- @tparam string key session key
118 | -- @tparam string value session value
119 | -- @tparam number ttl session ttl
120 | -- @tparam number current_time current time
121 | -- @tparam[opt] string old_key old session id
122 | -- @tparam string stale_ttl stale ttl
123 | -- @tparam[opt] table metadata table of metadata
124 | -- @tparam boolean remember whether storing persistent session or not
125 | -- @treturn true|nil ok
126 | -- @treturn string error message
127 | function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
128 | local dict = self.dict
129 | if not metadata and not old_key then
130 | local ok, err = dict:set(get_name(self, name, key), value, ttl)
131 | if not ok then
132 | return nil, err
133 | end
134 |
135 | return true
136 | end
137 |
138 | local old_name, old_ttl
139 | if old_key then
140 | old_name = get_name(self, name, old_key)
141 | if not remember then
142 | old_ttl = dict:ttl(old_name)
143 | end
144 | end
145 |
146 | local ok, err = dict:set(get_name(self, name, key), value, ttl)
147 | if not ok then
148 | return nil, err
149 | end
150 |
151 | if old_name then
152 | if remember then
153 | dict:delete(old_name)
154 |
155 | elseif (not old_ttl or old_ttl > stale_ttl) then
156 | local ok, err = dict:expire(old_name, stale_ttl)
157 | if not ok then
158 | log(WARN, "[session] ", errmsg(err, "failed to touch old session"))
159 | end
160 | end
161 | end
162 |
163 | if not metadata then
164 | return true
165 | end
166 |
167 | local audiences = metadata.audiences
168 | local subjects = metadata.subjects
169 | local count = #audiences
170 | for i = 1, count do
171 | local meta_key = get_name(self, name, audiences[i], subjects[i])
172 | local meta_value = meta_get_value(key, current_time + ttl)
173 |
174 | local ok, err = dict:rpush(meta_key, meta_value)
175 | if not ok then
176 | log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
177 | end
178 |
179 | if old_key then
180 | meta_value = meta_get_value(old_key, 0)
181 | local ok, err = dict:rpush(meta_key, meta_value)
182 | if not ok then
183 | log(WARN, "[session] ", errmsg(err, "failed to update old metadata"))
184 | end
185 | end
186 |
187 | -- no need to clean up every time we write
188 | -- it is just beneficial when a key is used a lot
189 | if random() < CLEANUP_PROBABILITY then
190 | cleanup(dict, meta_key, current_time)
191 | end
192 | end
193 |
194 | return true
195 | end
196 |
197 |
198 | ---
199 | -- Retrieve session data.
200 | --
201 | -- @function instance:get
202 | -- @tparam string name cookie name
203 | -- @tparam string key session key
204 | -- @treturn string|nil session data
205 | -- @treturn string error message
206 | function metatable:get(name, key)
207 | local value, err = self.dict:get(get_name(self, name, key))
208 | if not value then
209 | return nil, err
210 | end
211 |
212 | return value
213 | end
214 |
215 |
216 | ---
217 | -- Delete session data.
218 | --
219 | -- @function instance:delete
220 | -- @tparam string name cookie name
221 | -- @tparam string key session key
222 | -- @tparam[opt] table metadata session meta data
223 | -- @treturn boolean|nil session data
224 | -- @treturn string error message
225 | function metatable:delete(name, key, current_time, metadata)
226 | local dict = self.dict
227 |
228 | dict:delete(get_name(self, name, key))
229 |
230 | if not metadata then
231 | return true
232 | end
233 |
234 | local audiences = metadata.audiences
235 | local subjects = metadata.subjects
236 | local count = #audiences
237 | for i = 1, count do
238 | local meta_key = get_name(self, name, audiences[i], subjects[i])
239 | local meta_value = meta_get_value(key, 0)
240 |
241 | local ok, err = dict:rpush(meta_key, meta_value)
242 | if not ok then
243 | if not ok then
244 | log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
245 | end
246 | end
247 |
248 | cleanup(dict, meta_key, current_time)
249 | end
250 |
251 | return true
252 | end
253 |
254 |
255 | ---
256 | -- Read session metadata.
257 | --
258 | -- @function instance:read_metadata
259 | -- @tparam string name cookie name
260 | -- @tparam string audience session key
261 | -- @tparam string subject session key
262 | -- @tparam number current_time current time
263 | -- @treturn table|nil session metadata
264 | -- @treturn string error message
265 | function metatable:read_metadata(name, audience, subject, current_time)
266 | local meta_key = get_name(self, name, audience, subject)
267 | return read_metadata(self, meta_key, current_time)
268 | end
269 |
270 |
271 | local storage = {}
272 |
273 |
274 | ---
275 | -- Configuration
276 | -- @section configuration
277 |
278 |
279 | ---
280 | -- Shared memory storage backend configuration
281 | -- @field prefix Prefix for the keys stored in SHM.
282 | -- @field suffix Suffix for the keys stored in SHM.
283 | -- @field zone A name of shared memory zone (defaults to `sessions`).
284 | -- @table configuration
285 |
286 |
287 | ---
288 | -- Constructors
289 | -- @section constructors
290 |
291 |
292 | ---
293 | -- Create a SHM storage.
294 | --
295 | -- This creates a new shared memory storage instance.
296 | --
297 | -- @function module.new
298 | -- @tparam[opt] table configuration shm storage @{configuration}
299 | -- @treturn table shm storage instance
300 | function storage.new(configuration)
301 | local prefix = configuration and configuration.prefix
302 | local suffix = configuration and configuration.suffix
303 | local zone = configuration and configuration.zone or DEFAULT_ZONE
304 |
305 | local dict = assert(shared[zone], "lua_shared_dict " .. zone .. " is missing")
306 |
307 | return setmetatable({
308 | prefix = prefix,
309 | suffix = suffix,
310 | dict = dict,
311 | }, metatable)
312 | end
313 |
314 |
315 | return storage
316 |
--------------------------------------------------------------------------------
/lua-resty-session-4.1.1-1.rockspec:
--------------------------------------------------------------------------------
1 | package = "lua-resty-session"
2 | version = "4.1.1-1"
3 | source = {
4 | url = "git+https://github.com/bungle/lua-resty-session.git",
5 | tag = "v4.1.1",
6 | }
7 | description = {
8 | summary = "Session Library for OpenResty - Flexible and Secure",
9 | detailed = "lua-resty-session is a secure, and flexible session library for OpenResty.",
10 | homepage = "https://github.com/bungle/lua-resty-session",
11 | maintainer = "Aapo Talvensaari , Samuele Illuminati ",
12 | license = "BSD",
13 | }
14 | dependencies = {
15 | "lua >= 5.1",
16 | "lua-ffi-zlib >= 0.5",
17 | "lua-resty-openssl >= 1.5.0",
18 | }
19 | build = {
20 | type = "builtin",
21 | modules = {
22 | ["resty.session"] = "lib/resty/session.lua",
23 | ["resty.session.dshm"] = "lib/resty/session/dshm.lua",
24 | ["resty.session.file"] = "lib/resty/session/file.lua",
25 | ["resty.session.file.thread"] = "lib/resty/session/file/thread.lua",
26 | ["resty.session.file.utils"] = "lib/resty/session/file/utils.lua",
27 | ["resty.session.memcached"] = "lib/resty/session/memcached.lua",
28 | ["resty.session.mysql"] = "lib/resty/session/mysql.lua",
29 | ["resty.session.postgres"] = "lib/resty/session/postgres.lua",
30 | ["resty.session.redis"] = "lib/resty/session/redis.lua",
31 | ["resty.session.redis.cluster"] = "lib/resty/session/redis/cluster.lua",
32 | ["resty.session.redis.sentinel"] = "lib/resty/session/redis/sentinel.lua",
33 | ["resty.session.redis.common"] = "lib/resty/session/redis/common.lua",
34 | ["resty.session.shm"] = "lib/resty/session/shm.lua",
35 | ["resty.session.utils"] = "lib/resty/session/utils.lua",
36 | },
37 | }
38 |
--------------------------------------------------------------------------------
/spec/01-utils_spec.lua:
--------------------------------------------------------------------------------
1 | local utils = require "resty.session.utils"
2 |
3 |
4 | local describe = describe
5 | local tostring = tostring
6 | local random = math.random
7 | local assert = assert
8 | local ipairs = ipairs
9 | local match = string.match
10 | local it = it
11 |
12 |
13 | describe("Testing utils", function()
14 | describe("pack/unpack data", function()
15 | local function range_max(bytes)
16 | if bytes == 8 then
17 | bytes = bytes - 1
18 | end
19 | return 2 ^ (8 * bytes) - 1
20 | end
21 |
22 | it("bpack and bunpack produce consistent output", function()
23 | for _, i in ipairs{ 1, 2, 4, 8 } do
24 | local input = random(1, range_max(i))
25 | local packed = utils.bpack(i, input)
26 | local unpacked = utils.bunpack(i, packed)
27 |
28 | assert.equals(i, #packed)
29 | assert.equals(unpacked, input)
30 | end
31 | end)
32 | end)
33 |
34 | describe("trim", function()
35 | it("characters are trimmed as expected", function()
36 | local input = " \t\t\r\n\n\v\f\f\fyay\ntrim!\f\f\v\n\r\t "
37 | local expected_output = "yay\ntrim!"
38 | local output = utils.trim(input)
39 |
40 | assert.equals(output, expected_output)
41 | end)
42 | end)
43 |
44 | describe("encode/decode json", function()
45 | it("produce consistent output", function()
46 | local input = {
47 | foo = "bar",
48 | test = 123
49 | }
50 | local encoded = utils.encode_json(input)
51 | local decoded = utils.decode_json(encoded)
52 |
53 | assert.same(decoded, input)
54 | end)
55 | end)
56 |
57 | describe("encode/decode base64url", function()
58 | it("produce consistent output", function()
59 | local input = "<<>>?!"
60 | local encoded = utils.encode_base64url(input)
61 |
62 | assert.is_nil(match(encoded, "[/=+]"))
63 | local decoded = utils.decode_base64url(encoded)
64 | assert.equals(input, decoded)
65 | end)
66 | end)
67 |
68 | describe("deflate/inflate", function()
69 | it("produce consistent output", function()
70 | local input = utils.rand_bytes(1024)
71 | local deflated = utils.deflate(input)
72 | local inflated = utils.inflate(deflated)
73 |
74 | assert.equals(input, inflated)
75 | end)
76 | end)
77 |
78 | describe("Derive keys, encrypt and decrypt", function()
79 | local ikm = "some key material"
80 | local nonce = "0000000000000000"
81 | local usage = "encryption"
82 | local size = 44
83 |
84 | it("derives key of expected size with derive_hkdf_sha256", function()
85 | local k_bytes, err = utils.derive_hkdf_sha256(ikm, nonce, usage, size)
86 | assert.is_not_nil(k_bytes)
87 | assert.is_nil(err)
88 | assert.equals(size, #tostring(k_bytes))
89 | end)
90 |
91 | it("derives key of expected size with derive_pbkdf2_hmac_sha256", function()
92 | local k_bytes, err = utils.derive_pbkdf2_hmac_sha256(ikm, nonce, usage, size)
93 | assert.is_not_nil(k_bytes)
94 | assert.is_nil(err)
95 | assert.equals(size, #tostring(k_bytes))
96 | end)
97 |
98 | it("derives a valid key and calculates mac with it", function()
99 | local key, err, mac
100 | key, err = utils.derive_hmac_sha256_key(ikm, nonce)
101 | assert.is_not_nil(key)
102 | assert.is_nil(err)
103 | assert.equals(32, #key)
104 |
105 | mac, err = utils.hmac_sha256(key, "some message")
106 | assert.is_not_nil(mac)
107 | assert.is_nil(err)
108 | end)
109 |
110 | it("successfully derives key and iv; encryption and decryption succeeds", function()
111 | local data = {
112 | foo = "bar",
113 | tab = { "val" },
114 | num = 123,
115 | }
116 | local aad = "some_aad"
117 | local input_data = utils.encode_json(data)
118 |
119 | local key, err, iv, ciphertext, tag, plaintext
120 | for _, slow in ipairs({ true, false }) do
121 | key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce, slow)
122 | assert.is_not_nil(key)
123 | assert.is_not_nil(iv)
124 | assert.is_nil(err)
125 |
126 | ciphertext, err, tag = utils.encrypt_aes_256_gcm(key, iv, input_data, aad)
127 | assert.is_not_nil(ciphertext)
128 | assert.is_not_nil(tag)
129 | assert.is_nil(err)
130 |
131 | plaintext, err = utils.decrypt_aes_256_gcm(key, iv, ciphertext, aad, tag)
132 | assert.is_not_nil(plaintext)
133 | assert.is_nil(err)
134 |
135 | assert.equals(plaintext, input_data)
136 | end
137 | end)
138 | end)
139 | describe("load_storage", function()
140 | -- "dshm" is disabled as it currently cannot be checked by CI
141 | for _, strategy in ipairs({ "memcached", "mysql", "postgres", "redis" }) do
142 | it("respects pool parameters #" .. strategy, function()
143 | local storage = assert(utils.load_storage(strategy, {
144 | [strategy] = {
145 | pool = "doge",
146 | pool_size = 10,
147 | backlog = 20,
148 | },
149 | }))
150 |
151 | assert.equal("doge", storage.options.pool)
152 | assert.equal(10, storage.options.pool_size)
153 | assert.equal(20, storage.options.backlog)
154 | end)
155 | end
156 | it("respects pool parameters #redis-sentinel", function()
157 | local storage = assert(utils.load_storage("redis", {
158 | redis = {
159 | pool = "doge",
160 | pool_size = 10,
161 | backlog = 20,
162 | sentinels = {},
163 | },
164 | }))
165 |
166 | assert.equal("doge", storage.connector.config.connection_options.pool)
167 | assert.equal(10, storage.connector.config.connection_options.pool_size)
168 | assert.equal(20, storage.connector.config.connection_options.backlog)
169 | end)
170 | it("respects pool parameters #redis-cluster", function()
171 | local storage = assert(utils.load_storage("redis", {
172 | redis = {
173 | pool = "doge",
174 | pool_size = 10,
175 | backlog = 20,
176 | nodes = {},
177 | },
178 | }))
179 |
180 | assert.equal("doge", storage.options.connect_opts.pool)
181 | assert.equal(10, storage.options.connect_opts.pool_size)
182 | assert.equal(20, storage.options.connect_opts.backlog)
183 | end)
184 | end)
185 | end)
186 |
--------------------------------------------------------------------------------
/spec/02-file-utils_spec.lua:
--------------------------------------------------------------------------------
1 | local file_utils = require "resty.session.file.utils"
2 |
3 |
4 | local fmt = string.format
5 | local describe = describe
6 | local assert = assert
7 | local it = it
8 |
9 |
10 | describe("Testing file utils", function()
11 | describe("validate file name", function()
12 | local validate_file_name = file_utils.validate_file_name
13 | local name = "name"
14 | local sid1 = "ABCdef123_-iJqwertYUIOpasdfgHJKLzxcvbnmJuis"
15 | local aud_sub1 = "some-aud:some-sub"
16 | local simple_prefix = "pref"
17 | local special_prefix = "@-_-"
18 | local simple_suffix = "suff"
19 | local special_suffix = "-_-@"
20 | local filename_inv1 = "some.conf"
21 | local filename_inv2 = "abc"
22 | local filename_inv3 = name.."_".. "abc"
23 |
24 | it("validation fails with invalid files with prefix and suffix", function()
25 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv1))
26 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv2))
27 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv3))
28 | end)
29 |
30 |
31 | it("validation fails with invalid files with prefix only", function()
32 | assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv1))
33 | assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv2))
34 | assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv3))
35 | end)
36 |
37 | it("validation fails with invalid files with suffix only", function()
38 | assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv1))
39 | assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv2))
40 | assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv3))
41 | end)
42 |
43 | it("validation fails with invalid files with no prefix or suffix", function()
44 | assert.is_false(validate_file_name(nil, nil, name, filename_inv1))
45 | assert.is_false(validate_file_name(nil, nil, name, filename_inv2))
46 | assert.is_false(validate_file_name(nil, nil, name, filename_inv3))
47 | end)
48 |
49 | it("validation passes with prefix and suffix", function()
50 | local filename_sess = fmt("%s_%s_%s.%s", simple_prefix, name, sid1, simple_suffix)
51 | local filename_meta = fmt("%s_%s_%s.%s", simple_prefix, name, aud_sub1, simple_suffix)
52 |
53 | assert.is_true(validate_file_name(simple_prefix, simple_suffix, name, filename_sess))
54 | assert.is_true(validate_file_name(simple_prefix, simple_suffix, name, filename_meta))
55 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv1))
56 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv2))
57 | end)
58 |
59 | it("validation passes with special prefix and suffix", function()
60 | local sp_filename_sess = fmt("%s_%s_%s.%s", special_prefix, name, sid1, special_suffix)
61 | local sp_filename_meta = fmt("%s_%s_%s.%s", special_prefix, name, aud_sub1, special_suffix)
62 |
63 | assert.is_true(validate_file_name(special_prefix, special_suffix, name, sp_filename_sess))
64 | assert.is_true(validate_file_name(special_prefix, special_suffix, name, sp_filename_meta))
65 | end)
66 |
67 |
68 | it("validation passes with prefix", function()
69 | local filename_sess = fmt("%s_%s_%s", simple_prefix, name, sid1)
70 | local filename_meta = fmt("%s_%s_%s", simple_prefix, name, aud_sub1)
71 |
72 | assert.is_true(validate_file_name(simple_prefix, nil, name, filename_sess))
73 | assert.is_true(validate_file_name(simple_prefix, nil, name, filename_meta))
74 | end)
75 |
76 | it("validation passes with special prefix", function()
77 | local sp_filename_sess = fmt("%s_%s_%s", special_prefix, name, sid1)
78 | local sp_filename_meta = fmt("%s_%s_%s", special_prefix, name, aud_sub1)
79 |
80 | assert.is_true(validate_file_name(special_prefix, nil, name, sp_filename_sess))
81 | assert.is_true(validate_file_name(special_prefix, nil, name, sp_filename_meta))
82 | end)
83 |
84 | it("#only validation passes with suffix", function()
85 | local filename_sess = fmt("%s_%s.%s", name, sid1, simple_suffix)
86 | local filename_meta = fmt("%s_%s.%s", name, aud_sub1, simple_suffix)
87 |
88 | assert.is_true(validate_file_name(nil, simple_suffix, name, filename_sess))
89 | assert.is_true(validate_file_name(nil, simple_suffix, name, filename_meta))
90 | end)
91 |
92 | it("validation passes with special suffix", function()
93 | local sp_filename_sess = fmt("%s_%s.%s", name, sid1, special_suffix)
94 | local sp_filename_meta = fmt("%s_%s.%s", name, aud_sub1, special_suffix)
95 |
96 | assert.is_true(validate_file_name(nil, special_suffix, name, sp_filename_sess))
97 | assert.is_true(validate_file_name(nil, special_suffix, name, sp_filename_meta))
98 | end)
99 |
100 | it("validation passes with no prefix or suffix", function()
101 | local filename_sess = fmt("%s_%s", name, sid1)
102 | local filename_meta = fmt("%s_%s", name, aud_sub1)
103 |
104 | assert.is_true(validate_file_name(nil, nil, name, filename_sess))
105 | assert.is_true(validate_file_name(nil, nil, name, filename_meta))
106 | end)
107 | end)
108 | end)
109 |
--------------------------------------------------------------------------------
/spec/04-storage-1_spec.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- Ensure to keep the tests consistent with those in 05-storage-1_spec.lua
3 |
4 |
5 | local utils = require "resty.session.utils"
6 |
7 |
8 | local before_each = before_each
9 | local after_each = after_each
10 | local lazy_setup = lazy_setup
11 | local describe = describe
12 | local ipairs = ipairs
13 | local assert = assert
14 | local sleep = ngx.sleep
15 | local time = ngx.time
16 | local it = it
17 |
18 |
19 | local storage_configs = {
20 | file = {
21 | suffix = "session",
22 | },
23 | shm = {
24 | prefix = "sessions",
25 | connect_timeout = 10000,
26 | send_timeout = 10000,
27 | read_timeout = 10000,
28 | },
29 | redis = {
30 | prefix = "sessions",
31 | password = "password",
32 | },
33 | memcached = {
34 | prefix = "sessions",
35 | connect_timeout = 10000,
36 | send_timeout = 10000,
37 | read_timeout = 10000,
38 | },
39 | }
40 |
41 |
42 | for _, st in ipairs({
43 | "file",
44 | "shm",
45 | "redis",
46 | "memcached",
47 | }) do
48 | describe("Storage tests 1", function()
49 | local current_time
50 | local storage
51 | local long_ttl = 60
52 | local short_ttl = 2
53 | local key = "test_key_1iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
54 | local key1 = "test_key_2iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
55 | local key2 = "test_key_3iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
56 | local old_key = "old_key_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
57 | local name = "test_name"
58 | local value = "test_value"
59 |
60 | lazy_setup(function()
61 | local conf = {
62 | remember = true,
63 | store_metadata = true,
64 | }
65 | conf[st] = storage_configs[st]
66 | storage = utils.load_storage(st, conf)
67 | assert.is_not_nil(storage)
68 | end)
69 |
70 | before_each(function()
71 | current_time = time()
72 | end)
73 |
74 | describe("[#" .. st .. "] storage: SET + GET", function()
75 | local audiences = { "foo", "bar" }
76 | local subjects = { "john", "jane" }
77 |
78 | local metadata = {
79 | audiences = audiences,
80 | subjects = subjects,
81 | }
82 |
83 | after_each(function()
84 | storage:delete(name, key, current_time, metadata)
85 | storage:delete(name, key1, current_time, metadata)
86 | storage:delete(name, key2, current_time, metadata)
87 | end)
88 |
89 | it("SET: simple set does not return errors, GET fetches value correctly", function()
90 | local ok = storage:set(name, key, value, long_ttl, current_time)
91 | assert.is_not_nil(ok)
92 |
93 | local v, err = storage:get(name, key, current_time)
94 | assert.is_not_nil(v)
95 | assert.is_nil(err)
96 | assert.equals(v, value)
97 | end)
98 |
99 | it("SET: with metadata and remember works correctly", function()
100 | local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata, true)
101 | assert.is_not_nil(ok)
102 |
103 | sleep(1)
104 |
105 | local v, err = storage:get(name, key, time())
106 | assert.is_not_nil(v)
107 | assert.is_nil(err)
108 | assert.equals(v, value)
109 | end)
110 |
111 | it("SET: with metadata (long ttl) correctly appends metadata to collection", function()
112 | local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata, true)
113 | ok = ok and storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true)
114 | ok = ok and storage:set(name, key2, value, long_ttl, current_time, nil, nil, metadata, true)
115 | assert.is_not_nil(ok)
116 |
117 | sleep(1)
118 |
119 | for i = 1, #audiences do
120 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time())
121 | assert.is_not_nil(meta_values)
122 | assert.truthy(meta_values[key ])
123 | assert.truthy(meta_values[key1])
124 | assert.truthy(meta_values[key2])
125 | end
126 | end)
127 |
128 | it("SET: with metadata (short ttl) correctly expires metadata", function()
129 | local ok = storage:set(name, key, value, short_ttl, current_time, nil, nil, metadata, true)
130 |
131 | sleep(short_ttl + 1)
132 |
133 | ok = ok and storage:set(name, key1, value, long_ttl, time(), nil, nil, metadata, true)
134 | assert.is_not_nil(ok)
135 |
136 | sleep(1)
137 |
138 | for i = 1, #audiences do
139 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time())
140 | assert.falsy(meta_values[key])
141 | assert.truthy(meta_values[key1])
142 | end
143 | end)
144 |
145 | it("SET: with old_key correctly applies stale ttl on old key", function()
146 | local stale_ttl = 1
147 |
148 | local ok = storage:set(name, old_key, value, long_ttl, current_time)
149 | assert.is_not_nil(ok)
150 |
151 | ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, false)
152 | assert.is_not_nil(ok)
153 |
154 | sleep(3)
155 |
156 | local v = storage:get(name, old_key, time())
157 | assert.is_nil(v)
158 | end)
159 |
160 | it("SET: remember deletes file in old_key", function()
161 | local stale_ttl = long_ttl
162 |
163 | local ok = storage:set(name, old_key, value, long_ttl, current_time)
164 | assert.is_not_nil(ok)
165 |
166 | ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, true)
167 | assert.is_not_nil(ok)
168 |
169 | local v = storage:get(name, old_key, current_time)
170 | assert.is_nil(v)
171 | end)
172 |
173 | it("SET: ttl works as expected", function()
174 | local ok = storage:set(name, key, value, short_ttl, current_time)
175 | assert.is_not_nil(ok)
176 |
177 | sleep(3)
178 |
179 | local v = storage:get(name, key, time())
180 | assert.is_nil(v)
181 | end)
182 | end)
183 |
184 | describe("[#" .. st .. "] storage: DELETE", function()
185 | local audiences = { "foo" }
186 | local subjects = { "john" }
187 |
188 | local metadata = {
189 | audiences = audiences,
190 | subjects = subjects,
191 | }
192 |
193 | it("deleted file is really deleted", function()
194 | local ok = storage:set(name, key, value, short_ttl, current_time)
195 | assert.is_not_nil(ok)
196 |
197 | storage:delete(name, key, current_time, nil)
198 |
199 | local v = storage:get(name, key, current_time)
200 | assert.is_nil(v)
201 | end)
202 |
203 | it("with metadata correctly deletes metadata collection", function()
204 | local ok = storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true)
205 | assert.is_not_nil(ok)
206 |
207 | sleep(1)
208 |
209 | for i = 1, #audiences do
210 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time())
211 | assert.truthy(meta_values[key1])
212 | ok = storage:delete(name, key1, time(), metadata)
213 | assert.is_not_nil(ok)
214 |
215 | sleep(2)
216 |
217 | meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) or {}
218 | assert.falsy(meta_values[key1])
219 | end
220 | end)
221 | end)
222 | end)
223 | end
224 |
--------------------------------------------------------------------------------
/spec/05-storage-2_spec.lua:
--------------------------------------------------------------------------------
1 | ---
2 | -- For now these tests don't run on CI.
3 | -- Ensure to keep the tests consistent with those in 04-storage-1_spec.lua
4 |
5 |
6 | local utils = require "resty.session.utils"
7 |
8 |
9 | local before_each = before_each
10 | local after_each = after_each
11 | local lazy_setup = lazy_setup
12 | local describe = describe
13 | local ipairs = ipairs
14 | local assert = assert
15 | local sleep = ngx.sleep
16 | local time = ngx.time
17 | local it = it
18 |
19 |
20 | local storage_configs = {
21 | mysql = {
22 | username = "root",
23 | password = "password",
24 | database = "test",
25 | },
26 | postgres = {
27 | username = "postgres",
28 | password = "password",
29 | database = "test",
30 | },
31 | redis_sentinel = {
32 | prefix = "sessions",
33 | password = "password",
34 | sentinels = {
35 | { host = "127.0.0.1", port = "26379" }
36 | },
37 | connect_timeout = 10000,
38 | send_timeout = 10000,
39 | read_timeout = 10000,
40 | },
41 | redis_cluster = {
42 | password = "password",
43 | nodes = {
44 | { ip = "127.0.0.1", port = "6380" }
45 | },
46 | name = "somecluster",
47 | lock_zone = "sessions",
48 | connect_timeout = 10000,
49 | send_timeout = 10000,
50 | read_timeout = 10000,
51 | },
52 | dshm = {
53 | prefix = "sessions",
54 | connect_timeout = 10000,
55 | send_timeout = 10000,
56 | read_timeout = 10000,
57 | },
58 | }
59 |
60 |
61 | local function storage_type(ty)
62 | if ty == "redis_cluster" or ty == "redis_sentinel" then
63 | return "redis"
64 | end
65 | return ty
66 | end
67 |
68 |
69 | for _, st in ipairs({
70 | "mysql",
71 | "postgres",
72 | "redis_cluster",
73 | "redis_sentinel",
74 | "dshm"
75 | }) do
76 | describe("Storage tests 2 #noci", function()
77 | local current_time
78 | local storage
79 | local long_ttl = 60
80 | local short_ttl = 2
81 | local key = "test_key_1iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
82 | local key1 = "test_key_2iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
83 | local key2 = "test_key_3iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
84 | local old_key = "old_key_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
85 | local name = "test_name"
86 | local value = "test_value"
87 |
88 | lazy_setup(function()
89 | local conf = {
90 | remember = true,
91 | store_metadata = true,
92 | }
93 | conf[storage_type(st)] = storage_configs[st]
94 | storage = utils.load_storage(storage_type(st), conf)
95 | assert.is_not_nil(storage)
96 | end)
97 |
98 | before_each(function()
99 | current_time = time()
100 | end)
101 |
102 | describe("[#" .. st .. "] storage: SET + GET", function()
103 | local audiences = { "foo", "bar" }
104 | local subjects = { "john", "jane" }
105 |
106 | local metadata = {
107 | audiences = audiences,
108 | subjects = subjects,
109 | }
110 |
111 | after_each(function()
112 | current_time = time()
113 | storage:delete(name, key, current_time, metadata)
114 | storage:delete(name, key1, current_time, metadata)
115 | storage:delete(name, key2, current_time, metadata)
116 | end)
117 |
118 | it("SET: simple set does not return errors, GET fetches value correctly", function()
119 | local ok = storage:set(name, key, value, long_ttl, current_time)
120 | assert.is_not_nil(ok)
121 |
122 | local v, err = storage:get(name, key, current_time)
123 | assert.is_not_nil(v)
124 | assert.is_nil(err)
125 | assert.equals(v, value)
126 | end)
127 |
128 | it("SET: with metadata and remember works correctly", function()
129 | local ok = storage:set(name, key, value, long_ttl, time(), nil, nil, metadata, true)
130 | assert.is_not_nil(ok)
131 |
132 | sleep(1)
133 |
134 | local v, err = storage:get(name, key, time())
135 | assert.is_not_nil(v)
136 | assert.is_nil(err)
137 | assert.equals(v, value)
138 | end)
139 |
140 | it("SET: with metadata (long ttl) correctly appends metadata to collection", function()
141 | local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata, true)
142 | ok = ok and storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true)
143 | ok = ok and storage:set(name, key2, value, long_ttl, current_time, nil, nil, metadata, true)
144 | assert.is_not_nil(ok)
145 |
146 | sleep(1)
147 |
148 | for i = 1, #audiences do
149 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time())
150 | assert.is_not_nil(meta_values)
151 | assert.truthy(meta_values[key ])
152 | assert.truthy(meta_values[key1])
153 | assert.truthy(meta_values[key2])
154 | end
155 | end)
156 |
157 | it("SET: with metadata (short ttl) correctly expires metadata", function()
158 | local ok = storage:set(name, key, value, short_ttl, current_time, nil, nil, metadata, true)
159 |
160 | sleep(short_ttl + 1)
161 |
162 | ok = ok and storage:set(name, key1, value, long_ttl, time(), nil, nil, metadata, true)
163 | assert.is_not_nil(ok)
164 |
165 | sleep(1)
166 |
167 | for i = 1, #audiences do
168 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time())
169 | assert.falsy(meta_values[key])
170 | assert.truthy(meta_values[key1])
171 | end
172 | end)
173 |
174 | it("SET: with old_key correctly applies stale ttl on old key", function()
175 | local stale_ttl = 1
176 |
177 | local ok = storage:set(name, old_key, value, long_ttl, current_time)
178 | assert.is_not_nil(ok)
179 |
180 | ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, false)
181 | assert.is_not_nil(ok)
182 |
183 | sleep(3)
184 |
185 | local v = storage:get(name, old_key, time())
186 | assert.is_nil(v)
187 | end)
188 |
189 | it("SET: remember deletes file in old_key", function()
190 | local stale_ttl = long_ttl
191 | local ok = storage:set(name, old_key, value, long_ttl, current_time)
192 | assert.is_not_nil(ok)
193 |
194 | ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, true)
195 | assert.is_not_nil(ok)
196 |
197 | local v = storage:get(name, old_key, current_time)
198 | assert.is_nil(v)
199 | end)
200 |
201 | it("SET: ttl works as expected", function()
202 | local ok = storage:set(name, key, value, short_ttl, current_time)
203 | assert.is_not_nil(ok)
204 |
205 | sleep(3)
206 |
207 | local v = storage:get(name, key, time())
208 | assert.is_nil(v)
209 | end)
210 | end)
211 |
212 | describe("[#" .. st .. "] storage: DELETE", function()
213 | local audiences = { "foo" }
214 | local subjects = { "john" }
215 |
216 | local metadata = {
217 | audiences = audiences,
218 | subjects = subjects,
219 | }
220 |
221 | it("deleted file is really deleted", function()
222 | local ok = storage:set(name, key, value, short_ttl, current_time)
223 | assert.is_not_nil(ok)
224 |
225 | storage:delete(name, key, current_time)
226 |
227 | local v = storage:get(name, key, current_time)
228 | assert.is_nil(v)
229 | end)
230 |
231 | it("with metadata correctly deletes metadata collection", function()
232 | local ok = storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true)
233 | assert.is_not_nil(ok)
234 |
235 | sleep(1)
236 |
237 | for i = 1, #audiences do
238 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time())
239 | assert.truthy(meta_values[key1])
240 |
241 | ok = storage:delete(name, key1, time(), metadata)
242 | assert.is_not_nil(ok)
243 |
244 | sleep(2)
245 |
246 | meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) or {}
247 | assert.falsy(meta_values[key1])
248 | end
249 | end)
250 | end)
251 | end)
252 | end
253 |
--------------------------------------------------------------------------------
/t/01-cookies.t:
--------------------------------------------------------------------------------
1 | use Test::Nginx::Socket;
2 | repeat_each(2);
3 |
4 | $ENV{TEST_NGINX_NXSOCK} ||= html_dir();
5 | plan tests => repeat_each() * blocks() * 3 + 4;
6 |
7 | run_tests();
8 |
9 | __DATA__
10 |
11 | === TEST 1: session cookie is returned
12 | --- http_config
13 | init_by_lua_block {
14 | require("resty.session").init({
15 | storage = "cookie",
16 | })
17 | }
18 | --- config
19 | location = /test {
20 | content_by_lua_block {
21 | local session = require "resty.session".new()
22 | local ok, err = session:save()
23 | ngx.say(ok and "yay" or err)
24 | }
25 | }
26 |
27 | --- request
28 | GET /test
29 | --- response_body
30 | yay
31 | --- response_headers_like
32 | Set-Cookie: .*session=.+;.*
33 | --- error_code: 200
34 | --- no_error_log
35 | [error]
36 |
37 |
38 | === TEST 2: remember cookie is returned when remember=true
39 | --- http_config
40 | init_by_lua_block {
41 | require("resty.session").init({
42 | remember = true,
43 | storage = "cookie",
44 | })
45 | }
46 | --- config
47 | location = /test {
48 | content_by_lua_block {
49 | local session = require "resty.session".new()
50 | local ok, err = session:save()
51 | ngx.say(ok and "yay" or err)
52 | }
53 | }
54 |
55 | --- request
56 | GET /test
57 | --- response_body
58 | yay
59 | --- response_headers_like
60 | Set-Cookie: .*remember=.+;.*
61 | --- error_code: 200
62 | --- no_error_log
63 | [error]
64 |
65 |
66 | === TEST 3: session.open() opens a session from a valid cookie and data is
67 | extracted correctly
68 | --- http_config
69 | init_by_lua_block {
70 | require("resty.session").init({
71 | secret = "RaJKp8UQW1",
72 | storage = "cookie",
73 | audience = "my_application",
74 | idling_timeout = 0,
75 | rolling_timeout = 0,
76 | absolute_timeout = 0,
77 | })
78 | }
79 | --- config
80 | location = /test {
81 | content_by_lua_block {
82 | local session = require "resty.session".open()
83 | local sub = session:get_subject()
84 | local aud = session:get_audience()
85 | local quote = session:get("quote")
86 | ngx.say(sub .. "|" .. aud .. "|" .. quote)
87 | }
88 | }
89 |
90 | --- request
91 | GET /test
92 | --- more_headers
93 | Cookie: session=AQAAS3ZGU0k8tUKsWSci9Fb6PM5xbm469FlR5g_B5HWZ6KYGSOZjAAAAAABcAABTCuHjqpE7B6Ux7m4GCylZAAAAzcWnTvzG51whooR_4QQwDgGdMOOa5W7tG4JWiDFU3zuYLFzakWEi-y-ogrwTpnt24zQXP_uJK7r5lMPNzRSMJM9H1a_MIegzEMm-QSgVRaoZVJq3Oo; Path=/; SameSite=Lax; HttpOnly
94 | --- response_body
95 | Lua Fan|my_application|Lorem ipsum dolor sit amet
96 | --- error_code: 200
97 | --- no_error_log
98 | [error]
99 |
100 |
101 | === TEST 4: clear_request_cookie() clears session Cookie from request to
102 | upstream
103 | --- http_config
104 | init_by_lua_block {
105 | require("resty.session").init({
106 | secret = "RaJKp8UQW1",
107 | storage = "cookie",
108 | audience = "my_application",
109 | idling_timeout = 0,
110 | rolling_timeout = 0,
111 | absolute_timeout = 0,
112 | })
113 | }
114 |
115 | server {
116 | listen unix:/$TEST_NGINX_NXSOCK/nginx.sock;
117 | location /t {
118 | content_by_lua_block {
119 | local headers = ngx.req.get_headers()
120 | ngx.say("session_cookie: [", tostring(headers["Cookie"]), "]")
121 | }
122 | }
123 | }
124 |
125 | --- config
126 | location = /test {
127 | access_by_lua_block {
128 | local session = require "resty.session".open()
129 | session:clear_request_cookie()
130 | }
131 | proxy_pass http://unix:/$TEST_NGINX_NXSOCK/nginx.sock;
132 | }
133 |
134 | --- request
135 | GET /test
136 | --- more_headers
137 | Cookie: session=AQAAS3ZGU0k8tUKsWSci9Fb6PM5xbm469FlR5g_B5HWZ6KYGSOZjAAAAAABcAABTCuHjqpE7B6Ux7m4GCylZAAAAzcWnTvzG51whooR_4QQwDgGdMOOa5W7tG4JWiDFU3zuYLFzakWEi-y-ogrwTpnt24zQXP_uJK7r5lMPNzRSMJM9H1a_MIegzEMm-QSgVRaoZVJq3Oo; Path=/; SameSite=Lax; HttpOnly
138 | --- response_body
139 | session_cookie: [Path=/; SameSite=Lax; HttpOnly]
140 | --- error_code: 200
141 | --- no_error_log
142 | [error]
143 |
--------------------------------------------------------------------------------