Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ Here are the possible session configuration options:
| `request_headers` | `nil` | Set of headers to send to upstream, use `id`, `audience`, `subject`, `timeout`, `idling-timeout`, `rolling-timeout`, `absolute-timeout`. E.g. `{ "id", "timeout" }` will set `Session-Id` and `Session-Timeout` request headers when `set_headers` is called. |
| `response_headers` | `nil` | Set of headers to send to downstream, use `id`, `audience`, `subject`, `timeout`, `idling-timeout`, `rolling-timeout`, `absolute-timeout`. E.g. `{ "id", "timeout" }` will set `Session-Id` and `Session-Timeout` response headers when `set_headers` is called. |
| `storage` | `nil` | Storage is responsible of storing session data, use `nil` or `"cookie"` (data is stored in cookie), `"dshm"`, `"file"`, `"memcached"`, `"mysql"`, `"postgres"`, `"redis"`, or `"shm"`, or give a name of custom module (`"custom-storage"`), or a `table` that implements session storage interface. |
| `revocation` | `nil` | Enable Redis-backed session revocation for cookie (stateless) sessions, use `nil`, `true`, `false`, a Redis configuration `table`, or a `table` that implements the revocation store interface (see below). |
| `revocation` | `nil` | Storage used for cookie session revocation records. Use `nil` or `false` to disable, a storage name such as `"shm"`, `"redis"`, `"mysql"`, or `"postgres"`, a custom storage module name, or a storage `table` with `set`/`get` methods. |
| `revocation_fail_mode` | `"open"` | Behavior when the revocation store is unreachable, use `"open"` (treat as not revoked) or `"closed"` (reject the session). |
| `dshm` | `nil` | Configuration for dshm storage, e.g. `{ prefix = "sessions" }` (see below) |
| `file` | `nil` | Configuration for file storage, e.g. `{ path = "/tmp", suffix = "session" }` (see below) |
Expand All @@ -350,22 +350,24 @@ just set the `storage` to `nil` or `"cookie"`.

Cookie (stateless) sessions are self-contained: once issued, a cookie remains
valid until it expires according to the configured timeouts. Revocation adds
an optional Redis-backed denylist so that destroyed sessions are rejected
an optional storage-backed denylist so that destroyed sessions are rejected
immediately, without waiting for the cookie to expire.

Revocation is only available when session data is stored in the cookie
(`storage` is `nil` or `"cookie"`). It must be enabled explicitly with
`revocation = true` (using the `redis` configuration) or
`revocation = { ... }` (inline Redis or custom store settings). Setting
`revocation = false` disables it.
(`storage` is `nil` or `"cookie"`). Select the backend explicitly with
`revocation = "dshm"`, `"file"`, `"memcached"`, `"mysql"`, `"postgres"`,
`"redis"`, or `"shm"`. The backend uses its normal configuration section and
the same storage `set`/`get` contract used for session data. Custom storage
module names and pre-built storage tables are also supported. Setting
`revocation = false` or leaving it unset disables revocation.

On every `session:open`, the library checks whether the session identifier is
revoked. On `session:destroy`, the identifier is written to Redis with a TTL
equal to the remaining session lifetime (rolling and absolute timeouts). The
revocation mark is a lightweight sentinel; no session payload is stored in
Redis.
revoked. On `session:destroy`, the identifier is written to the selected
storage with a TTL equal to the remaining session lifetime (rolling and
absolute timeouts). The revocation mark is a lightweight sentinel; no session
payload is stored.

Use `revocation_fail_mode` to control behavior when Redis is unreachable:
Use `revocation_fail_mode` to control behavior when the storage is unavailable:

- `"open"` (default): log a warning and treat the session as not revoked.
Destroy still clears the cookie even if the revocation write fails.
Expand All @@ -377,23 +379,44 @@ the last audience). It does not revoke the previous session identifier on
audiences). After rotation or partial logout, the previous cookie remains
usable until its `stale_ttl` or timeout elapses.

Example:
Examples:

```lua
-- Redis denylist
require("resty.session").init({
storage = "cookie",
revocation = true,
revocation = "redis",
redis = {
host = "127.0.0.1",
password = "secret",
prefix = "sessions",
},
})

-- Shared memory denylist
require("resty.session").init({
storage = "cookie",
revocation = "shm",
shm = {
zone = "sessions",
prefix = "revocations",
},
})

-- MySQL denylist
require("resty.session").init({
storage = "cookie",
revocation = "mysql",
mysql = {
host = "127.0.0.1",
database = "sessions",
username = "session",
password = "secret",
},
})
```

The `redis.mode` setting selects whether a Redis connection is used for
session data (`"storage"`) or for revocation (`"revocation"`). When unset,
it defaults to `"revocation"` for cookie storage and `"storage"` otherwise.
The same pattern works for `"dshm"`, `"file"`, `"memcached"`, and `"postgres"`.


## DSHM Storage Configuration
Expand Down Expand Up @@ -582,7 +605,6 @@ connections. Common configuration settings among them all:

| Option | Default | Description |
|---------------------|:-------:|----------------------------------------------------------------------------------------------|
| `mode` | `nil` | Role of this Redis connection: `"storage"` for session data or `"revocation"` for the session denylist. Defaults to `"revocation"` when `storage` is `nil` or `"cookie"`, otherwise `"storage"`. |
| `prefix` | `nil` | Prefix for the keys stored in Redis. |
| `suffix` | `nil` | Suffix for the keys stored in Redis. |
| `username` | `nil` | The database username to authenticate. |
Expand Down
15 changes: 6 additions & 9 deletions lib/resty/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ local encode_base64url = utils.encode_base64url
local decode_base64url = utils.decode_base64url
local table_is_empty = utils.is_empty_table
local load_storage = utils.load_storage
local load_revocation = utils.load_revocation
local encode_json = utils.encode_json
local decode_json = utils.decode_json
local base64_size = utils.base64_size
Expand Down Expand Up @@ -2434,7 +2433,7 @@ local session = {
-- @field request_headers Set of headers to send to upstream, use `id`, `audience`, `subject`, `timeout`, `idling-timeout`, `rolling-timeout`, `absolute-timeout`. E.g. `{ "id", "timeout" }` will set `Session-Id` and `Session-Timeout` request headers when `set_headers` is called.
-- @field response_headers Set of headers to send to downstream, use `id`, `audience`, `subject`, `timeout`, `idling-timeout`, `rolling-timeout`, `absolute-timeout`. E.g. `{ "id", "timeout" }` will set `Session-Id` and `Session-Timeout` response headers when `set_headers` is called.
-- @field storage Storage is responsible of storing session data, use `nil` or `"cookie"` (data is stored in cookie), `"dshm"`, `"file"`, `"memcached"`, `"mysql"`, `"postgres"`, `"redis"`, or `"shm"`, or give a name of custom module (`"custom-storage"`), or a `table` that implements session storage interface (defaults to `nil`)
-- @field revocation Session revocation backend for cookie (stateless) sessions, use `nil` (auto-load from `redis` when configured), `false` to disable, `"redis"`, `true` (alias for `"redis"`), or a pre-built store `table` with `set`/`get` methods (defaults to `nil`)
-- @field revocation Storage used for cookie session revocation records, use `nil` or `false` to disable, `"dshm"`, `"file"`, `"memcached"`, `"mysql"`, `"postgres"`, `"redis"`, or `"shm"`, a custom storage module name, or a storage `table` with `set`/`get` methods (defaults to `nil`)
-- @field revocation_fail_mode Behavior when the revocation store is unreachable, use `"open"` (treat as not revoked) or `"closed"` (reject the session) (defaults to `"open"`)
-- @field dshm Configuration for dshm storage, e.g. `{ prefix = "sessions" }`
-- @field file Configuration for file storage, e.g. `{ path = "/tmp", suffix = "session" }`
Expand Down Expand Up @@ -2497,9 +2496,6 @@ local function opt(configuration, name, default)
end
end

elseif name == "revocation" then
value = load_revocation(nil, configuration)

end

else
Expand Down Expand Up @@ -2558,10 +2554,7 @@ local function opt(configuration, name, default)
else
local t = type(value)
if t == "string" then
value = assert(load_revocation(value, configuration), "unable to load session revocation")

elseif value == true then
value = assert(load_revocation("redis", configuration), "unable to load session revocation")
value = assert(load_storage(value, configuration), "unable to load session revocation storage")

elseif t == "table" then
if type(value.set) ~= "function" or type(value.get) ~= "function" then
Expand Down Expand Up @@ -2682,6 +2675,10 @@ function session.new(configuration)
local revocation = opt(configuration, "revocation", DEFAULT_REVOCATION)
local revocation_fail_mode = opt(configuration, "revocation_fail_mode", DEFAULT_REVOCATION_FAIL_MODE)

if storage then
revocation = nil
end

if cookie_prefix == "__Host-" then
cookie_name = cookie_prefix .. cookie_name
remember_cookie_name = cookie_prefix .. remember_cookie_name
Expand Down
4 changes: 2 additions & 2 deletions lib/resty/session/file/thread.lua
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ local function get(path, prefix, suffix, name, key, current_time)
-- TODO: do we want to check expiry here?
-- The cookie header already has the info and has a MAC too.
local exp = get_modification(file_path)
if exp and exp < current_time then
return nil, "expired"
if not exp or exp < current_time then
return nil
end

return file_read(file_path)
Expand Down
6 changes: 3 additions & 3 deletions lib/resty/session/mysql.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ local DEFAULT_TABLE = "sessions"
local DEFAULT_CHARSET = "ascii"


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"
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, exp = new.exp"
local SET_META_PREFIX = "INSERT INTO %s (aud, sub, sid) VALUES "
local SET_META_VALUES = "('%s', '%s', '%s')"
local SET_META_SUFFIX = " ON DUPLICATE KEY UPDATE sid = sid"
Expand Down Expand Up @@ -193,12 +193,12 @@ function metatable:get(name, key, current_time) -- luacheck: ignore

local row = res[1]
if not row then
return nil, "session not found"
return nil
end

local data = row.data
if not row.data then
return nil, "session not found"
return nil
end

return data
Expand Down
73 changes: 0 additions & 73 deletions lib/resty/session/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -923,8 +923,6 @@ local load_storage do
elseif storage == "redis" then
local cfg = configuration and configuration.redis
if cfg then
assert(cfg.mode ~= "revocation", "invalid redis mode for session storage")

if cfg.nodes then
if not REDIS_CLUSTER then
REDIS_CLUSTER = require("resty.session.redis.cluster")
Expand Down Expand Up @@ -961,76 +959,6 @@ local load_storage do
end



local load_revocation do
local REDIS
local CUSTOM = {}

---
-- Loads session revocation store and creates a new instance using session configuration.
--
-- @function utils.load_revocation
-- @tparam nil|boolean|string revocation revocation store name, `nil` to auto-load from
-- `redis` when configured for revocation, `true` for `"redis"`, or `false` to disable
-- @tparam[opt] table configuration session configuration
-- @treturn table|nil instance of session revocation store
-- @treturn string|nil error message
--
-- @usage
-- local redis = require("resty.session.utils").load_revocation("redis", {
-- redis = {
-- host = "127.0.0.1",
-- }
-- })
load_revocation = function(revocation, configuration)
if revocation == false or revocation == "cookie" then
return nil
end

if revocation == true then
revocation = "redis"
end

local session_storage = configuration and configuration.storage
if session_storage and session_storage ~= "cookie" then
return nil
end

if not revocation then
local redis_cfg = configuration and configuration.redis
if not redis_cfg or not redis_cfg.host or redis_cfg.mode == "storage" then
return nil
end

revocation = "redis"
end

if type(revocation) ~= "string" then
error("invalid session revocation")
end

if revocation == "redis" then
local cfg = configuration and configuration.redis
if not cfg or not cfg.host or cfg.mode == "storage" then
return nil
end

if not REDIS then
REDIS = require("resty.session.redis")
end
return REDIS.new(cfg)

else
if not CUSTOM[revocation] then
CUSTOM[revocation] = require(revocation)
end
return CUSTOM[revocation].new(configuration and configuration[revocation])
end
end
end



---
-- Helper to format error messages.
--
Expand Down Expand Up @@ -1267,7 +1195,6 @@ return {
decrypt_aes_256_gcm = decrypt_aes_256_gcm,
hmac_sha256 = hmac_sha256,
load_storage = load_storage,
load_revocation = load_revocation,
errmsg = errmsg,
get_name = get_name,
set_flag = set_flag,
Expand Down
Loading