aboutsummaryrefslogtreecommitdiffstats
path: root/server/resty/session
diff options
context:
space:
mode:
authorFiete Ostkamp <Fiete.Ostkamp@telekom.de>2023-04-14 11:59:32 +0000
committerFiete Ostkamp <Fiete.Ostkamp@telekom.de>2023-04-14 11:59:32 +0000
commitd68841d9f75636575cd778838a8ceea5fd5aada3 (patch)
tree778c84203ed9bfa4dc1c8234e4e2cf60da6ebd8c /server/resty/session
parent42af09588f1f839b9ab36356f02f34c89559bcfa (diff)
Upload ui
Issue-ID: PORTAL-1084 Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de> Change-Id: Id0c94859a775094e67b0bb9c91ca5e776a08c068
Diffstat (limited to 'server/resty/session')
-rw-r--r--server/resty/session/ciphers/aes.lua113
-rw-r--r--server/resty/session/ciphers/none.lua15
-rw-r--r--server/resty/session/compressors/none.lua15
-rw-r--r--server/resty/session/compressors/zlib.lua43
-rw-r--r--server/resty/session/encoders/base16.lua29
-rw-r--r--server/resty/session/encoders/base64.lua39
-rw-r--r--server/resty/session/encoders/hex.lua1
-rw-r--r--server/resty/session/hmac/sha1.lua1
-rw-r--r--server/resty/session/identifiers/random.lua13
-rw-r--r--server/resty/session/serializers/json.lua6
-rw-r--r--server/resty/session/storage/cookie.lua7
-rw-r--r--server/resty/session/storage/dshm.lua163
-rw-r--r--server/resty/session/storage/memcache.lua303
-rw-r--r--server/resty/session/storage/memcached.lua1
-rw-r--r--server/resty/session/storage/redis.lua478
-rw-r--r--server/resty/session/storage/shm.lua125
-rw-r--r--server/resty/session/strategies/default.lua232
-rw-r--r--server/resty/session/strategies/regenerate.lua43
18 files changed, 1627 insertions, 0 deletions
diff --git a/server/resty/session/ciphers/aes.lua b/server/resty/session/ciphers/aes.lua
new file mode 100644
index 0000000..9a088ad
--- /dev/null
+++ b/server/resty/session/ciphers/aes.lua
@@ -0,0 +1,113 @@
+local aes = require "resty.aes"
+
+local setmetatable = setmetatable
+local tonumber = tonumber
+local ceil = math.ceil
+local var = ngx.var
+local sub = string.sub
+local rep = string.rep
+
+local HASHES = aes.hash
+
+local CIPHER_MODES = {
+ ecb = "ecb",
+ cbc = "cbc",
+ cfb1 = "cfb1",
+ cfb8 = "cfb8",
+ cfb128 = "cfb128",
+ ofb = "ofb",
+ ctr = "ctr",
+ gcm = "gcm",
+}
+
+local CIPHER_SIZES = {
+ [128] = 128,
+ [192] = 192,
+ [256] = 256,
+}
+
+local defaults = {
+ size = CIPHER_SIZES[tonumber(var.session_aes_size, 10)] or 256,
+ mode = CIPHER_MODES[var.session_aes_mode] or "cbc",
+ hash = HASHES[var.session_aes_hash] or HASHES.sha512,
+ rounds = tonumber(var.session_aes_rounds, 10) or 1,
+}
+
+local function adjust_salt(salt)
+ if not salt then
+ return nil
+ end
+
+ local z = #salt
+ if z < 8 then
+ return sub(rep(salt, ceil(8 / z)), 1, 8)
+ end
+ if z > 8 then
+ return sub(salt, 1, 8)
+ end
+
+ return salt
+end
+
+local function get_cipher(self, key, salt)
+ local mode = aes.cipher(self.size, self.mode)
+ if not mode then
+ return nil, "invalid cipher mode " .. self.mode .. "(" .. self.size .. ")"
+ end
+
+ return aes:new(key, adjust_salt(salt), mode, self.hash, self.rounds)
+end
+
+local cipher = {}
+
+cipher.__index = cipher
+
+function cipher.new(session)
+ local config = session.aes or defaults
+ return setmetatable({
+ size = CIPHER_SIZES[tonumber(config.size, 10)] or defaults.size,
+ mode = CIPHER_MODES[config.mode] or defaults.mode,
+ hash = HASHES[config.hash] or defaults.hash,
+ rounds = tonumber(config.rounds, 10) or defaults.rounds,
+ }, cipher)
+end
+
+function cipher:encrypt(data, key, salt, _)
+ local cip, err = get_cipher(self, key, salt)
+ if not cip then
+ return nil, err or "unable to aes encrypt data"
+ end
+
+ local encrypted_data
+ encrypted_data, err = cip:encrypt(data)
+ if not encrypted_data then
+ return nil, err or "aes encryption failed"
+ end
+
+ if self.mode == "gcm" then
+ return encrypted_data[1], nil, encrypted_data[2]
+ end
+
+ return encrypted_data
+end
+
+function cipher:decrypt(data, key, salt, _, tag)
+ local cip, err = get_cipher(self, key, salt)
+ if not cip then
+ return nil, err or "unable to aes decrypt data"
+ end
+
+ local decrypted_data
+ decrypted_data, err = cip:decrypt(data, tag)
+ if not decrypted_data then
+ return nil, err or "aes decryption failed"
+ end
+
+ if self.mode == "gcm" then
+ return decrypted_data, nil, tag
+ end
+
+ return decrypted_data
+end
+
+return cipher
diff --git a/server/resty/session/ciphers/none.lua b/server/resty/session/ciphers/none.lua
new file mode 100644
index 0000000..b29bb88
--- /dev/null
+++ b/server/resty/session/ciphers/none.lua
@@ -0,0 +1,15 @@
+local cipher = {}
+
+function cipher.new()
+ return cipher
+end
+
+function cipher.encrypt(_, data, _, _)
+ return data
+end
+
+function cipher.decrypt(_, data, _, _, _)
+ return data
+end
+
+return cipher
diff --git a/server/resty/session/compressors/none.lua b/server/resty/session/compressors/none.lua
new file mode 100644
index 0000000..3d14a5c
--- /dev/null
+++ b/server/resty/session/compressors/none.lua
@@ -0,0 +1,15 @@
+local compressor = {}
+
+function compressor.new()
+ return compressor
+end
+
+function compressor.compress(_, data)
+ return data
+end
+
+function compressor.decompress(_, data)
+ return data
+end
+
+return compressor
diff --git a/server/resty/session/compressors/zlib.lua b/server/resty/session/compressors/zlib.lua
new file mode 100644
index 0000000..1d23be0
--- /dev/null
+++ b/server/resty/session/compressors/zlib.lua
@@ -0,0 +1,43 @@
+local zlib = require "ffi-zlib"
+local sio = require "pl.stringio"
+
+local concat = table.concat
+
+local function gzip(func, input)
+ local stream = sio.open(input)
+ local output = {}
+ local n = 0
+
+ local ok, err = func(function(size)
+ return stream:read(size)
+ end, function(data)
+ n = n + 1
+ output[n] = data
+ end, 8192)
+
+ if not ok then
+ return nil, err
+ end
+
+ if n == 0 then
+ return ""
+ end
+
+ return concat(output, nil, 1, n)
+end
+
+local compressor = {}
+
+function compressor.new()
+ return compressor
+end
+
+function compressor.compress(_, data)
+ return gzip(zlib.deflateGzip, data)
+end
+
+function compressor.decompress(_, data)
+ return gzip(zlib.inflateGzip, data)
+end
+
+return compressor
diff --git a/server/resty/session/encoders/base16.lua b/server/resty/session/encoders/base16.lua
new file mode 100644
index 0000000..552f50e
--- /dev/null
+++ b/server/resty/session/encoders/base16.lua
@@ -0,0 +1,29 @@
+local to_hex = require "resty.string".to_hex
+
+local tonumber = tonumber
+local gsub = string.gsub
+local char = string.char
+
+local function chr(c)
+ return char(tonumber(c, 16) or 0)
+end
+
+local encoder = {}
+
+function encoder.encode(value)
+ if not value then
+ return nil, "unable to base16 encode value"
+ end
+
+ return to_hex(value)
+end
+
+function encoder.decode(value)
+ if not value then
+ return nil, "unable to base16 decode value"
+ end
+
+ return (gsub(value, "..", chr))
+end
+
+return encoder
diff --git a/server/resty/session/encoders/base64.lua b/server/resty/session/encoders/base64.lua
new file mode 100644
index 0000000..ddaf4e8
--- /dev/null
+++ b/server/resty/session/encoders/base64.lua
@@ -0,0 +1,39 @@
+local encode_base64 = ngx.encode_base64
+local decode_base64 = ngx.decode_base64
+
+local gsub = string.gsub
+
+local ENCODE_CHARS = {
+ ["+"] = "-",
+ ["/"] = "_",
+}
+
+local DECODE_CHARS = {
+ ["-"] = "+",
+ ["_"] = "/",
+}
+
+local encoder = {}
+
+function encoder.encode(value)
+ if not value then
+ return nil, "unable to base64 encode value"
+ end
+
+ local encoded = encode_base64(value, true)
+ if not encoded then
+ return nil, "unable to base64 encode value"
+ end
+
+ return gsub(encoded, "[+/]", ENCODE_CHARS)
+end
+
+function encoder.decode(value)
+ if not value then
+ return nil, "unable to base64 decode value"
+ end
+
+ return decode_base64((gsub(value, "[-_]", DECODE_CHARS)))
+end
+
+return encoder
diff --git a/server/resty/session/encoders/hex.lua b/server/resty/session/encoders/hex.lua
new file mode 100644
index 0000000..1b94a5a
--- /dev/null
+++ b/server/resty/session/encoders/hex.lua
@@ -0,0 +1 @@
+return require "resty.session.encoders.base16" \ No newline at end of file
diff --git a/server/resty/session/hmac/sha1.lua b/server/resty/session/hmac/sha1.lua
new file mode 100644
index 0000000..1753412
--- /dev/null
+++ b/server/resty/session/hmac/sha1.lua
@@ -0,0 +1 @@
+return ngx.hmac_sha1
diff --git a/server/resty/session/identifiers/random.lua b/server/resty/session/identifiers/random.lua
new file mode 100644
index 0000000..a2f9739
--- /dev/null
+++ b/server/resty/session/identifiers/random.lua
@@ -0,0 +1,13 @@
+local tonumber = tonumber
+local random = require "resty.random".bytes
+local var = ngx.var
+
+local defaults = {
+ length = tonumber(var.session_random_length, 10) or 16
+}
+
+return function(session)
+ local config = session.random or defaults
+ local length = tonumber(config.length, 10) or defaults.length
+ return random(length, true) or random(length)
+end
diff --git a/server/resty/session/serializers/json.lua b/server/resty/session/serializers/json.lua
new file mode 100644
index 0000000..960c4d8
--- /dev/null
+++ b/server/resty/session/serializers/json.lua
@@ -0,0 +1,6 @@
+local json = require "cjson.safe"
+
+return {
+ serialize = json.encode,
+ deserialize = json.decode,
+}
diff --git a/server/resty/session/storage/cookie.lua b/server/resty/session/storage/cookie.lua
new file mode 100644
index 0000000..95e26d1
--- /dev/null
+++ b/server/resty/session/storage/cookie.lua
@@ -0,0 +1,7 @@
+local storage = {}
+
+function storage.new()
+ return storage
+end
+
+return storage
diff --git a/server/resty/session/storage/dshm.lua b/server/resty/session/storage/dshm.lua
new file mode 100644
index 0000000..e6d887f
--- /dev/null
+++ b/server/resty/session/storage/dshm.lua
@@ -0,0 +1,163 @@
+local dshm = require "resty.dshm"
+
+local setmetatable = setmetatable
+local tonumber = tonumber
+local concat = table.concat
+local var = ngx.var
+
+local defaults = {
+ region = var.session_dshm_region or "sessions",
+ connect_timeout = tonumber(var.session_dshm_connect_timeout, 10),
+ read_timeout = tonumber(var.session_dshm_read_timeout, 10),
+ send_timeout = tonumber(var.session_dshm_send_timeout, 10),
+ host = var.session_dshm_host or "127.0.0.1",
+ port = tonumber(var.session_dshm_port, 10) or 4321,
+ pool = {
+ name = var.session_dshm_pool_name,
+ size = tonumber(var.session_dshm_pool_size, 10) or 100,
+ timeout = tonumber(var.session_dshm_pool_timeout, 10) or 1000,
+ backlog = tonumber(var.session_dshm_pool_backlog, 10),
+ },
+}
+
+local storage = {}
+
+storage.__index = storage
+
+function storage.new(session)
+ local config = session.dshm or defaults
+ local pool = config.pool or defaults.pool
+
+ local connect_timeout = tonumber(config.connect_timeout, 10) or defaults.connect_timeout
+
+ local store = dshm:new()
+ if store.set_timeouts then
+ local send_timeout = tonumber(config.send_timeout, 10) or defaults.send_timeout
+ local read_timeout = tonumber(config.read_timeout, 10) or defaults.read_timeout
+
+ if connect_timeout then
+ if send_timeout and read_timeout then
+ store:set_timeouts(connect_timeout, send_timeout, read_timeout)
+ else
+ store:set_timeout(connect_timeout)
+ end
+ end
+
+ elseif store.set_timeout and connect_timeout then
+ store:set_timeout(connect_timeout)
+ end
+
+
+ local self = {
+ store = store,
+ encoder = session.encoder,
+ region = config.region or defaults.region,
+ host = config.host or defaults.host,
+ port = tonumber(config.port, 10) or defaults.port,
+ pool_timeout = tonumber(pool.timeout, 10) or defaults.pool.timeout,
+ connect_opts = {
+ pool = pool.name or defaults.pool.name,
+ pool_size = tonumber(pool.size, 10) or defaults.pool.size,
+ backlog = tonumber(pool.backlog, 10) or defaults.pool.backlog,
+ },
+ }
+
+ return setmetatable(self, storage)
+end
+
+function storage:connect()
+ return self.store:connect(self.host, self.port, self.connect_opts)
+end
+
+function storage:set_keepalive()
+ return self.store:set_keepalive(self.pool_timeout)
+end
+
+function storage:key(id)
+ return concat({ self.region, id }, "::")
+end
+
+function storage:set(key, ttl, data)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ data, err = self.encoder.encode(data)
+
+ if not data then
+ self:set_keepalive()
+ return nil, err
+ end
+
+ ok, err = self.store:set(key, data, ttl)
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:get(key)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local data
+ data, err = self.store:get(key)
+ if data then
+ data, err = self.encoder.decode(data)
+ end
+
+ self:set_keepalive()
+
+ return data, err
+end
+
+function storage:delete(key)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ ok, err = self.store:delete(key)
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:touch(key, ttl)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ ok, err = self.store:touch(key, ttl)
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:open(id)
+ local key = self:key(id)
+ return self:get(key)
+end
+
+function storage:save(id, ttl, data)
+ local key = self:key(id)
+ return self:set(key, ttl, data)
+end
+
+function storage:destroy(id)
+ local key = self:key(id)
+ return self:delete(key)
+end
+
+function storage:ttl(id, ttl)
+ local key = self:key(id)
+ return self:touch(key, ttl)
+end
+
+return storage
diff --git a/server/resty/session/storage/memcache.lua b/server/resty/session/storage/memcache.lua
new file mode 100644
index 0000000..da44ba7
--- /dev/null
+++ b/server/resty/session/storage/memcache.lua
@@ -0,0 +1,303 @@
+local memcached = require "resty.memcached"
+local setmetatable = setmetatable
+local tonumber = tonumber
+local concat = table.concat
+local sleep = ngx.sleep
+local null = ngx.null
+local var = ngx.var
+
+local function enabled(value)
+ if value == nil then
+ return nil
+ end
+
+ return value == true
+ or value == "1"
+ or value == "true"
+ or value == "on"
+end
+
+local function ifnil(value, default)
+ if value == nil then
+ return default
+ end
+
+ return enabled(value)
+end
+
+local defaults = {
+ prefix = var.session_memcache_prefix or "sessions",
+ socket = var.session_memcache_socket,
+ host = var.session_memcache_host or "127.0.0.1",
+ uselocking = enabled(var.session_memcache_uselocking or true),
+ connect_timeout = tonumber(var.session_memcache_connect_timeout, 10),
+ read_timeout = tonumber(var.session_memcache_read_timeout, 10),
+ send_timeout = tonumber(var.session_memcache_send_timeout, 10),
+ port = tonumber(var.session_memcache_port, 10) or 11211,
+ spinlockwait = tonumber(var.session_memcache_spinlockwait, 10) or 150,
+ maxlockwait = tonumber(var.session_memcache_maxlockwait, 10) or 30,
+ pool = {
+ name = var.session_memcache_pool_name,
+ timeout = tonumber(var.session_memcache_pool_timeout, 10),
+ size = tonumber(var.session_memcache_pool_size, 10),
+ backlog = tonumber(var.session_memcache_pool_backlog, 10),
+ },
+}
+
+local storage = {}
+
+storage.__index = storage
+
+function storage.new(session)
+ local config = session.memcache or defaults
+ local pool = config.pool or defaults.pool
+ local locking = ifnil(config.uselocking, defaults.uselocking)
+
+ local connect_timeout = tonumber(config.connect_timeout, 10) or defaults.connect_timeout
+
+ local memcache = memcached:new()
+ if memcache.set_timeouts then
+ local send_timeout = tonumber(config.send_timeout, 10) or defaults.send_timeout
+ local read_timeout = tonumber(config.read_timeout, 10) or defaults.read_timeout
+
+ if connect_timeout then
+ if send_timeout and read_timeout then
+ memcache:set_timeouts(connect_timeout, send_timeout, read_timeout)
+ else
+ memcache:set_timeout(connect_timeout)
+ end
+ end
+
+ elseif memcache.set_timeout and connect_timeout then
+ memcache:set_timeout(connect_timeout)
+ end
+
+ local self = {
+ memcache = memcache,
+ prefix = config.prefix or defaults.prefix,
+ uselocking = locking,
+ spinlockwait = tonumber(config.spinlockwait, 10) or defaults.spinlockwait,
+ maxlockwait = tonumber(config.maxlockwait, 10) or defaults.maxlockwait,
+ pool_timeout = tonumber(pool.timeout, 10) or defaults.pool.timeout,
+ connect_opts = {
+ pool = pool.name or defaults.pool.name,
+ pool_size = tonumber(pool.size, 10) or defaults.pool.size,
+ backlog = tonumber(pool.backlog, 10) or defaults.pool.backlog,
+ },
+ }
+
+ local socket = config.socket or defaults.socket
+ if socket and socket ~= "" then
+ self.socket = socket
+ else
+ self.host = config.host or defaults.host
+ self.port = config.port or defaults.port
+ end
+
+ return setmetatable(self, storage)
+end
+
+function storage:connect()
+ local socket = self.socket
+ if socket then
+ return self.memcache:connect(socket, self.connect_opts)
+ end
+ return self.memcache:connect(self.host, self.port, self.connect_opts)
+end
+
+function storage:set_keepalive()
+ return self.memcache:set_keepalive(self.pool_timeout)
+end
+
+function storage:key(id)
+ return concat({ self.prefix, id }, ":" )
+end
+
+function storage:lock(key)
+ if not self.uselocking or self.locked then
+ return true
+ end
+
+ if not self.token then
+ self.token = var.request_id
+ end
+
+ local lock_key = concat({ key, "lock" }, "." )
+ local lock_ttl = self.maxlockwait + 1
+ local attempts = (1000 / self.spinlockwait) * self.maxlockwait
+ local waittime = self.spinlockwait / 1000
+
+ for _ = 1, attempts do
+ local ok = self.memcache:add(lock_key, self.token, lock_ttl)
+ if ok then
+ self.locked = true
+ return true
+ end
+
+ sleep(waittime)
+ end
+
+ return false, "unable to acquire a session lock"
+end
+
+function storage:unlock(key)
+ if not self.uselocking or not self.locked then
+ return true
+ end
+
+ local lock_key = concat({ key, "lock" }, "." )
+ local token = self:get(lock_key)
+
+ if token == self.token then
+ self.memcache:delete(lock_key)
+ self.locked = nil
+ end
+end
+
+function storage:get(key)
+ local data, err = self.memcache:get(key)
+ if not data then
+ return nil, err
+ end
+
+ if data == null then
+ return nil
+ end
+
+ return data
+end
+
+function storage:set(key, data, ttl)
+ return self.memcache:set(key, data, ttl)
+end
+
+function storage:expire(key, ttl)
+ return self.memcache:touch(key, ttl)
+end
+
+function storage:delete(key)
+ return self.memcache:delete(key)
+end
+
+function storage:open(id, keep_lock)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:lock(key)
+ if not ok then
+ self:set_keepalive()
+ return nil, err
+ end
+
+ local data
+ data, err = self:get(key)
+
+ if err or not data or not keep_lock then
+ self:unlock(key)
+ end
+
+ self:set_keepalive()
+
+ return data, err
+end
+
+function storage:start(id)
+ if not self.uselocking or not self.locked then
+ return true
+ end
+
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:lock(key)
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:save(id, ttl, data, close)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:set(key, data, ttl)
+
+ if close then
+ self:unlock(key)
+ end
+
+ self:set_keepalive()
+
+ if not ok then
+ return nil, err
+ end
+
+ return true
+end
+
+function storage:close(id)
+ if not self.uselocking or not self.locked then
+ return true
+ end
+
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ self:unlock(key)
+ self:set_keepalive()
+
+ return true
+end
+
+function storage:destroy(id)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:delete(key)
+
+ self:unlock(key)
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:ttl(id, ttl, close)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:expire(key, ttl)
+
+ if close then
+ self:unlock(key)
+ end
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+return storage
diff --git a/server/resty/session/storage/memcached.lua b/server/resty/session/storage/memcached.lua
new file mode 100644
index 0000000..0ecc508
--- /dev/null
+++ b/server/resty/session/storage/memcached.lua
@@ -0,0 +1 @@
+return require "resty.session.storage.memcache"
diff --git a/server/resty/session/storage/redis.lua b/server/resty/session/storage/redis.lua
new file mode 100644
index 0000000..3de0472
--- /dev/null
+++ b/server/resty/session/storage/redis.lua
@@ -0,0 +1,478 @@
+local setmetatable = setmetatable
+local tonumber = tonumber
+local type = type
+local reverse = string.reverse
+local gmatch = string.gmatch
+local find = string.find
+local byte = string.byte
+local sub = string.sub
+local concat = table.concat
+local sleep = ngx.sleep
+local null = ngx.null
+local var = ngx.var
+
+local LB = byte("[")
+local RB = byte("]")
+
+local function parse_cluster_nodes(nodes)
+ if not nodes or nodes == "" then
+ return nil
+ end
+
+ if type(nodes) == "table" then
+ return nodes
+ end
+
+ local addrs
+ local i
+ for node in gmatch(nodes, "%S+") do
+ local ip = node
+ local port = 6379
+ local pos = find(reverse(ip), ":", 2, true)
+ if pos then
+ local p = tonumber(sub(ip, -pos + 1), 10)
+ if p >= 1 and p <= 65535 then
+ local addr = sub(ip, 1, -pos - 1)
+ if find(addr, ":", 1, true) then
+ if byte(addr, -1) == RB then
+ ip = addr
+ port = p
+ end
+
+ else
+ ip = addr
+ port = p
+ end
+ end
+ end
+
+ if byte(ip, 1, 1) == LB then
+ ip = sub(ip, 2)
+ end
+
+ if byte(ip, -1) == RB then
+ ip = sub(ip, 1, -2)
+ end
+
+ if not addrs then
+ i = 1
+ addrs = {{
+ ip = ip,
+ port = port,
+ }}
+ else
+ i = i + 1
+ addrs[i] = {
+ ip = ip,
+ port = port,
+ }
+ end
+ end
+
+ if not i then
+ return
+ end
+
+ return addrs
+end
+
+local redis_single = require "resty.redis"
+local redis_cluster
+do
+ local pcall = pcall
+ local require = require
+ local ok
+ ok, redis_cluster = pcall(require, "resty.rediscluster")
+ if not ok then
+ ok, redis_cluster = pcall(require, "rediscluster")
+ if not ok then
+ redis_cluster = nil
+ end
+ end
+end
+
+local UNLOCK = [[
+if redis.call("GET", KEYS[1]) == ARGV[1] then
+ return redis.call("DEL", KEYS[1])
+else
+ return 0
+end
+]]
+
+local function enabled(value)
+ if value == nil then return nil end
+ return value == true or (value == "1" or value == "true" or value == "on")
+end
+
+local function ifnil(value, default)
+ if value == nil then
+ return default
+ end
+
+ return enabled(value)
+end
+
+local defaults = {
+ prefix = var.session_redis_prefix or "sessions",
+ socket = var.session_redis_socket,
+ host = var.session_redis_host or "127.0.0.1",
+ username = var.session_redis_username,
+ password = var.session_redis_password or var.session_redis_auth,
+ server_name = var.session_redis_server_name,
+ ssl = enabled(var.session_redis_ssl) or false,
+ ssl_verify = enabled(var.session_redis_ssl_verify) or false,
+ uselocking = enabled(var.session_redis_uselocking or true),
+ port = tonumber(var.session_redis_port, 10) or 6379,
+ database = tonumber(var.session_redis_database, 10) or 0,
+ connect_timeout = tonumber(var.session_redis_connect_timeout, 10),
+ read_timeout = tonumber(var.session_redis_read_timeout, 10),
+ send_timeout = tonumber(var.session_redis_send_timeout, 10),
+ spinlockwait = tonumber(var.session_redis_spinlockwait, 10) or 150,
+ maxlockwait = tonumber(var.session_redis_maxlockwait, 10) or 30,
+ pool = {
+ name = var.session_redis_pool_name,
+ timeout = tonumber(var.session_redis_pool_timeout, 10),
+ size = tonumber(var.session_redis_pool_size, 10),
+ backlog = tonumber(var.session_redis_pool_backlog, 10),
+ },
+}
+
+
+if redis_cluster then
+ defaults.cluster = {
+ name = var.session_redis_cluster_name,
+ dict = var.session_redis_cluster_dict,
+ maxredirections = tonumber(var.session_redis_cluster_maxredirections, 10),
+ nodes = parse_cluster_nodes(var.session_redis_cluster_nodes),
+ }
+end
+
+local storage = {}
+
+storage.__index = storage
+
+function storage.new(session)
+ local config = session.redis or defaults
+ local pool = config.pool or defaults.pool
+ local cluster = config.cluster or defaults.cluster
+ local locking = ifnil(config.uselocking, defaults.uselocking)
+
+ local self = {
+ prefix = config.prefix or defaults.prefix,
+ uselocking = locking,
+ spinlockwait = tonumber(config.spinlockwait, 10) or defaults.spinlockwait,
+ maxlockwait = tonumber(config.maxlockwait, 10) or defaults.maxlockwait,
+ }
+
+ local username = config.username or defaults.username
+ if username == "" then
+ username = nil
+ end
+ local password = config.password or config.auth or defaults.password
+ if password == "" then
+ password = nil
+ end
+
+ local connect_timeout = tonumber(config.connect_timeout, 10) or defaults.connect_timeout
+
+ local cluster_nodes
+ if redis_cluster then
+ cluster_nodes = parse_cluster_nodes(cluster.nodes or defaults.cluster.nodes)
+ end
+
+ local connect_opts = {
+ pool = pool.name or defaults.pool.name,
+ pool_size = tonumber(pool.size, 10) or defaults.pool.size,
+ backlog = tonumber(pool.backlog, 10) or defaults.pool.backlog,
+ server_name = config.server_name or defaults.server_name,
+ ssl = ifnil(config.ssl, defaults.ssl),
+ ssl_verify = ifnil(config.ssl_verify, defaults.ssl_verify),
+ }
+
+ if cluster_nodes then
+ self.redis = redis_cluster:new({
+ name = cluster.name or defaults.cluster.name,
+ dict_name = cluster.dict or defaults.cluster.dict,
+ username = var.session_redis_username,
+ password = var.session_redis_password or defaults.password,
+ connection_timout = connect_timeout, -- typo in library
+ connection_timeout = connect_timeout,
+ keepalive_timeout = tonumber(pool.timeout, 10) or defaults.pool.timeout,
+ keepalive_cons = tonumber(pool.size, 10) or defaults.pool.size,
+ max_redirection = tonumber(cluster.maxredirections, 10) or defaults.cluster.maxredirections,
+ serv_list = cluster_nodes,
+ connect_opts = connect_opts,
+ })
+ self.cluster = true
+
+ else
+ local redis = redis_single:new()
+
+ if redis.set_timeouts then
+ local send_timeout = tonumber(config.send_timeout, 10) or defaults.send_timeout
+ local read_timeout = tonumber(config.read_timeout, 10) or defaults.read_timeout
+
+ if connect_timeout then
+ if send_timeout and read_timeout then
+ redis:set_timeouts(connect_timeout, send_timeout, read_timeout)
+ else
+ redis:set_timeout(connect_timeout)
+ end
+ end
+
+ elseif redis.set_timeout and connect_timeout then
+ redis:set_timeout(connect_timeout)
+ end
+
+ self.redis = redis
+ self.username = username
+ self.password = password
+ self.database = tonumber(config.database, 10) or defaults.database
+ self.pool_timeout = tonumber(pool.timeout, 10) or defaults.pool.timeout
+ self.connect_opts = connect_opts
+
+ local socket = config.socket or defaults.socket
+ if socket and socket ~= "" then
+ self.socket = socket
+ else
+ self.host = config.host or defaults.host
+ self.port = config.port or defaults.port
+ end
+ end
+
+ return setmetatable(self, storage)
+end
+
+function storage:connect()
+ if self.cluster then
+ return true -- cluster handles this on its own
+ end
+
+ local ok, err
+ if self.socket then
+ ok, err = self.redis:connect(self.socket, self.connect_opts)
+ else
+ ok, err = self.redis:connect(self.host, self.port, self.connect_opts)
+ end
+
+ if not ok then
+ return nil, err
+ end
+
+ if self.password and self.redis:get_reused_times() == 0 then
+ -- usernames are supported only on Redis 6+, so use new AUTH form only when absolutely necessary
+ if self.username then
+ ok, err = self.redis:auth(self.username, self.password)
+ else
+ ok, err = self.redis:auth(self.password)
+ end
+ if not ok then
+ self.redis:close()
+ return nil, err
+ end
+ end
+
+ if self.database ~= 0 then
+ ok, err = self.redis:select(self.database)
+ if not ok then
+ self.redis:close()
+ end
+ end
+
+ return ok, err
+end
+
+function storage:set_keepalive()
+ if self.cluster then
+ return true -- cluster handles this on its own
+ end
+
+ return self.redis:set_keepalive(self.pool_timeout)
+end
+
+function storage:key(id)
+ return concat({ self.prefix, id }, ":" )
+end
+
+function storage:lock(key)
+ if not self.uselocking or self.locked then
+ return true
+ end
+
+ if not self.token then
+ self.token = var.request_id
+ end
+
+ local lock_key = concat({ key, "lock" }, "." )
+ local lock_ttl = self.maxlockwait + 1
+ local attempts = (1000 / self.spinlockwait) * self.maxlockwait
+ local waittime = self.spinlockwait / 1000
+
+ for _ = 1, attempts do
+ local ok = self.redis:set(lock_key, self.token, "EX", lock_ttl, "NX")
+ if ok ~= null then
+ self.locked = true
+ return true
+ end
+
+ sleep(waittime)
+ end
+
+ return false, "unable to acquire a session lock"
+end
+
+function storage:unlock(key)
+ if not self.uselocking or not self.locked then
+ return
+ end
+
+ local lock_key = concat({ key, "lock" }, "." )
+
+ self.redis:eval(UNLOCK, 1, lock_key, self.token)
+ self.locked = nil
+end
+
+function storage:get(key)
+ local data, err = self.redis:get(key)
+ if not data then
+ return nil, err
+ end
+
+ if data == null then
+ return nil
+ end
+
+ return data
+end
+
+function storage:set(key, data, lifetime)
+ return self.redis:setex(key, lifetime, data)
+end
+
+function storage:expire(key, lifetime)
+ return self.redis:expire(key, lifetime)
+end
+
+function storage:delete(key)
+ return self.redis:del(key)
+end
+
+function storage:open(id, keep_lock)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:lock(key)
+ if not ok then
+ self:set_keepalive()
+ return nil, err
+ end
+
+ local data
+ data, err = self:get(key)
+
+ if err or not data or not keep_lock then
+ self:unlock(key)
+ end
+ self:set_keepalive()
+
+ return data, err
+end
+
+function storage:start(id)
+ if not self.uselocking or not self.locked then
+ return true
+ end
+
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ ok, err = self:lock(self:key(id))
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:save(id, ttl, data, close)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:set(key, data, ttl)
+
+ if close then
+ self:unlock(key)
+ end
+
+ self:set_keepalive()
+
+ if not ok then
+ return nil, err
+ end
+
+ return true
+end
+
+function storage:close(id)
+ if not self.uselocking or not self.locked then
+ return true
+ end
+
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ self:unlock(key)
+ self:set_keepalive()
+
+ return true
+end
+
+function storage:destroy(id)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:delete(key)
+
+ self:unlock(key)
+ self:set_keepalive()
+
+ return ok, err
+end
+
+function storage:ttl(id, ttl, close)
+ local ok, err = self:connect()
+ if not ok then
+ return nil, err
+ end
+
+ local key = self:key(id)
+
+ ok, err = self:expire(key, ttl)
+
+ if close then
+ self:unlock(key)
+ end
+
+ self:set_keepalive()
+
+ return ok, err
+end
+
+return storage
diff --git a/server/resty/session/storage/shm.lua b/server/resty/session/storage/shm.lua
new file mode 100644
index 0000000..6f81435
--- /dev/null
+++ b/server/resty/session/storage/shm.lua
@@ -0,0 +1,125 @@
+local lock = require "resty.lock"
+
+local setmetatable = setmetatable
+local tonumber = tonumber
+local concat = table.concat
+local var = ngx.var
+local shared = ngx.shared
+
+local function enabled(value)
+ if value == nil then return nil end
+ return value == true or (value == "1" or value == "true" or value == "on")
+end
+
+local function ifnil(value, default)
+ if value == nil then
+ return default
+ end
+
+ return enabled(value)
+end
+
+local defaults = {
+ store = var.session_shm_store or "sessions",
+ uselocking = enabled(var.session_shm_uselocking or true),
+ lock = {
+ exptime = tonumber(var.session_shm_lock_exptime, 10) or 30,
+ timeout = tonumber(var.session_shm_lock_timeout, 10) or 5,
+ step = tonumber(var.session_shm_lock_step, 10) or 0.001,
+ ratio = tonumber(var.session_shm_lock_ratio, 10) or 2,
+ max_step = tonumber(var.session_shm_lock_max_step, 10) or 0.5,
+ }
+}
+
+local storage = {}
+
+storage.__index = storage
+
+function storage.new(session)
+ local config = session.shm or defaults
+ local store = config.store or defaults.store
+ local locking = ifnil(config.uselocking, defaults.uselocking)
+
+ local self = {
+ store = shared[store],
+ uselocking = locking,
+ }
+
+ if locking then
+ local lock_opts = config.lock or defaults.lock
+ local opts = {
+ exptime = tonumber(lock_opts.exptime, 10) or defaults.exptime,
+ timeout = tonumber(lock_opts.timeout, 10) or defaults.timeout,
+ step = tonumber(lock_opts.step, 10) or defaults.step,
+ ratio = tonumber(lock_opts.ratio, 10) or defaults.ratio,
+ max_step = tonumber(lock_opts.max_step, 10) or defaults.max_step,
+ }
+ self.lock = lock:new(store, opts)
+ end
+
+ return setmetatable(self, storage)
+end
+
+function storage:open(id, keep_lock)
+ if self.uselocking then
+ local ok, err = self.lock:lock(concat{ id, ".lock" })
+ if not ok then
+ return nil, err
+ end
+ end
+
+ local data, err = self.store:get(id)
+
+ if self.uselocking and (err or not data or not keep_lock) then
+ self.lock:unlock()
+ end
+
+ return data, err
+end
+
+function storage:start(id)
+ if self.uselocking then
+ return self.lock:lock(concat{ id, ".lock" })
+ end
+
+ return true
+end
+
+function storage:save(id, ttl, data, close)
+ local ok, err = self.store:set(id, data, ttl)
+ if close and self.uselocking then
+ self.lock:unlock()
+ end
+
+ return ok, err
+end
+
+function storage:close()
+ if self.uselocking then
+ self.lock:unlock()
+ end
+
+ return true
+end
+
+function storage:destroy(id)
+ self.store:delete(id)
+
+ if self.uselocking then
+ self.lock:unlock()
+ end
+
+ return true
+end
+
+function storage:ttl(id, lifetime, close)
+ local ok, err = self.store:expire(id, lifetime)
+
+ if close and self.uselocking then
+ self.lock:unlock()
+ end
+
+ return ok, err
+end
+
+return storage
diff --git a/server/resty/session/strategies/default.lua b/server/resty/session/strategies/default.lua
new file mode 100644
index 0000000..a43ef5a
--- /dev/null
+++ b/server/resty/session/strategies/default.lua
@@ -0,0 +1,232 @@
+local type = type
+local concat = table.concat
+
+local strategy = {}
+
+function strategy.load(session, cookie, key, keep_lock)
+ local storage = session.storage
+ local id = cookie.id
+ local id_encoded = session.encoder.encode(id)
+
+ local data, err, tag
+ if storage.open then
+ data, err = storage:open(id_encoded, keep_lock)
+ if not data then
+ return nil, err or "cookie data was not found"
+ end
+
+ else
+ data = cookie.data
+ end
+
+ local expires = cookie.expires
+ local usebefore = cookie.usebefore
+ local hash = cookie.hash
+
+ if not key then
+ key = concat{ id, expires, usebefore }
+ end
+
+ local hkey = session.hmac(session.secret, key)
+
+ data, err, tag = session.cipher:decrypt(data, hkey, id, session.key, hash)
+ if not data then
+ if storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, err or "unable to decrypt data"
+ end
+
+ if tag then
+ if tag ~= hash then
+ if storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, "cookie has invalid tag"
+ end
+
+ else
+ local input = concat{ key, data, session.key }
+ if session.hmac(hkey, input) ~= hash then
+ if storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, "cookie has invalid signature"
+ end
+ end
+
+ data, err = session.compressor:decompress(data)
+ if not data then
+ if storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, err or "unable to decompress data"
+ end
+
+ data, err = session.serializer.deserialize(data)
+ if not data then
+ if storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, err or "unable to deserialize data"
+ end
+
+ session.id = id
+ session.expires = expires
+ session.usebefore = usebefore
+ session.data = type(data) == "table" and data or {}
+ session.present = true
+
+ return true
+end
+
+function strategy.open(session, cookie, keep_lock)
+ return strategy.load(session, cookie, nil, keep_lock)
+end
+
+function strategy.start(session)
+ local storage = session.storage
+ if not storage.start then
+ return true
+ end
+
+ local id_encoded = session.encoder.encode(session.id)
+
+ local ok, err = storage:start(id_encoded)
+ if not ok then
+ return nil, err or "unable to start session"
+ end
+
+ return true
+end
+
+function strategy.modify(session, action, close, key)
+ local id = session.id
+ local id_encoded = session.encoder.encode(id)
+ local storage = session.storage
+ local expires = session.expires
+ local usebefore = session.usebefore
+ local ttl = expires - session.now
+
+ if ttl <= 0 then
+ if storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, "session is already expired"
+ end
+
+ if not key then
+ key = concat{ id, expires, usebefore }
+ end
+
+ local data, err = session.serializer.serialize(session.data)
+ if not data then
+ if close and storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, err or "unable to serialize data"
+ end
+
+ data, err = session.compressor:compress(data)
+ if not data then
+ if close and storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, err or "unable to compress data"
+ end
+
+ local hkey = session.hmac(session.secret, key)
+
+ local encrypted_data, tag
+ encrypted_data, err, tag = session.cipher:encrypt(data, hkey, id, session.key)
+ if not encrypted_data then
+ if close and storage.close then
+ storage:close(id_encoded)
+ end
+
+ return nil, err
+ end
+
+ local hash
+ if tag then
+ hash = tag
+ else
+ -- it would be better to calculate signature from encrypted_data,
+ -- but this is kept for backward compatibility
+ hash = session.hmac(hkey, concat{ key, data, session.key })
+ end
+
+ if action == "save" and storage.save then
+ local ok
+ ok, err = storage:save(id_encoded, ttl, encrypted_data, close)
+ if not ok then
+ return nil, err
+ end
+ elseif close and storage.close then
+ local ok
+ ok, err = storage:close(id_encoded)
+ if not ok then
+ return nil, err
+ end
+ end
+
+ if usebefore then
+ expires = expires .. ":" .. usebefore
+ end
+
+ hash = session.encoder.encode(hash)
+
+ local cookie
+ if storage.save then
+ cookie = concat({ id_encoded, expires, hash }, "|")
+ else
+ local encoded_data = session.encoder.encode(encrypted_data)
+ cookie = concat({ id_encoded, expires, encoded_data, hash }, "|")
+ end
+
+ return cookie
+end
+
+function strategy.touch(session, close)
+ return strategy.modify(session, "touch", close)
+end
+
+function strategy.save(session, close)
+ return strategy.modify(session, "save", close)
+end
+
+function strategy.destroy(session)
+ local id = session.id
+ if id then
+ local storage = session.storage
+ if storage.destroy then
+ return storage:destroy(session.encoder.encode(id))
+ elseif storage.close then
+ return storage:close(session.encoder.encode(id))
+ end
+ end
+
+ return true
+end
+
+function strategy.close(session)
+ local id = session.id
+ if id then
+ local storage = session.storage
+ if storage.close then
+ return storage:close(session.encoder.encode(id))
+ end
+ end
+
+ return true
+end
+
+return strategy
diff --git a/server/resty/session/strategies/regenerate.lua b/server/resty/session/strategies/regenerate.lua
new file mode 100644
index 0000000..f2a97dd
--- /dev/null
+++ b/server/resty/session/strategies/regenerate.lua
@@ -0,0 +1,43 @@
+local default = require "resty.session.strategies.default"
+
+local concat = table.concat
+
+local strategy = {
+ regenerate = true,
+ start = default.start,
+ destroy = default.destroy,
+ close = default.close,
+}
+
+local function key(source)
+ if source.usebefore then
+ return concat{ source.id, source.usebefore }
+ end
+
+ return source.id
+end
+
+function strategy.open(session, cookie, keep_lock)
+ return default.load(session, cookie, key(cookie), keep_lock)
+end
+
+function strategy.touch(session, close)
+ return default.modify(session, "touch", close, key(session))
+end
+
+function strategy.save(session, close)
+ if session.present then
+ local storage = session.storage
+ if storage.ttl then
+ storage:ttl(session.encoder.encode(session.id), session.cookie.discard, true)
+ elseif storage.close then
+ storage:close(session.encoder.encode(session.id))
+ end
+
+ session.id = session:identifier()
+ end
+
+ return default.modify(session, "save", close, key(session))
+end
+
+return strategy