├── .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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 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 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
resty.sessionSession library.
resty.session.dshmDistributed Shared Memory (DSHM) backend for session library
resty.session.fileFile storage backend for session library.
resty.session.file.threadFile storage backend worker thread module
resty.session.file.utilsFile storage utilities
resty.session.memcachedMemcached backend for session library
resty.session.mysqlMySQL / MariaDB backend for session library
resty.session.postgresPostgres backend for session library.
resty.session.redisRedis backend for session library
resty.session.redis.clusterRedis Cluster backend for session library
resty.session.redis.commonCommon Redis functions shared between Redis, 104 | Redis Cluster and Redis Sentinel implementations.
resty.session.redis.sentinelRedis Sentinel backend for session library
resty.session.shmShared Memory (SHM) backend for session library
resty.session.utilsCommon utilities for session library and storage backends
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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 56 | 57 |
58 | 59 |

Module resty.session.file-thread

60 |

File storage backend worker thread module

61 |

62 |

63 | 64 | 65 |

Functions

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
set (path, content)Store data in file.
get (path)Read data from a file.
delete (path)Delete a file.
80 | 81 |
82 |
83 | 84 | 85 |

Functions

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 |
  1. 113 | true or nil 114 | ok
  2. 115 |
  3. 116 | string 117 | error message
  4. 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 |
  1. 143 | string or nil 144 | content
  2. 145 |
  3. 146 | string 147 | error message
  4. 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 |
  1. 173 | string or nil 174 | ok
  2. 175 |
  3. 176 | string 177 | error message
  4. 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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 61 | 62 |
63 | 64 |

Module resty.session.file

65 |

File storage backend for session library.

66 |

67 | 68 |

69 | 70 | 71 |

Configuration

72 | 73 | 74 | 75 | 76 | 77 |
configurationFile storage backend configuration
78 |

Constructors

79 | 80 | 81 | 82 | 83 | 84 |
module.new ([configuration])Create a file storage.
85 |

Storage

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)Store session data.
instance:get (name, key)Retrieve session data.
instance:delete (name, key[, metadata])Delete session data.
instance:read_metadata (name, audience, subject, current_time)Read session metadata.
104 | 105 |
106 |
107 | 108 | 109 |

Configuration

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 |

Constructors

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 |

Storage

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 |
  1. 232 | true or nil 233 | ok
  2. 234 |
  3. 235 | string 236 | error message
  4. 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 |
  1. 266 | string or nil 267 | session data
  2. 268 |
  3. 269 | string 270 | error message
  4. 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 |
  1. 305 | boolean or nil 306 | session data
  2. 307 |
  3. 308 | string 309 | error message
  4. 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 |
  1. 347 | table or nil 348 | session metadata
  2. 349 |
  3. 350 | string 351 | error message
  4. 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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 59 | 60 |
61 | 62 |

Module resty.session.file.thread

63 |

File storage backend worker thread module

64 |

65 | 66 |

67 | 68 | 69 |

Functions

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)Store session data.
module.GET (path, prefix, suffix, name, key)Retrieve session data.
module.delete (path, prefix, suffix, name, key, current_time)Delete session data.
module.read_metadata (path, prefix, suffix, name, audience, subject, current_time)Read session metadata.
88 | 89 |
90 |
91 | 92 | 93 |

Functions

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 |
  1. 161 | table or nil 162 | session metadata
  2. 163 |
  3. 164 | string 165 | error message
  4. 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 |
  1. 207 | string or nil 208 | session data
  2. 209 |
  3. 210 | string 211 | error message
  4. 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 |
  1. 257 | table or nil 258 | session metadata
  2. 259 |
  3. 260 | string 261 | error message
  4. 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 |
  1. 311 | table or nil 312 | session metadata
  2. 313 |
  3. 314 | string 315 | error message
  4. 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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 59 | 60 |
61 | 62 |

Module resty.session.file.utils

63 |

File storage utilities

64 |

65 | 66 |

67 | 68 | 69 |

Functions

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
file_create (path, content)Store data in file.
file_append (path, data)Append data in file.
file_read (path)Read data from a file.
get_modification (path)Get the value modification time of a file.
meta_get_key (audience, subject)Given an audience and a subject, generate a metadata key.
validate_file_name (prefix, suffix, name, filename)Validate a file name.
cleanup (path, prefix, suffix, name, current_time)Clean up expired session and metadata files.
100 | 101 |
102 |
103 | 104 | 105 |

Functions

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 |
  1. 131 | true or nil 132 | ok
  2. 133 |
  3. 134 | string 135 | error message
  4. 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 |
  1. 165 | true or nil 166 | ok
  2. 167 |
  3. 168 | string 169 | error message
  4. 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 |
  1. 195 | string or nil 196 | content
  2. 197 |
  3. 198 | string 199 | error message
  4. 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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 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 |

Functions

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)Store session data.
module.GET (storage, red, name, key)Retrieve session data.
module.UNLINK (storage, red, name, key, current_time[, metadata])Delete session data.
module.READ_METADATA (storage, red, name, audience, subject, current_time)Read session metadata.
89 | 90 |
91 |
92 | 93 | 94 |

Functions

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 |
  1. 158 | true or nil 159 | ok
  2. 160 |
  3. 161 | string 162 | error message
  4. 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 |
  1. 200 | string or nil 201 | session data
  2. 202 |
  3. 203 | string 204 | error message
  4. 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 |
  1. 251 | boolean or nil 252 | session data
  2. 253 |
  3. 254 | string 255 | error message
  4. 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 |
  1. 301 | table or nil 302 | session metadata
  2. 303 |
  3. 304 | string 305 | error message
  4. 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 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 61 | 62 |
63 | 64 |

Module resty.session.shm

65 |

Shared Memory (SHM) backend for session library

66 |

67 | 68 |

69 | 70 | 71 |

Configuration

72 | 73 | 74 | 75 | 76 | 77 |
configurationShared memory storage backend configuration
78 |

Constructors

79 | 80 | 81 | 82 | 83 | 84 |
module.new ([configuration])Create a SHM storage.
85 |

Storage

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)Store session data.
instance:get (name, key)Retrieve session data.
instance:delete (name, key[, metadata])Delete session data.
instance:read_metadata (name, audience, subject, current_time)Read session metadata.
104 | 105 |
106 |
107 | 108 | 109 |

Configuration

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 |

Constructors

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 |

Storage

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 |
  1. 229 | true or nil 230 | ok
  2. 231 |
  3. 232 | string 233 | error message
  4. 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 |
  1. 263 | string or nil 264 | session data
  2. 265 |
  3. 266 | string 267 | error message
  4. 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 |
  1. 302 | boolean or nil 303 | session data
  2. 304 |
  3. 305 | string 306 | error message
  4. 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 |
  1. 344 | table or nil 345 | session metadata
  2. 346 |
  3. 347 | string 348 | error message
  4. 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 | --------------------------------------------------------------------------------