diff --git a/.busted b/.busted new file mode 100644 index 00000000..b7a74800 --- /dev/null +++ b/.busted @@ -0,0 +1,7 @@ +return { + default = { + lua = "resty --shdict 'sessions 1m' --main-conf 'thread_pool default threads=32 max_queue=65536;'", + verbose = true, + output = "gtest", + } +} diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml new file mode 100644 index 00000000..275788c0 --- /dev/null +++ b/.github/workflows/unit_tests.yaml @@ -0,0 +1,19 @@ +name: unit_tests +on: [push, pull_request] + +jobs: + test: + strategy: + fail-fast: false + matrix: + luaVersion: ["luajit-openresty"] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup environment + run: docker build dev/ -t resty-session + + - name: Run tests + run: docker run -v $PWD:/test -w /test resty-session bash -c "luarocks make && busted --exclude-tags=noci" diff --git a/.luacheckrc b/.luacheckrc index 5e28250d..abad11fa 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,2 +1,4 @@ std = "ngx_lua" - +redefined = false +max_line_length = false +files["lib/resty/session/file.lua"] = { ignore = {"143"}} \ No newline at end of file diff --git a/Changes.md b/Changes.md index 564bf79d..814b359c 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,11 @@ All notable changes to `lua-resty-session` will be documented in this file. +## [4.0.0] - 2023-02-01 +- Full rewrite of the library, and is not backwards compatible. Refer new + documentation on this new library. + + ## [3.10] - 2022-01-14 ### Fixed - 3.9 introduced an issue where calling session:regenerate with flush=true, diff --git a/LICENSE b/LICENSE index 8b9d0475..d6be106e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 – 2022, Aapo Talvensaari +Copyright (c) 2014 – 2023 Aapo Talvensaari, 2022 – 2023 Samuele Illuminati All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 9d80593e..271c2eef 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,10 @@ -.PHONY: lint +.PHONY: lint test docs lint: @luacheck -q ./lib + +test: + busted + +docs: + ldoc . diff --git a/README.md b/README.md index 0299a868..c07fa5f7 100644 --- a/README.md +++ b/README.md @@ -2,1180 +2,1454 @@ **lua-resty-session** is a secure, and flexible session library for OpenResty. -## Hello World with lua-resty-session +## TL;DR; + +- Sessions are immutable (each save generates a new session), and lockless. +- Session data is AES-256-GCM encrypted with a key derived using HKDF-SHA256. +- Session has a fixed size header that is protected with HMAC-SHA256 MAC with + a key derived using HKDF-SHA256. +- Session data can be stored in a stateless cookie or in various backend storages. +- A single session cookie can maintain multiple sessions across different audiences. + +*Note:* Version 4.0.0 was a rewrite of this library with a lot of lessons learned +during the years. If you still use older version, please refer +[old documentation](https://github.com/bungle/lua-resty-session/tree/v3.10). + + +## Status + +This library is considered production ready. + + +## Synopsis ```nginx worker_processes 1; events { - worker_connections 1024; + worker_connections 1024; } http { - server { - listen 8080; - server_name localhost; - default_type text/html; - - location / { - content_by_lua ' - ngx.say("Start the test!") - '; - } - location /start { - content_by_lua ' - local session = require "resty.session".start() - session.data.name = "OpenResty Fan" - session:save() - ngx.say("Session started. ", - "Check if it is working!") - '; - } - location /test { - content_by_lua ' - local session = require "resty.session".open() - ngx.say("Session was started by ", - session.data.name or "Anonymous", - "! Modify the session.") - '; - } - location /modify { - content_by_lua ' - local session = require "resty.session".start() - session.data.name = "Lua Fan" - session:save() - ngx.say("Session modified. ", - "Check if it is modified!") - '; - } - location /modified { - content_by_lua ' - local session = require "resty.session".open() - ngx.say("Session was modified by ", - session.data.name or "Anonymous", - "! Destroy the session.") - '; - } - location /destroy { - content_by_lua ' - require "resty.session".destroy() - ngx.say("Session was destroyed. ", - "Is it really so?") - '; - } - location /check { - content_by_lua ' - local session = require "resty.session".open() - ngx.say("Session was really destroyed, you are known as ", - "", - session.data.name or "Anonymous", - "! Start again.") - '; - } + init_by_lua_block { + require "resty.session".init({ + remember = true, + audience = "demo", + secret = "RaJKp8UQW1", + storage = "cookie", + }) + } + + server { + listen 8080; + server_name localhost; + default_type text/html; + + location / { + content_by_lua_block { + ngx.say([[ + + + Start the test + + + ]]) + } } -} -``` -## Installation + location /start { + content_by_lua_block { + local session = require "resty.session".new() + session:set_subject("OpenResty Fan") + session:set("quote", "The quick brown fox jumps over the lazy dog") + local ok, err = session:save() + + ngx.say(string.format([[ + + +

Session started (%s)

+

Check if it really was

+ + + ]], err or "no error")) + } + } -Just place [`session.lua`](https://github.com/bungle/lua-resty-session/blob/master/lib/resty/session.lua) -and [`session`](https://github.com/bungle/lua-resty-session/tree/master/lib/resty/session) directory -somewhere in your `package.path`, under `resty` directory. If you are using OpenResty, the default -location would be `/usr/local/openresty/lualib/resty`. + location /started { + content_by_lua_block { + local session, err = require "resty.session".start() + + ngx.say(string.format([[ + + +

Session was started by %s (%s)

+

%s

+

Modify the session

+ + + ]], + session:get_subject() or "Anonymous", + err or "no error", + session:get("quote") or "no quote" + )) + } + } + + location /modify { + content_by_lua_block { + local session, err = require "resty.session".start() + session:set_subject("Lua Fan") + session:set("quote", "Lorem ipsum dolor sit amet") + local _, err_save = session:save() + + ngx.say(string.format([[ + + +

Session was modified (%s)

+

Check if it is modified

+ + + ]], err or err_save or "no error")) + } + } + + location /modified { + content_by_lua_block { + local session, err = require "resty.session".start() + + ngx.say(string.format([[ + + +

Session was started by %s (%s)

+

%s

+

Destroy the session

+ + + ]], + session:get_subject() or "Anonymous", + err or "no error", + session:get("quote") or "no quote" + )) + } + } + + location /destroy { + content_by_lua_block { + local ok, err = require "resty.session".destroy() + + ngx.say(string.format([[ + + +

Session was destroyed (%s)

+

Check that it really was?

+ + + ]], err or "no error")) + } + } + + location /destroyed { + content_by_lua_block { + local session, err = require "resty.session".open() + + ngx.say(string.format([[ + + +

Session was really destroyed, you are known as %s (%s)

+

Start again

+ + + ]], + session:get_subject() or "Anonymous", + err or "no error" + )) + } + } + } +} +``` -### Using OpenResty Package Manager (opm) -```Shell -$ opm get bungle/lua-resty-session +# Table of Contents + +* [Installation](#installation) + * [Using OpenResty Package Manager (opm)](#using-openresty-package-manager-opm) + * [Using LuaRocks](#using-luarocks) +* [Configuration](#configuration) + * [Session Configuration](#session-configuration) + * [Cookie Storage Configuration](#cookie-storage-configuration) + * [DSHM Storage Configuration](#dshm-storage-configuration) + * [File Storage Configuration](#file-storage-configuration) + * [Memcached Storage Configuration](#memcached-storage-configuration) + * [MySQL / MariaDB Storage Configuration](#mysql--mariadb-storage-configuration) + * [Postgres Configuration](#postgres-configuration) + * [Redis Configuration](#redis-configuration) + * [Single Redis Configuration](#single-redis-configuration) + * [Redis Sentinels Configuration](#redis-sentinels-configuration) + * [Redis Cluster Configuration](#redis-cluster-configuration) + * [SHM Configuration](#shm-configuration) +* [API](#api) + * [Initialization](#initialization) + * [session.init](#sessioninit) + * [Constructors](#constructors) + * [session.new](#sessionnew) + * [Helpers](#helpers) + * [session.open](#sessionopen) + * [session.start](#sessionstart) + * [session.logout](#sessionlogout) + * [session.destroy](#sessiondestroy) + * [Instance Methods](#instance-methods) + * [session:open](#sessionopen-1) + * [session:save](#sessionsave) + * [session:touch](#sessiontouch) + * [session:refresh](#sessionrefresh) + * [session:logout](#sessionlogout-1) + * [session:destroy](#sessiondestroy-1) + * [session:close](#sessionclose) + * [session:set_data](#sessionset_data) + * [session:get_data](#sessionget_data) + * [session:set](#sessionset) + * [session:get](#sessionget) + * [session:set_audience](#sessionset_audience) + * [session:get_audience](#sessionget_audience) + * [session:set_subject](#sessionset_subject) + * [session:get_subject](#sessionget_subject) + * [session:get_property](#sessionget_property) + * [session:set_remember](#sessionset_remember) + * [session:get_remember](#sessionget_remember) + * [session:clear_request_cookie](#sessionclear_request_cookie) + * [session:set_headers](#sessionset_headers) + * [session:set_request_headers](#sessionset_request_headers) + * [session:set_response_headers](#sessionset_response_headers) + * [session.info:set](#sessioninfoset) + * [session.info:get](#sessioninfoget) + * [session.info:save](#sessioninfosave) +* [Cookie Format](#cookie-format) +* [Data Encryption](#data-encryption) +* [Cookie Header Authentication](#cookie-header-authentication) +* [Custom Storage Interface](#custom-storage-interface) +* [License](#license) + + +# Installation + +## Using OpenResty Package Manager (opm) + +```bash +❯ opm get bungle/lua-resty-session ``` -### Using LuaRocks +## Using LuaRocks -```Shell -$ luarocks install lua-resty-session +```bash +❯ luarocks install lua-resty-session ``` LuaRocks repository for `lua-resty-session` is located at https://luarocks.org/modules/bungle/lua-resty-session. -## About The Defaults - -`lua-resty-session` does by default session only cookies (non-persistent, and `HttpOnly`) so that -the cookies are not readable from Javascript (not subjectible to XSS in that matter). It will also set -`Secure` flag by default when the request was made via SSL/TLS connection or when cookie name (`session.name`) -is prefixed with `__Secure-` or `__Host-` (see [Cookies: HTTP State Management Mechanism](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05). -Cookies send via SSL/TLS don't work when sent via HTTP and vice-versa (unless the checks are disabled). -By default the HMAC key is generated from session id (random bytes generated with OpenSSL), expiration time, -unencrypted data, `http_user_agent` and `scheme`. You may also configure it to use `remote_addr` as well by -setting `set $session_check_addr on;` (but this may be problematic with clients behind proxies or NATs that -change the remote address between requests). If you are using SSL Session IDs you may also add -`set $session_check_ssi on;`, but please check that it works accordingly (you may need to adjust both SSL -and session library settings). - -The data part is encrypted with AES-algorithm (by default it uses OpenSSL `EVP_aes_256_cbc` and -`EVP_sha512` functions that are provided with `lua-resty-string`. They come pre-installed with -the default OpenResty bundle. The `lua-resty-session` library is not tested with all the -`resty.aes` functions (but the defaults are tested to be working). Please let me know or contact -`lua-resty-string` project if you hit any problems with different algorithms. We also support -pluggable cipher adapters. You can also disable encryption by choosing `none` adapter. - -Session identifier length is by default 16 bytes (randomly generated data with OpenSSL -`RAND_bytes` function). The server secret is also generated by default with this same -function and it's default length is 32 bytes. This will work until Nginx is restarted, but you -might want to consider setting your own secret using `set $session_secret 623q4hR325t36VsCD3g567922IC0073T;`, -for example (this will work in farms installations as well). On farm installations you should -also configure other session configuration variables the same on all the servers in the farm. - -Cookie parts are encoded with cookie safe Base64 encoding without padding (we also support pluggable -encoders). Before encrypting and encoding the data part, the data is serialized with JSON encoding -(so you can use basic Lua types in data, and expect to receive them back as the same Lua types). -JSON encoding is done by the bundled OpenResty cJSON library (Lua cJSON). We do support pluggable -serializers as well, though only serializer currently supplied is JSON. Cookie's path scope is by -default `/` (meaning that it will be send to all paths in the server). The domain scope is not set -by default, and it means that the cookie will only be sent back to same domain/host where it originated. -If you set session name (e.g. `set $session_name `) and it contains prefix `__Secure-` the -`Secure` flag will be forced, and if it contains `__Host-` the `path` is forced to `/` and the -`domain` is removed, and the `Secure` flag will be forced too. - -For session data we do support pluggable storage adapters. The default adapter is `cookie` that -stores data to client-side cookie. Currently we do also support a few server side storages: `shm` -(aka a shared dictionary), `memcache`, `redis`, and `dshm`. - -## Notes About Turning Lua Code Cache Off - -In issue ([#15](https://github.com/bungle/lua-resty-session/issues/15)) it was raised that there may -be problems of using `lua-resty-session` when the `lua_code_cache` setting has been turned off. - -Nginx: - -```nginx -lua_code_cache off; -``` -The problem is caused by the fact that by default we do generate session secret automatically with -a random generator (on first use of the library). If the code cache is turned off, we regenerate -the secret on each request. That will invalidate the cookies aka making sessions non-functioning. -The cure for this problem is to define the secret in Nginx or in Lua code (it is a good idea to -always have session secret defined). +# Configuration -Nginx: - -```nginx -set $session_secret 623q4hR325t36VsCD3g567922IC0073T; -``` +The configuration can be divided to generic session configuration and the server +side storage configuration. -Lua: +Here is an example: ```lua -local session = require "resty.session".start{ secret = "623q4hR325t36VsCD3g567922IC0073T" } --- or -local session = require "resty.session".new() -session.secret = "623q4hR325t36VsCD3g567922IC0073T" +init_by_lua_block { + require "resty.session".init({ + remember = true, + store_metadata = true, + secret = "RaJKp8UQW1", + secret_fallbacks = { + "X88FuG1AkY", + "fxWNymIpbb", + }, + storage = "postgres", + postgres = { + username = "my-service", + password = "kVgIXCE5Hg", + database = "sessions", + }, + }) +} ``` -## About Locking - -With some storage adapters we implement `locking` mechanism. The `locks` are normally released -automatically, and they will timeout, but if you happen to call `session.start()` or `session:start()`, -then it is your responsibility to release the lock by calling `session:close()`, `session:save()` or -`session:destroy()`. - -## Pluggable Session Strategies - -Strategies can be a bit cumbersome to do with just configuration, and that's why you can -implement them only with the code. Currently `lua-resty-session` comes with two strategies: - -* `default` — the default strategy (original implementation) -* `regenerate` — similar to default strategy, but does not use session `expiry` with `HMAC` - functions, and instead generates a new session identifier on each `save`. - -The `default` one has been here from the beginning, but recently I got information about -use case of Javascript application with parallel asynchronous queries, where the session -was saved to a database with a custom storage adapter using `header_filter` phase, which resulted -the need to use the asynchronous `ngx.timer`. And that resulted that the JS XHR requests may -have sent an old cookie, or perhaps a new cookie that was not yet found in db because of async -timer. This resulted issues because cryptographic functions in `default` strategy used `expires`, -and every time you saved a cookie it got a new `expiry`. The `regenerate` adapter does not use -`expiry` anymore, but it instead generates a new `session id` on each `save` call. This makes -a new row in a database while the previous `session` will still function. If your storage adapter -implements `ttl` the `regenerate` strategy will call that with the old id and `10` seconds -of `ttl`. `default` strategy is still adequate if you use `cookie` storage adapter as that -is not issue with it, but if using server side storage adapter like `redis` or `memcache` -you may want to consider using `regenerate` if you have a heavily JS based application with -a lot of asynchronous queries at the same time. This issue happens usually when session -is about to be renewed, so it is quite rare even when using `default` strategy. - -Strategy can be selected with configuration (if no configuration is present, the `default` -strategy is picked up): -```nginx -set $session_strategy regenerate; +## Session Configuration + +Session configuration can be passed to [initialization](#initialization), [constructor](#constructors), +and [helper](#helpers) functions. + +Here are the possible session configuration options: + +| Option | Default | Description | +|-----------------------------|:------------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `secret` | `nil` | Secret used for the key derivation. The secret is hashed with SHA-256 before using it. E.g. `"RaJKp8UQW1"`. | +| `secret_fallbacks` | `nil` | Array of secrets that can be used as alternative secrets (when doing key rotation), E.g. `{ "6RfrAYYzYq", "MkbTkkyF9C" }`. | +| `ikm` | (random) | Initial keying material (or ikm) can be specified directly (without using a secret) with exactly 32 bytes of data. E.g. `"5ixIW4QVMk0dPtoIhn41Eh1I9enP2060"` | +| `ikm_fallbacks` | `nil` | Array of initial keying materials that can be used as alternative keys (when doing key rotation), E.g. `{ "QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7" }`. | +| `cookie_prefix` | `nil` | Cookie prefix, use `nil`, `"__Host-"` or `"__Secure-"`. | +| `cookie_name` | `"session"` | Session cookie name, e.g. `"session"`. | +| `cookie_path` | `"/"` | Cookie path, e.g. `"/"`. | +| `cookie_http_only` | `true` | Mark cookie HTTP only, use `true` or `false`. | +| `cookie_secure` | `nil` | Mark cookie secure, use `nil`, `true` or `false`. | +| `cookie_priority` | `nil` | Cookie priority, use `nil`, `"Low"`, `"Medium"`, or `"High"`. | +| `cookie_same_site` | `"Lax"` | Cookie same-site policy, use `nil`, `"Lax"`, `"Strict"`, `"None"`, or `"Default"` | +| `cookie_same_party` | `nil` | Mark cookie with same party flag, use `nil`, `true`, or `false`. | +| `cookie_partitioned` | `nil` | Mark cookie with partitioned flag, use `nil`, `true`, or `false`. | +| `remember` | `false` | Enable or disable persistent sessions, use `nil`, `true`, or `false`. | +| `remember_safety` | `"Medium"` | Remember cookie key derivation complexity, use `nil`, `"None"` (fast), `"Low"`, `"Medium"`, `"High"` or `"Very High"` (slow). | +| `remember_cookie_name` | `"remember"` | Persistent session cookie name, e.g. `"remember"`. | +| `audience` | `"default"` | Session audience, e.g. `"my-application"`. | +| `subject` | `nil` | Session subject, e.g. `"john.doe@example.com"`. | +| `enforce_same_subject` | `false` | When set to `true`, audiences need to share the same subject. The library removes non-subject matching audience data on save. | +| `stale_ttl` | `10` | When session is saved a new session is created, stale ttl specifies how long the old one can still be used, e.g. `10` (in seconds). | +| `idling_timeout` | `900` | Idling timeout specifies how long the session can be inactive until it is considered invalid, e.g. `900` (15 minutes) (in seconds), `0` disables the checks and touching. | +| `rolling_timeout` | `3600` | Rolling timeout specifies how long the session can be used until it needs to be renewed, e.g. `3600` (an hour) (in seconds), `0` disables the checks and rolling. | +| `absolute_timeout` | `86400` | Absolute timeout limits how long the session can be renewed, until re-authentication is required, e.g. `86400` (a day) (in seconds), `0` disables the checks. | +| `remember_rolling_timeout` | `604800` | Remember timeout specifies how long the persistent session is considered valid, e.g. `604800` (a week) (in seconds), `0` disables the checks and rolling. | +| `remember_absolute_timeout` | `2592000` | Remember absolute timeout limits how long the persistent session can be renewed, until re-authentication is required, e.g. `2592000` (30 days) (in seconds), `0` disables the checks. | +| `hash_storage_key` | `false` | Whether to hash or not the storage key. With storage key hashed it is impossible to decrypt data on server side without having a cookie too, use `nil`, `true` or `false`. | +| `hash_subject` | `false` | Whether to hash or not the subject when `store_metadata` is enabled, e.g. for PII reasons. | +| `store_metadata` | `false` | Whether to also store metadata of sessions, such as collecting data of sessions for a specific audience belonging to a specific subject. | +| `touch_threshold` | `60` | Touch threshold controls how frequently or infrequently the `session:refresh` touches the cookie, e.g. `60` (a minute) (in seconds) | +| `compression_threshold` | `1024` | Compression threshold controls when the data is deflated, e.g. `1024` (a kilobyte) (in bytes), `0` disables compression. | +| `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. | +| `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) | +| `memcached` | `nil` | Configuration for memcached storage, e.g. `{ prefix = "sessions" }` (see below) | +| `mysql` | `nil` | Configuration for MySQL / MariaDB storage, e.g. `{ database = "sessions" }` (see below) | +| `postgres` | `nil` | Configuration for Postgres storage, e.g. `{ database = "sessions" }` (see below) | +| `redis` | `nil` | Configuration for Redis / Redis Sentinel / Redis Cluster storages, e.g. `{ prefix = "sessions" }` (see below) | +| `shm` | `nil` | Configuration for shared memory storage, e.g. `{ zone = "sessions" }` | +| `["custom-storage"]` | `nil` | custom storage (loaded with `require "custom-storage"`) configuration. | + + +## Cookie Storage Configuration + +When storing data to cookie, there is no additional configuration required, +just set the `storage` to `nil` or `"cookie"`. + + +## DSHM Storage Configuration + +With DHSM storage you can use the following settings (set the `storage` to `"dshm"`): + +| Option | Default | Description | +|---------------------|:-------------:|----------------------------------------------------------------------------------------------| +| `prefix` | `nil` | The Prefix for the keys stored in DSHM. | +| `suffix` | `nil` | The suffix for the keys stored in DSHM. | +| `host` | `"127.0.0.1"` | The host to connect. | +| `port` | `4321` | The port to connect. | +| `connect_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. | +| `send_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `send` method. | +| `read_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. | +| `keepalive_timeout` | `nil` | Controls the default maximal idle time of the connections in the connection pool. | +| `pool` | `nil` | A custom name for the connection pool being used. | +| `pool_size` | `nil` | The size of the connection pool. | +| `backlog` | `nil` | A queue size to use when the connection pool is full (configured with pool_size). | +| `ssl` | `nil` | Enable SSL. | +| `ssl_verify` | `nil` | Verify server certificate. | +| `server_name` | `nil` | The server name for the new TLS extension Server Name Indication (SNI). | + +Please refer to [ngx-distributed-shm](https://github.com/grrolland/ngx-distributed-shm) to get necessary +dependencies installed. + + +## File Storage Configuration + +With file storage you can use the following settings (set the `storage` to `"file"`): + +| Option | Default | Description | +|---------------------|:---------------:|-------------------------------------------------------------------------------------| +| `prefix` | `nil` | File prefix for session file. | +| `suffix` | `nil` | File suffix (or extension without `.`) for session file. | +| `pool` | `nil` | Name of the thread pool under which file writing happens (available on Linux only). | +| `path` | (tmp directory) | Path (or directory) under which session files are created. | + + +The implementation requires `LuaFileSystem` which you can install with LuaRocks: +```sh +❯ luarocks install LuaFileSystem ``` -To implement a custom strategy, please checkout the existing ones. -Basically you need to implement at least these functions: -- boolean open(session, cookie) -- boolean start(session) -- boolean destroy(session) -- boolean close(session) -- cookie save(session, close) -- cookie touch(session, close) - -## Pluggable HMAC Algorithms - -If your strategy happens to be using `HMAC`, like the `default` and `regenerate` ones do, -you can tell them what `HMAC` algorithm to use. At the moment only `HMAC SHA1` is available -as that comes with OpenResty and works without additional dependencies. You may implement -your own custom HMAC algorithms (preferrably binding to some existing crypto library, -such as OpenSSL), and the strategies will pick up from there. +## Memcached Storage Configuration + +With file Memcached you can use the following settings (set the `storage` to `"memcached"`): + +| Option | Default | Description | +|---------------------|:-----------:|----------------------------------------------------------------------------------------------| +| `prefix` | `nil` | Prefix for the keys stored in memcached. | +| `suffix` | `nil` | Suffix for the keys stored in memcached. | +| `host` | `127.0.0.1` | The host to connect. | +| `port` | `11211` | The port to connect. | +| `socket` | `nil` | The socket file to connect to. | +| `connect_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. | +| `send_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `send` method. | +| `read_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. | +| `keepalive_timeout` | `nil` | Controls the default maximal idle time of the connections in the connection pool. | +| `pool` | `nil` | A custom name for the connection pool being used. | +| `pool_size` | `nil` | The size of the connection pool. | +| `backlog` | `nil` | A queue size to use when the connection pool is full (configured with pool_size). | +| `ssl` | `false` | Enable SSL | +| `ssl_verify` | `nil` | Verify server certificate | +| `server_name` | `nil` | The server name for the new TLS extension Server Name Indication (SNI). | + + +## MySQL / MariaDB Storage Configuration + +With file MySQL / MariaDB you can use the following settings (set the `storage` to `"mysql"`): + +| Option | Default | Description | +|---------------------|:-----------------:|----------------------------------------------------------------------------------------------| +| `host` | `"127.0.0.1"` | The host to connect. | +| `port` | `3306` | The port to connect. | +| `socket` | `nil` | The socket file to connect to. | +| `username` | `nil` | The database username to authenticate. | +| `password` | `nil` | Password for authentication, may be required depending on server configuration. | +| `charset` | `"ascii"` | The character set used on the MySQL connection. | +| `database` | `nil` | The database name to connect. | +| `table_name` | `"sessions"` | Name of database table to which to store session data. | +| `table_name_meta` | `"sessions_meta"` | Name of database meta data table to which to store session meta data. | +| `max_packet_size` | `1048576` | The upper limit for the reply packets sent from the MySQL server (in bytes). | +| `connect_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. | +| `send_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `send` method. | +| `read_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. | +| `keepalive_timeout` | `nil` | Controls the default maximal idle time of the connections in the connection pool. | +| `pool` | `nil` | A custom name for the connection pool being used. | +| `pool_size` | `nil` | The size of the connection pool. | +| `backlog` | `nil` | A queue size to use when the connection pool is full (configured with pool_size). | +| `ssl` | `false` | Enable SSL. | +| `ssl_verify` | `nil` | Verify server certificate. | + +You also need to create following tables in your database: + +```sql +-- +-- Database table that stores session data. +-- +CREATE TABLE IF NOT EXISTS sessions ( + sid CHAR(43) PRIMARY KEY, + name VARCHAR(255), + data MEDIUMTEXT, + exp DATETIME, + INDEX (exp) +) CHARACTER SET ascii; + +-- +-- Sessions metadata table. +-- +-- This is only needed if you want to store session metadata. +-- +CREATE TABLE IF NOT EXISTS sessions_meta ( + aud VARCHAR(255), + sub VARCHAR(255), + sid CHAR(43), + PRIMARY KEY (aud, sub, sid), + CONSTRAINT FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE ON UPDATE CASCADE +) CHARACTER SET ascii; +``` -HMAC can be selected with configuration (if no configuration is present, the `sha1` strategy is picked up): -```nginx -set $session_hmac sha1; +## Postgres Configuration + +With file Postgres you can use the following settings (set the `storage` to `"postgres"`): + +| Option | Default | Description | +|---------------------|:-----------------:|-----------------------------------------------------------------------------------------------------------| +| `host` | `"127.0.0.1"` | The host to connect. | +| `port` | `5432` | The port to connect. | +| `application` | `5432` | Set the name of the connection as displayed in pg_stat_activity (defaults to `"pgmoon"`). | +| `username` | `"postgres"` | The database username to authenticate. | +| `password` | `nil` | Password for authentication, may be required depending on server configuration. | +| `database` | `nil` | The database name to connect. | +| `table_name` | `"sessions"` | Name of database table to which to store session data (can be `database schema` prefixed). | +| `table_name_meta` | `"sessions_meta"` | Name of database meta data table to which to store session meta data (can be `database schema` prefixed). | +| `connect_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. | +| `send_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `send` method. | +| `read_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. | +| `keepalive_timeout` | `nil` | Controls the default maximal idle time of the connections in the connection pool. | +| `pool` | `nil` | A custom name for the connection pool being used. | +| `pool_size` | `nil` | The size of the connection pool. | +| `backlog` | `nil` | A queue size to use when the connection pool is full (configured with pool_size). | +| `ssl` | `false` | Enable SSL. | +| `ssl_verify` | `nil` | Verify server certificate. | +| `ssl_required` | `nil` | Abort the connection if the server does not support SSL connections. | + +You also need to create following tables in your database: + +```sql +-- +-- Database table that stores session data. +-- +CREATE TABLE IF NOT EXISTS sessions ( + sid TEXT PRIMARY KEY, + name TEXT, + data TEXT, + exp TIMESTAMP WITH TIME ZONE +); +CREATE INDEX ON sessions (exp); + +-- +-- Sessions metadata table. +-- +-- This is only needed if you want to store session metadata. +-- +CREATE TABLE IF NOT EXISTS sessions_meta ( + aud TEXT, + sub TEXT, + sid TEXT REFERENCES sessions (sid) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (aud, sub, sid) +); ``` -To implement your own, you need to implement this interface: `digest hmac(secret, input)`. +The implementation requires `pgmoon` which you can install with LuaRocks: +```sh +❯ luarocks install pgmoon +``` -## Pluggable Storage Adapters +## Redis Configuration -With version 2.0 we started to support pluggable session data storage adapters. We do currently have -support for these backends: +The session library supports single Redis, Redis Sentinel, and Redis Cluster +connections. Common configuration settings among them all: -* `cookie` aka Client Side Cookie (this is the default adapter) -* `shm` aka Lua Shared Dictionary -* `memcache` aka Memcached Storage Backend (thanks [@zandbelt](https://github.com/zandbelt)) -* `redis` aka Redis Backend -* `dshm` aka [ngx-distributed-shm](https://github.com/grrolland/ngx-distributed-shm) Storage Adapter (thanks [@grrolland](https://github.com/grrolland)) +| Option | Default | Description | +|---------------------|:-------:|----------------------------------------------------------------------------------------------| +| `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. | +| `password` | `nil` | Password for authentication. | +| `connect_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. | +| `send_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `send` method. | +| `read_timeout` | `nil` | Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. | +| `keepalive_timeout` | `nil` | Controls the default maximal idle time of the connections in the connection pool. | +| `pool` | `nil` | A custom name for the connection pool being used. | +| `pool_size` | `nil` | The size of the connection pool. | +| `backlog` | `nil` | A queue size to use when the connection pool is full (configured with pool_size). | +| `ssl` | `false` | Enable SSL | +| `ssl_verify` | `nil` | Verify server certificate | +| `server_name` | `nil` | The server name for the new TLS extension Server Name Indication (SNI). | -Here are some comparisons about the backends: +The `single redis` implementation is selected when you don't pass either `sentinels` or `nodes`, +which would lead to selecting `sentinel` or `cluster` implementation. -| | cookie | shm | memcache | redis | dshm | -| :---------------------------- | :----: | :-: | :------: | :---: | :--: | -| Stateless | ✓ | | | | | -| Lock-less | ✓ | ¹ | ¹ | ¹ | ✓ | -| Works with Web Farms | ✓ | | ✓ | ✓ | ✓ | -| Session Data Stored on Client | ✓ | | | | | -| Zero Configuration | ✓ | | | | | -| Extra Dependencies | | | ✓ | ✓ | ✓ | -| Extra Security ² | | ✓ | ✓ | ✓ | ✓ | +### Single Redis Configuration -¹ Can be configured lock-less. +Single Redis has following additional configuration options (set the `storage` to `"redis"`): -² HMAC is stored on a client but the data is stored on a server. That means that you are unable to edit - cookie if you cannot edit server side storage as well, and vice-versa. +| Option | Default | Description | +|-------------|:---------------:|--------------------------------| +| `host` | `"127.0.0.1"` | The host to connect. | +| `port` | `6379` | The port to connect. | +| `socket` | `nil` | The socket file to connect to. | +| `database` | `nil` | The database to connect. | -The storage adapter can be selected from Nginx config like this: -```nginx -set $session_storage shm; -``` +### Redis Sentinels Configuration -Or with Lua code like this: +Redis Sentinel has following additional configuration options (set the `storage` to `"redis"` +and configure the `sentinels`): -```lua -local session = require "resty.session".new() -- OR .open() | .start() --- After new you cannot specify storage as a string, --- you need to give actual implementation -session.storage = require "resty.session.storage.shm".new(session) --- or -local session = require "resty.session".new({ - storage = "shm" -}) -``` +| Option | Default | Description | +|---------------------|:--------:|--------------------------------| +| `master` | `nil` | Name of master. | +| `role` | `nil` | `"master"` or `"slave"`. | +| `socket` | `nil` | The socket file to connect to. | +| `sentinels` | `nil` | Redis Sentinels. | +| `sentinel_username` | `nil` | Optional sentinel username. | +| `sentinel_password` | `nil` | Optional sentinel password. | +| `database` | `nil` | The database to connect. | -#### Cookie Storage Adapter +The `sentinels` is an array of Sentinel records: -Cookie storage adapter is the default adapter that is used if storage adapter has not been configured. Cookie -adapter does not have any settings. +| Option | Default | Description | +|--------|:-------:|----------------------| +| `host` | `nil` | The host to connect. | +| `port` | `nil` | The port to connect. | -Cookie adapter can be selected with configuration (if no configuration is present, the cookie adapter is picked up): +The `sentinel` implementation is selected when you pass `sentinels` as part of `redis` +configuration (and do not pass `nodes`, which would select `cluster` implementation). -```nginx -set $session_storage cookie; +The implementation requires `lua-resty-redis-connector` which you can install with LuaRocks: +```sh +❯ luarocks install lua-resty-redis-connector ``` -**NOTE:** -If you store large amounts of data in a cookie, this library will automatically split the cookies to 4k chars chunks. With large cookies, you may need to adjust your Nginx configuration to accept large client header buffers. E.g.: +### Redis Cluster Configuration -```nginx -large_client_header_buffers 4 16k; -``` +Redis Cluster has following additional configuration options (set the `storage` to `"redis"` +and configure the `nodes`): -#### Shared Dictionary Storage Adapter +| Option | Default | Description | +|---------------------------|:-------:|--------------------------------------------------------| +| `name` | `nil` | Redis cluster name. | +| `nodes` | `nil` | Redis cluster nodes. | +| `lock_zone` | `nil` | Shared dictionary name for locks. | +| `lock_prefix` | `nil` | Shared dictionary name prefix for lock. | +| `max_redirections` | `nil` | Maximum retry attempts for redirection. | +| `max_connection_attempts` | `nil` | Maximum retry attempts for connection. | +| `max_connection_timeout` | `nil` | Maximum connection timeout in total among the retries. | -Shared dictionary uses OpenResty shared dictionary and works with multiple worker processes, but it isn't a good -choice if you want to run multiple separate frontends. It is relatively easy to configure and has some added -benefits on security side compared to `cookie`, although the normal cookie adapter is quite secure as well. -For locking the `shm` adapter uses `lua-resty-lock`. +The `nodes` is an array of Cluster node records: -Shared dictionary adapter can be selected with configuration: +| Option | Default | Description | +|--------|:-------------:|----------------------------| +| `ip` | `"127.0.0.1"` | The IP address to connect. | +| `port` | `6379` | The port to connect. | -```nginx -set $session_storage shm; -``` +The `cluster` implementation is selected when you pass `nodes` as part of `redis` +configuration. -But for this to work, you will also need a storage configured for that: +For `cluster` to work properly, you need to configure `lock_zone`, so also add this +to your Nginx configuration: ```nginx -http { - lua_shared_dict sessions 10m; -} +lua_shared_dict redis_cluster_locks 100k; ``` -Additionally you can configure the locking and some other things as well: +And set the `lock_zone` to `"redis_cluster_locks"` -```nginx -set $session_shm_store sessions; -set $session_shm_uselocking on; -set $session_shm_lock_exptime 30; # (in seconds) -set $session_shm_lock_timeout 5; # (in seconds) -set $session_shm_lock_step 0.001; # (in seconds) -set $session_shm_lock_ratio 2; -set $session_shm_lock_max_step 0.5; # (in seconds) +The implementation requires `resty-redis-cluster` or `kong-redis-cluster` which you can install with LuaRocks: +```sh +❯ luarocks install resty-redis-cluster +# or +❯ luarocks install kong-redis-cluster ``` -The keys stored in shared dictionary are in form: -`{session id}` and `{session id}.lock`. +## SHM Configuration +With SHM storage you can use the following settings (set the `storage` to `"shm"`): -#### Memcache Storage Adapter +| Option | Default | Description | +|----------|:------------:|------------------------------------| +| `prefix` | `nil` | Prefix for the keys stored in SHM. | +| `suffix` | `nil` | Suffix for the keys stored in SHM. | +| `zone` | `"sessions"` | A name of shared memory zone. | -Memcache storage adapter stores the session data inside Memcached server. -It is scalable and works with web farms. - -Memcache adapter can be selected with configuration: +You will also need to create a shared dictionary `zone` in Nginx: ```nginx -set $session_storage memcache; +lua_shared_dict sessions 10m; ``` -Additionally you can configure Memcache adapter with these settings: +*Note:* you may need to adjust the size of shared memory zone according to your needs. -```nginx -set $session_memcache_prefix sessions; -set $session_memcache_connect_timeout 1000; # (in milliseconds) -set $session_memcache_send_timeout 1000; # (in milliseconds) -set $session_memcache_read_timeout 1000; # (in milliseconds) -set $session_memcache_socket unix:///var/run/memcached/memcached.sock; -set $session_memcache_host 127.0.0.1; -set $session_memcache_port 11211; -set $session_memcache_uselocking on; -set $session_memcache_spinlockwait 150; # (in milliseconds) -set $session_memcache_maxlockwait 30; # (in seconds) -set $session_memcache_pool_name sessions; -set $session_memcache_pool_timeout 1000; # (in milliseconds) -set $session_memcache_pool_size 10; -set $session_memcache_pool_backlog 10; -``` -The keys stored in Memcached are in form: +# API -`{prefix}:{session id}` and `{prefix}:{session id}.lock`. +LDoc generated API docs can also be viewed at [bungle.github.io/lua-resty-session](https://bungle.github.io/lua-resty-session/). -#### Redis Storage Adapter -Redis storage adapter stores the session data inside Redis server. -It is scalable and works with web farms. +## Initialization -Redis adapter can be selected with configuration: +### session.init -```nginx -set $session_storage redis; -``` +**syntax:** *session.init(configuration)* -Additionally you can configure Redis adapter with these settings: +Initialize the session library. -```nginx -set $session_redis_prefix sessions; -set $session_redis_database 0; -set $session_redis_connect_timeout 1000; # (in milliseconds) -set $session_redis_send_timeout 1000; # (in milliseconds) -set $session_redis_read_timeout 1000; # (in milliseconds) -set $session_redis_socket unix:///var/run/redis/redis.sock; -set $session_redis_host 127.0.0.1; -set $session_redis_port 6379; -set $session_redis_ssl off; -set $session_redis_ssl_verify off; -set $session_redis_server_name example.com; # for TLS SNI -set $session_redis_username username; -set $session_redis_password password; -set $session_redis_uselocking on; -set $session_redis_spinlockwait 150; # (in milliseconds) -set $session_redis_maxlockwait 30; # (in seconds) -set $session_redis_pool_name sessions; -set $session_redis_pool_timeout 1000; # (in milliseconds) -set $session_redis_pool_size 10; -set $session_redis_pool_backlog 10; -set $session_redis_cluster_name redis-cluster; -set $session_redis_cluster_dict sessions; -set $session_redis_cluster_maxredirections 5; -set $session_redis_cluster_nodes '127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006'; -``` - -**Note**: `session_redis_auth` has been deprecated; use `session_redis_password`. +This function can be called on `init` or `init_worker` phases on OpenResty +to set global default configuration to all session instances created by this +library. -To use `cluster` you need also to install: -```shell -luarocks install kong-redis-cluster -# OR -luarocks install lua-resty-redis-cluster - -# OR install this manually https://github.com/steve0511/resty-redis-cluster +```lua +require "resty.session".init({ + audience = "my-application", + storage = "redis", + redis = { + username = "session", + password = "storage", + }, +}) ``` -The keys stored in Redis are in form: +See [configuration](#configuration) for possible configuration settings. -`{prefix}:{session id}` and `{prefix}:{session id}.lock`. -#### DSHM Storage Adapter +## Constructors -DSHM storage adapter stores the session data inside Distributed Shared Memory server based -on Vertx and Hazelcast. It is scalable and works with web farms. +### session.new -The DSHM lua library and the DSHM servers should be installed conforming with the documentation -[here](https://github.com/grrolland/ngx-distributed-shm/blob/master/README.md). +**syntax:** *session = session.new(configuration)* +Creates a new session instance. -DSHM adapter can be selected with configuration: - -```nginx -set $session_storage dshm; +```lua +local session = require "resty.session".new() +-- OR +local session = require "resty.session".new({ + audience = "my-application", +}) ``` -Additionally you can configure DSHM adapter with these settings: +See [configuration](#configuration) for possible configuration settings. -```nginx -set $session_dshm_region sessions; -set $session_dshm_connect_timeout 1000; # (in milliseconds) -set $session_dshm_send_timeout 1000; # (in milliseconds) -set $session_dshm_read_timeout 1000; # (in milliseconds) -set $session_dshm_host 127.0.0.1; -set $session_dshm_port 4321; -set $session_dshm_pool_name sessions; -set $session_dshm_pool_timeout 1000; # (in milliseconds) -set $session_dshm_pool_size 10; -set $session_dshm_pool_backlog 10; -``` -The keys stored in DSHM are in form: +## Helpers -`{region}::{encoded session id}` +### session.open -The `region` represents the cache region in DSHM. +**syntax:** *session, err, exists = session.open(configuration)* -#### Implementing a Storage Adapter +This can be used to open a session, and it will either return an existing +session or a new session. The `exists` (a boolean) return parameters tells whether +it was existing or new session that was returned. The `err` (a string) contains +a message of why opening might have failed (the function will still return +`session` too). -It is possible to implement additional storage adapters using the plugin architecture in `lua-resty-session`. +```lua +local session = require "resty.session".open() +-- OR +local session, err, exists = require "resty.session".open({ + audience = "my-application", +}) +``` -You need to implement APIs you need +See [configuration](#configuration) for possible configuration settings. -* `storage new(session)` -* `boolean storage:open(id)` -* `boolean storage:start(id)` -* `boolean storage:save(id, ttl, data, close)` -* `bookean storage:close(id)` -* `boolean storage:destroy(id)` -* `boolean storage:ttl(id, ttl, close)` -The `id` parameter is already encoded, but `data` is in raw bytes, so please encode it as needed. +### session.start -You have to place your adapter inside `resty.session.storage` for auto-loader to work. +**syntax:** *session, err, exists, refreshed = session.start(configuration)* -To configure session to use your adapter, you can do so with Nginx configuration -(or in Lua code): +This can be used to start a session, and it will either return an existing +session or a new session. In case there is an existing session, the +session will be refreshed as well (as needed). The `exists` (a boolean) +return parameters tells whether it was existing or new session that was +returned. The `refreshed` (a boolean) tells whether the call to `refresh` +was succesful. The `err` (a string) contains a message of why opening or +refreshing might have failed (the function will still return `session` too). -```nginx -# Just an example. Pull request for MySQL support is greatly welcomed. -set $session_storage mysql; +```lua +local session = require "resty.session".start() +-- OR +local session, err, exists, refreshed = require "resty.session".start({ + audience = "my-application", +}) ``` -## Pluggable Ciphers +See [configuration](#configuration) for possible configuration settings. -With version 2.1 we started to support pluggable ciphers. We currently have support for these ciphers: -* `aes` aka AES encryption / decryption using `lua-resty-string`'s AES library (the default). -* `none` aka no encryption or decryption is done. +### session.logout -The cipher adapter can be selected from Nginx config like this: +**syntax:** *ok, err, exists, logged_out = session.logout(configuration)* -```nginx -set $session_cipher aes; -``` +It logouts from a specific audience. -Or with Lua code like this: +A single session cookie may be shared between multiple audiences +(or applications), thus there is a need to be able to logout from +just a single audience while keeping the session for the other +audiences. The `exists` (a boolean) return parameters tells whether +session existed. The `logged_out` (a boolean) return parameter signals +if the session existed and was also logged out. The `err` (a string) +contains a reason why session didn't exists or why the logout failed. +The `ok` (truthy) will be `true` when session existed and was +successfully logged out. -```lua -local session = require "resty.session".start{ cipher = "aes" } -``` - -#### AES Cipher +When there is only a single audience, then this can be considered +equal to `session.destroy`. -AES Cipher uses `lua-resty-string`'s (an OpenResty core library) AES implementation -(bindings to OpenSSL) for encryption. +When the last audience is logged out, the cookie will be destroyed +as well and invalidated on a client. -AES adapter can be selected with configuration: - -```nginx -set $session_cipher aes; +```lua +require "resty.session".logout() +-- OR +local ok, err, exists, logged_out = require "resty.session".logout({ + audience = "my-application", +}) ``` -Additionally you can configure Memcache adapter with these settings: -```nginx -set $session_aes_size 256; -set $session_aes_mode "cbc"; -set $session_aes_hash "sha512"; -set $session_aes_rounds 1; -``` +See [configuration](#configuration) for possible configuration settings. -Here follows the description of each setting: -**size** +### session.destroy -`session.aes.size` holds the size of the cipher (`lua-resty-string` supports AES in `128`, `192`, -and `256` bits key sizes). See `aes.cipher` function in `lua-resty-string` for more information. -By default this will use `256` bits key size. This can be configured with Nginx -`set $session_aes_size 256;`. +**syntax:** *ok, err, exists, destroyed = session.destroy(configuration)* -**mode** +It destroys the whole session and clears the cookies. -`session.aes.mode` holds the mode of the cipher. `lua-resty-string` supports AES in `ecb`, `cbc`, -`cfb1`, `cfb8`, `cfb128`, `ofb`, `ctr`, and `gcm` (recommended!) modes (ctr mode is not available -with 256 bit keys). See `aes.cipher` function in `lua-resty-string` for more information. -By default `cbc` mode is used. This can be configured with Nginx `set $session_aes_mode cbc;`. +A single session cookie may be shared between multiple audiences +(or applications), thus there is a need to be able to logout from +just a single audience while keeping the session for the other +audiences. The `exists` (a boolean) return parameters tells whether +session existed. The `destroyed` (a boolean) return parameter signals +if the session existed and was also destroyed out. The `err` (a string) +contains a reason why session didn't exists or why the logout failed. +The `ok` (truthy) will be `true` when session existed and was +successfully logged out. -**hash** +```lua +require "resty.session".destroy() +-- OR +local ok, err, exists, destroyed = require "resty.session".destroy({ + cookie_name = "auth", +}) +``` -`session.aes.hash` is used in ecryption key, and iv derivation (see: OpenSSL -[EVP_BytesToKey](https://www.openssl.org/docs/crypto/EVP_BytesToKey.html)). By default `sha512` is -used but `md5`, `sha1`, `sha224`, `sha256`, and `sha384` are supported as well in `lua-resty-string`. -This can be configured with Nginx `set $session_aes_hash sha512;`. +See [configuration](#configuration) for possible configuration settings. -**rounds** -`session.aes.rounds` can be used to slow-down the encryption key, and iv derivation. By default -this is set to `1` (the fastest). This can be configured with Nginx `set $session_aes_rounds 1;`. +## Instance Methods -#### None Cipher +### session:open -None cipher disables encryption of the session data. This can be handy if you want to -debug things or want you session management as light as possible, or perhaps share the -session data with some other process without having to deal with encryption key management. -In general it is better to have encryption enabled in a production. +**syntax:** *ok, err = session:open()* -None adapter can be selected with configuration: +This can be used to open a session. It returns `true` when +session was opened and validated. Otherwise, it returns `nil` and +an error message. -```nginx -set $session_cipher none; +```lua +local session = require "resty.session".new() +local ok, err = session:open() +if ok then + -- session exists + +else + -- session did not exists or was invalid +end ``` -There isn't any settings for None adapter as it is basically a no-op adapter. -#### Implementing a Cipher Adapter +### session:save -If you want to write your own cipher adapter, you need to implement these three methods: +**syntax:** *ok, err = session:save()* -* `cipher new(session)` -* `string, err, tag = cipher:encrypt(data, key, salt, aad)` -* `string, err, tag = cipher:decrypt(data, key, salt, aad, tag)` +Saves the session data and issues a new session cookie with a new session id. +When `remember` is enabled, it will also issue a new persistent cookie and +possibly save the data in backend store. It returns `true` when session was saved. +Otherwise, it returns `nil` and an error message. -If you do not use say salt or aad (associated data) in your cipher, you can ignore them. -If you don't use `AEAD` construct (like `AES in GCM-mode`), don't return `tag`. +```lua +local session = require "resty.session".new() +session:set_subject("john") +local ok, err = session:save() +if not ok then + -- error when saving session +end +``` -You have to place your adapter inside `resty.session.ciphers` for auto-loader to work. -## Pluggable Serializers +### session:touch -Currently we only support JSON serializer, but there is a plugin architecture that you can use to -plugin your own serializer. The serializer is used to serialize session data in a form that can be -later deserialized and stored in some of our supported storages. +**syntax:** *ok, err = session:touch()* -The supported serializer names are: +Updates idling offset of the session by sending an updated session cookie. +It only sends the client cookie and never calls any backend session store +APIs. Normally the `session:refresh` is used to call this indirectly. In +error case it returns `nil` and an error message, otherwise `true`. -* `json` +```lua +local session, err, exists = require "resty.session".open() +if exists then + ok, err = session:touch() +end +``` -You need only to implement two functions to write an adapter: -* `string serialize(table)` -* `table deserialize(string)` +### session:refresh -You have to place your adapter inside `resty.session.serializers` for auto-loader to work. +**syntax:** *ok, err = session:refresh()* -To configure session to use your adapter, you can do so with Nginx configuration (or in Lua code): +Either saves the session (creating a new session id) or touches the session +depending on whether the rolling timeout is getting closer, which means +by default when 3/4 of rolling timeout is spent, that is 45 minutes with default +rolling timeout of an hour. The touch has a threshold, by default one minute, +so it may be skipped in some cases (you can call `session:touch()` to force it). +In error case it returns `nil` and an error message, otherwise `true`. -```nginx -set $session_serializer json; +```lua +local session, err, exists = require "resty.session".open() +if exists then + local ok, err = session:refresh() +end ``` -## Pluggable Compressors +The above code looks a bit like `session.start()` helper. -The session data may grew quite a big if you decide to store for example JWT tokens in a session. -By compressing the data we can make the data part of the cookie smaller before sending it to client -or before storing it to a backend store (using pluggable storage adapters). -The supported compressors are: +### session:logout -* `none` (the default) -* `zlib` (this has extra requirement to `penlight` and `ffi-zlib`) +**syntax:** *ok, err = session:logout()* -To use `zlib` you need also to install: -```shell -luarocks install lua-ffi-zlib -luarocks install penlight +Logout either destroys the session or just clears the data for the current audience, +and saves it (logging out from the current audience). In error case it returns `nil` +and an error message, otherwise `true`. -# OR install these manually: -# - https://github.com/hamishforbes/lua-ffi-zlib -# - https://github.com/lunarmodules/Penlight +```lua +local session, err, exists = require "resty.session".open() +if exists then + local ok, err = session:logout() +end ``` -If you want to write your own compressor you need to implement these three methods: +### session:destroy -* `cipher new(session)` -* `string compressor:compress(data)` -* `string compressor:decompress(data)` +**syntax:** *ok, err = session:destroy()* -To configure session to use your compressor, you can do so with Nginx configuration (or in Lua code): +Destroy the session and clear the cookies. In error case it returns `nil` +and an error message, otherwise `true`. -```nginx -set $session_compressor none; +```lua +local session, err, exists = require "resty.session".open() +if exists then + local ok, err = session:destroy() +end ``` -## Pluggable Encoders -Cookie data needs to be encoded in cookie form before it is send to client. We support -two encoding methods by default: modified cookie friendly base-64, and base-16 (or hexadecimal encoding). +### session:close -The supported encoder names are: +**syntax:** *session:close()* -* `base64` -* `base16` or `hex` +Just closes the session instance so that it cannot be used anymore. + +```lua +local session = require "resty.session".new() +session:set_subject("john") +local ok, err = session:save() +if not ok then + -- error when saving session +end +session:close() +``` -If you want to write your own encoder, you need to implement these two methods: -* `string encode(string)` -* `string decode(string)` +### session:set_data -You have to place your adapter inside `resty.session.encoders` for auto-loader to work. +**syntax:** *session:set_data(data)* -To configure session to use your adapter, you can do so with Nginx configuration (or in Lua code): +Set session data. The `data` needs to be a `table`. -```nginx -set $session_encoder base64; +```lua +local session, err, exists = require "resty.session".open() +if not exists then + session:set_data({ + cart = {}, + }) + session:save() +end ``` -## Pluggable Session Identifier Generators -With version 2.12 we started to support pluggable session identifier generators in `lua-resty-session`. -Right now we support only one type of generator, and that is: +### session:get_data -* `random` +**syntax:** *data = session:get_data()* -If you want to write your own session identifier generator, you need to implement one function: +Get session data. -* `string generate(session)` +```lua +local session, err, exists = require "resty.session".open() +if exists then + local data = session:get_data() + ngx.req.set_header("Authorization", "Bearer " .. data.access_token) +end +``` -(the `config` is actually a `session` instance) -You have to place your generator inside `resty.session.identifiers` for auto-loader to work. +### session:set -To configure session to use your generator, you can do so with Nginx configuration (or in Lua code): +**syntax:** *session:set(key, value)* -```nginx -set $session_identifier_generator random; +Set a value in session. + +```lua +local session, err, exists = require "resty.session".open() +if not exists then + session:set("access-token", "eyJ...") + session:save() +end ``` -#### Random Sesssion Identifier Generator -Random generator uses `lua-resty-string`'s (an OpenResty core library) OpenSSL based cryptographically -safe random generator. +### session:get -Random generator can be selected with configuration: +**syntax:** *value = session:get(key)* -```nginx -set $session_identifier random; +Get a value from session. + +```lua +local session, err, exists = require "resty.session".open() +if exists then + local access_token = session:get("access-token") + ngx.req.set_header("Authorization", "Bearer " .. access_token) +end ``` -Additionally you can configure Random generator with these settings: +### session:set_audience -```nginx -set $session_random_length 16; +**syntax:** *session:set_audience(audience)* + +Set session audience. + +```lua +local session = require "resty.session".new() +session.set_audience("my-service") ``` -Here follows the description of each setting: -**length** +### session:get_audience -`session.random.length` holds the length of the `session.id`. By default it is 16 bytes. -This can be configured with Nginx `set $session_random_length 16;`. +**syntax:** *audience = session:get_audience()* -## Lua API +Set session subject. -### Functions and Methods -#### session session.new(opts) +### session:set_subject -With this function you can create a new session table (i.e. the actual session instance). This allows -you to generate session table first, and set invidual configuration before calling `session:open()` or -`session:start()`. You can also pass in `opts` Lua `table` with the configurations. +**syntax:** *session:set_subject(subject)* + +Set session audience. ```lua local session = require "resty.session".new() --- set the configuration parameters before calling start -session.cookie.domain = "mydomain.com" --- call start before setting session.data parameters -session:start() -session.data.uid = 1 --- save session and update the cookie to be sent to the client -session:save() +session.set_subject("john@doe.com") ``` -This is equivalent to this: -```lua -local session = require "resty.session".new{ cookie = { domain = "mydomain.com" } } -session:start() -session.data.uid = 1 -session:save() -``` +### session:get_subject -As well as with this: +**syntax:** *subject = session:get_subject()* -```lua -local session = require "resty.session".start{ cookie = { domain = "mydomain.com" } } -session.data.uid = 1 -session:save() -``` - -#### session, present, reason = session.open(opts, keep_lock) - -With this function you can open a new session. It will create a new session Lua `table` on each call (unless called with -colon `:` as in examples above with `session.new`). Calling this function repeatedly will be a no-op when using colon `:`. -This function will return a (new) session `table` as a result. If the session cookie is supplied with user's HTTP(S) -client then this function validates the supplied session cookie. If validation is successful, the user supplied session -data will be used (if not, a new session is generated with empty data). You may supply optional session configuration -variables with `opts` argument, but be aware that many of these will only have effect if the session is a fresh session -(i.e. not loaded from user supplied cookie). If you set the `keep_lock` argument to `true` the possible lock implemented -by a storage adapter will not be released after opening the session. The second `boolean` return argument `present` will -be `true` if the user client send a valid cookie (meaning that session was already started on some earlier request), -and `false` if the new session was created (either because user client didn't send a cookie or that the cookie was not -a valid one). If the cookie was not `present` the last `string` argument `reason` will return the reason why it failed -to open a session cookie. This function will not set a client cookie or write data to database (e.g. update the expiry). -You need to call `session:start()` to really start the session. This open function is mainly used if you only want to -read data and avoid automatically sending a cookie (see also issue [#12](https://github.com/bungle/lua-resty-session/issues/12)). -But be aware that this doesn't update cookie expiration time stored in a cookie or in the database. +Get session subject. ```lua -local session = require "resty.session".open() --- Set some options (overwriting the defaults or nginx configuration variables) -local session = require "resty.session".open{ random = { length = 32 }} --- Read some data -if session.present then - ngx.print(session.data.uid) +local session, err, exists = require "resty.session".open() +if exists then + local subject = session.get_subject() end --- Now let's really start the session --- (session.started will be always false in this example): -if not session.started then - session:start() -- with some storage adapters this will held a lock. -end - -session.data.greeting = "Hello, World!" -session:save() -- this releases the possible lock held by :start() ``` -#### session, present, reason session.start(opts) - -With this function you can start a new session. It will create a new session Lua `table` on each call (unless called with -colon `:` as in examples above with `session.new`). Right now you should only start session once per request as calling -this function repeatedly will overwrite the previously started session cookie and session data. This function will return -a (new) session `table` as a result. If the session cookie is supplied with user's HTTP(S) client then this function -validates the supplied session cookie. If validation is successful, the user supplied session data will be used -(if not, a new session is generated with empty data). You may supply optional session configuration variables -with `opts` argument, but be aware that many of these will only have effect if the session is a fresh session -(i.e. not loaded from user supplied cookie). This function does also manage session cookie renewing configured -with `$session_cookie_renew`. E.g. it will send a new cookie with a new expiration time if the following is -met `session.expires - now < session.cookie.renew or session.expires > now + session.cookie.lifetime`. The second -`boolean` return argument will be `true` if the user client send a valid cookie (meaning that session was already -started on some earlier request), and `false` if the new session was created (either because user client didn't send -a cookie or that the cookie was not a valid one). On error this will return nil and error message. -```lua -local session = require "resty.session".start() --- Set some options (overwriting the defaults or nginx configuration variables) -local session = require "resty.session".start{ random = { length = 32 }} --- Always remember to: -session:close() --- OR -session:save() --- OR -session:destroy() -``` +### session:get_property + +**syntax:** *value = session:get_property(name)* -#### boolean session.destroy(opts) +Get session property. Possible property names: -This function will immediately set session data to empty table `{}`. It will also send a new cookie to -client with empty data and Expires flag `Expires=Thu, 01 Jan 1970 00:00:01 GMT` (meaning that the client -should remove the cookie, and not send it back again). This function returns a boolean value if everything went -as planned. It returns nil and error on failure. +- `"id"`: 43 bytes session id (same as nonce, but base64 url-encoded) +- `"nonce"`: 32 bytes nonce (same as session id but in raw bytes) +- `"audience"`: Current session audience +- `"subject"`: Current session subject +- `"timeout"`: Closest timeout (in seconds) (what's left of it) +- `"idling-timeout`"`: Session idling timeout (in seconds) (what's left of it) +- `"rolling-timeout`"`: Session rolling timeout (in seconds) (what's left of it) +- `"absolute-timeout`"`: Session absolute timeout (in seconds) (what's left of it) + +*Note:* the returned value may be `nil`. ```lua -require "resty.session".destroy() --- but usually you want to possibly lock (server side storages) --- the session before destroying -local session require "resty.session".start() -session:destroy() +local session, err, exists = require "resty.session".open() +if exists then + local timeout = session.get_property("timeout") +end ``` -#### string session:get_cookie() -Returns the cookie from the request or `nil` if the cookie was not found. +### session:set_remember -#### table session:parse_cookie(cookie) +**syntax:** *session:set_remember(value)* -Parses cookie and returns the data back as a `table` on success and `nil` and error on errors. +Set persistent sessions on/off. -#### boolean session:regenerate(flush, close) - -This function regenerates a session. It will generate a new session identifier (`session.id`) and optionally -flush the session data if `flush` argument evaluates `true`. It will automatically call `session:save` which -means that a new expires flag is set on the cookie, and the data is encrypted with the new parameters. With -client side sessions (`cookie` storage adapter) this overwrites the current cookie with a new one (but it -doesn't invalidate the old one as there is no state held on server side - invalidation actually happens when -the cookie's expiration time is not valid anymore). Optionally you may pass `false` to this method as a second -argument, if you don't want to `close` the session just yet, but just to regenerate a new id and save the session. -This function returns a boolean value if everything went as planned. If not it will return `nil` and error string -as a second return value. +In many login forms user is given an option for "remember me". +You can call this function based on what user selected. ```lua -local session = require "resty.session".start() -session:regenerate() --- flush the current data, and but keep session --- open and possible locks still held -session:regenerate(true, false) +local session = require "resty.session".new() +if ngx.var.args.remember then + session:set_remember(true) +end +session:set_subject(ngx.var.args.username) +session:save() ``` -#### boolean session:save(close) -This function saves the session and sends (not immediate though, as actual sending is handled by Nginx/OpenResty) -a new cookie to client (with a new expiration time and encrypted data). You need to call this function whenever -you want to save the changes made to `session.data` table. It is advised that you call this function only once -per request (no need to encrypt and set cookie many times). This function returns a boolean value if everything -went as planned. If not it will return error string as a second return value. Optionally you may pass `false` -to this method, if you don't want to `close` the session just yet, but just to save the data. +### session:get_remember + +**syntax:** *remember = session:get_remember()* + +Get state of persistent sessions. ```lua -local session = require "resty.session".start() -session.data.uid = 1 -session:save() +local session, err, exists = require "resty.session".open() +if exists then + local remember = session.get_remember() +end ``` -#### boolean, string session:close() -This function is mainly usable with storages that implement `locking` as calling this with e.g. `cookie` storage -does not do anything else than set `session.closed` to `true`. +### session:clear_request_cookie +**syntax:** *session:clear_request_cookie()* -#### session:hide() - -Sometimes, when you are using `lua-resty-session` in reverse proxy, you may want to hide the session -cookies from the upstream server. To do that you can call `session:hide()`. +Modifies the request headers by removing the session related +cookies. This is useful when you use the session library on +a proxy server and don't want the session cookies to be forwarded +to the upstream service. ```lua -local session = require "resty.session".start() -session:hide() +local session, err, exists = require "resty.session".open() +if exists then + session:clear_request_cookie() +end ``` -### Fields -#### string session.id +### session:set_headers -`session.id` holds the current session id. By default it is 16 bytes long (raw binary bytes). -It is automatically generated. +**syntax:** *session:set_headers(arg1, arg2, ...)* -#### boolean session.present +Sets request and response headers based on configuration. -`session.present` can be used to check if the session that was opened with `session.open` or `session.start` -was really a one the was received from a client. If the session is a new one, this will be false. +```lua +local session, err, exists = require "resty.session".open({ + request_headers = { "audience", "subject", "id" }, + response_headers = { "timeout", "idling-timeout", "rolling-timeout", "absolute-timeout" }, +}) +if exists then + session:set_headers() +end +``` -#### boolean session.opened +When called without arguments it will set request headers configured with `request_headers` +and response headers configured with `response_headers`. -`session.opened` can be used to check if the `session:open()` was called for the current session -object. +See [configuration](#configuration) for possible header names. -#### boolean session.started -`session.started` can be used to check if the `session:start()` was called for the current session -object. +### session:set_request_headers -#### boolean session.destroyed +**syntax:** *session:set_request_headers(arg1, arg2, ...)* -`session.destroyed` can be used to check if the `session:destroy()` was called for the current session -object. It will also set `session.opened`, `session.started`, and `session.present` to false. +Set request headers. -#### boolean session.closed +```lua +local session, err, exists = require "resty.session".open() +if exists then + session:set_request_headers("audience", "subject", "id") +end +``` -`session.closed` can be used to check if the `session:close()` was called for the current session -object. +When called without arguments it will set request headers configured with `request_headers`. -#### string session.key +See [configuration](#configuration) for possible header names. -`session.key` holds the HMAC key. It is automatically generated. Nginx configuration like -`set $session_check_ssi on;`, `set $session_check_ua on;`, `set $session_check_scheme on;` and `set $session_check_addr on;` - will have effect on the generated key. -#### table session.data +### session:set_response_headers -`session.data` holds the data part of the session cookie. This is a Lua `table`. `session.data` -is the place where you store or retrieve session variables. When you want to save the data table, -you need to call `session:save` method. +**syntax:** *session:set_response_headers(arg1, arg2, ...)* -**Setting session variable:** +Set request headers. ```lua -local session = require "resty.session".start() -session.data.uid = 1 -session:save() +local session, err, exists = require "resty.session".open() +if exists then + session:set_response_headers("timeout", "idling-timeout", "rolling-timeout", "absolute-timeout") +end ``` -**Retrieving session variable (in other request):** +When called without arguments it will set request headers configured with `response_headers`. -```lua -local session = require "resty.session".open() -local uid = session.data.uid -``` +See [configuration](#configuration) for possible header names. -#### number session.expires -`session.expires` holds the expiration time of the session (expiration time will be generated when -`session:save` method is called). +### session.info:set -#### string session.secret +**syntax:** *session.info:set(key, value)* -`session.secret` holds the secret that is used in keyed HMAC generation. +Set a value in session information store. Session information store +may be used in scenarios when you want to store data on server side +storage, but do not want to create a new session and send a new +session cookie. The information store data is not considered when +checking authentication tag or message authentication code. Thus if +you want to use this for data that needs to be encrypted, you need +to encrypt value before passing it to thus function. -#### boolean session.cookie.persistent +```lua +local session, err, exists = require "resty.session".open() +if exists then + session.info:set("last-access", ngx.now()) + session.info:save() +end +``` -`session.cookie.persistent` is by default `false`. This means that cookies are not persisted between browser sessions -(i.e. they are deleted when the browser is closed). You can enable persistent sessions if you want to by setting this -to `true`. This can be configured with Nginx `set $session_cookie_persistent on;`. +With cookie storage this still works, but it is then almost the same as +`session:set`. -#### number session.usebefore -`session.usebefore` holds the expiration time based on session usgae (expiration time will be generated -when the session is saved or started). This expiry time is only stored client-side in the cookie. -Note that just opening a session will not update the cookie! To mark the session as used you must call -`session:touch`. (You can also use `session:save` but that will also write session data to the -storage, whereas just calling `touch` reads the session data and updates the `usebefore` value in the -client-side cookie without writing to the storage, it will just be setting a new cookie) +### session.info:get -#### number session.cookie.idletime +**syntax:** *value = session.info:get(key)* -`session.cookie.idletime` holds the cookie idletime in seconds in the future. If a cookie is not used -(idle) for this time, the session becomes invalid. By default this is set to 0 seconds, meaning it is -disabled. This can be configured with Nginx `set $session_cookie_idletime 300;`. +Get a value from session information store. -#### number session.cookie.discard +```lua +local session, err, exists = require "resty.session".open() +if exists then + local last_access = session.info:get("last-access") +end +``` -`session.cookie.discard` holds the time in seconds how of long you want to keep old cookies alive when -using `regenerate` session strategy. This can be configured with Nginx `set $session_cookie_discard 10;` -(10 seconds is the default value). This works only with server side session storage adapters and when -using `regenerate` strategy (perhaps your custom strategy could utilize this too). -#### number session.cookie.renew +### session.info:save -`session.cookie.renew` holds the minimun seconds until the cookie expires, and renews cookie automatically -(i.e. sends a new cookie with a new expiration time according to `session.cookie.lifetime`). This can be configured -with Nginx `set $session_cookie_renew 600;` (600 seconds is the default value). +**syntax:** *value = session.info:save()* -#### number session.cookie.lifetime +Save information. Only updates backend storage. Does not send a new cookie (except with cookie storage). -`session.cookie.lifetime` holds the cookie lifetime in seconds in the future. By default this is set -to 3,600 seconds. This can be configured with Nginx `set $session_cookie_lifetime 3600;`. This does not -set cookie's expiration time on session only (by default) cookies, but it is used if the cookies are -configured persistent with `session.cookie.persistent == true`. See also notes about -[ssl_session_timeout](#nginx-configuration-variables). +```lua +local session = require "resty.session".new() +session.info:set("last-access", ngx.now()) +local ok, err = session.info:save() +``` -#### string session.cookie.path -`session.cookie.path` holds the value of the cookie path scope. This is by default permissive `/`. You -may want to have a more specific scope if your application resides in different path (e.g. `/forums/`). -This can be configured with Nginx `set $session_cookie_path /forums/;`. +# Cookie Format -#### string session.cookie.domain +``` +[ HEADER -------------------------------------------------------------------------------------] +[ Type || Flags || SID || Created at || Rolling Offset || Size || Tag || Idling Offset || Mac ] +[ 1B || 2B || 32B || 5B || 4B || 3B || 16B || 3B || 16B ] +``` -`session.cookie.domain` holds the value of the cookie domain. By default this is automatically set using -Nginx variable `host`. This can be configured with Nginx `set $session_cookie_domain openresty.org;`. -For `localhost` this is omitted. +and -#### string session.cookie.samesite +``` +[ PAYLOAD --] +[ Data *B ] +``` -`session.cookie.samesite` holds the value of the cookie SameSite flag. By default we do use value of `Lax`. -The possible values are `Lax`, `Strict`, `None`, and `off`. Actually, setting this parameter anything else than -`Lax`, `Strict` or `None` will turn this off (but in general, you shouldn't do it). If you want better protection -against Cross Site Request Forgery (CSRF), set this to `Strict`. Default value of `Lax` gives you quite a -good protection against CSRF, but `Strict` goes even further. +Both the `HEADER` and `PAYLOAD` are base64 url-encoded before putting in a `Set-Cookie` header. +When using a server side storage, the `PAYLOAD` is not put in the cookie. With cookie storage +the base64 url-encoded header is concatenated with base64 url-encoded payload. + +The `HEADER` is fixed size 82 bytes binary or 110 bytes in base64 url-encoded form. + +Header fields explained: + +- Type: number `1` binary packed in a single little endian byte (currently the only supported `type`). +- Flags: binary packed flags (short) in a two byte little endian form. +- SID: `32` bytes of crypto random data (Session ID). +- Created at: binary packed secs from epoch in a little endian form, truncated to 5 bytes. +- Rolling Offset: binary packed secs from creation time in a little endian form (integer). +- Size: binary packed data size (short) in a two byte little endian form. +- Tag: `16` bytes of authentication tag from AES-256-GCM encryption of the data. +- Idling Offset: binary packed secs from creation time + rolling offset in a little endian form, truncated to 3 bytes. +- Mac: `16` bytes message authentication code of the header. + + +# Data Encryption + +1. Initial keying material (IKM): + 1. derive IKM from `secret` by hashing `secret` with SHA-256, or + 2. use 32 byte IKM when passed to library with `ikm` +2. Generate 32 bytes of crypto random session id (`sid`) +3. Derive 32 byte encryption key and 12 byte initialization vector with HKDF using SHA-256 + 1. Use HKDF extract to derive a new key from `ikm` to get `key` (this step can be done just once per `ikm`): + - output length: `32` + - digest: `"sha256"` + - key: `` + - mode: `extract only` + - info: `""` + - salt: `""` + 2. Use HKDF expand to derive `44` bytes of `output`: + - output length: `44` + - digest: `"sha256"` + - key: `` + - mode: `expand only` + - info: `"encryption:"` + - salt: `""` + 3. The first 32 bytes of `output` are the encryption key (`aes-key`), and the last 12 bytes are the initialization vector (`iv`) +4. Encrypt `plaintext` (JSON encoded and optionally deflated) using AES-256-GCM to get `ciphertext` and `tag` + 1. cipher: `"aes-256-gcm"` + 2. key: `` + 3. iv: `` + 4. plaintext: `` + 5. aad: use the first 47 bytes of `header` as `aad`, that includes: + 1. Type + 2. Flags + 3. Session ID + 4. Creation Time + 5. Rolling Offset + 6. Data Size + +There is a variation for `remember` cookies on step 3, where we may use `PBKDF2` +instead of `HKDF`, depending on `remember_safety` setting. The `PBKDF2` settings: + +- output length: `44` +- digest: `"sha256"` +- password: `<key>` +- salt: `"encryption:<sid>"` +- iterations: `<1000|10000|100000|1000000>` + +Iteration counts are based on `remember_safety` setting (`"Low"`, `"Medium"`, `"High"`, `"Very High"`), +if `remember_safety` is set to `"None"`, we will use the HDKF as above. + + +# Cookie Header Authentication + +1. Derive 32 byte authentication key (`mac_key`) with HKDF using SHA-256: + 1. Use HKDF extract to derive a new key from `ikm` to get `key` (this step can be done just once per `ikm` and reused with encryption key generation): + - output length: `32` + - digest: `"sha256"` + - key: `<ikm>` + - mode: `extract only` + - info: `""` + - salt: `""` + 2. Use HKDF expand to derive `32` bytes of `mac-key`: + - output length: `32` + - digest: `"sha256"` + - key: `<key>` + - mode: `expand only` + - info: `"authentication:<sid>"` + - salt: `""` +2. Calculate message authentication code using HMAC-SHA256: + - digest: `"sha256"` + - key: `<mac-key>` + - message: use the first 66 bytes of `header`, that includes: + 1. Type + 2. Flags + 3. Session ID + 4. Creation Time + 5. Rolling Offset + 6. Data Size + 7. Tag + 8. Idling Offset + + +# Custom Storage Interface + +If you want to implement custom storage, you need to implement following interface: -#### boolean session.cookie.secure +```lua +--- +-- <custom> backend for session library +-- +-- @module <custom> -`session.cookie.secure` holds the value of the cookie `Secure` flag. meaning that when set the client will -only send the cookie with encrypted TLS/SSL connection. By default the `Secure` flag is set on all the -cookies where the request was made through TLS/SSL connection. This can be configured and forced with -Nginx `set $session_cookie_secure on;`. -#### boolean session.cookie.httponly +--- +-- Storage +-- @section instance -`session.cookie.httponly` holds the value of the cookie `HttpOnly` flag. By default this is enabled, -and I cannot think of an situation where one would want to turn this off. By keeping this on you can -prevent your session cookies access from Javascript and give some safety of XSS attacks. If you really -want to turn this off, this can be configured with Nginx `set $session_cookie_httponly off;`. -#### string session.cookie.maxsize +local metatable = {} -`session.cookie.maxsize` is used to configure maximum size of a single cookie. This value is used to split a -large cookie into chunks. By default it is `4000` bytes of serialized and encoded data which does not count -the cookie name and cookie flags. If you expect your cookies + flags be more than e.g. `4096` bytes, you -should reduce the `session.cookie.maxsize` so that a single cookie fits into `4096` bytes because otherwise -the user-agent may ignore the cookie (being too big). -#### number session.cookie.chunks +metatable.__index = metatable -`session.cookie.chunks` should be used as a read only property to determine how many separate cookies was -used for a session. Usually this is `1`, but if you are using a `cookie` storage backend and store a lot -of data in session, then the cookie is divided to `n` chunks where each stores data containing 4.000 bytes -(the last one 4000 or less). This was implemented in version 2.15. -#### boolean session.check.ssi +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end -`session.check.ssi` is additional check to validate that the request was made with the same SSL -session as when the original cookie was delivered. This check is enabled by default on releases prior 2.12 -on non-persistent sessions and disabled by default on persistent sessions and on releases 2.12 and later. -Please note that on TLS with TLS Tickets enabled, this will be empty) and not used. This is discussed on issue #5 -(https://github.com/bungle/lua-resty-session/issues/5). You can disable TLS tickets with Nginx configuration: -```nginx -ssl_session_tickets off; -``` +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + -- NYI +end -#### boolean session.check.ua -`session.check.ua` is additional check to validate that the request was made with the same user-agent browser string -as where the original cookie was delivered. This check is enabled by default. +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(name, key) + -- NYI +end -#### boolean session.check.addr -`session.check.addr` is additional check to validate that the request was made from the same remote ip-address -as where the original cookie was delivered. This check is disabled by default. +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(name, key, current_time, metadata) + -- NYI +end -#### boolean session.check.scheme -`session.check.scheme` is additional check to validate that the request was made using the same protocol -as the one used when the original cookie was delivered. This check is enabled by default. +local storage = {} -## Nginx Configuration Variables -You can set default configuration parameters directly from Nginx configuration. It's **IMPORTANT** to understand -that these are read only once (not on every request), for performance reasons. This is especially important if -you run multiple sites (with different configurations) on the same Nginx server. You can of course set the common -parameters on Nginx configuration even on that case. But if you are really supporting multiple site with different -configurations (e.g. different `session.secret` on each site), you should set these in code (see: `session.new` -and `session.start`). +--- +-- Constructors +-- @section constructors -Please note that Nginx has also its own SSL/TLS caches and timeouts. Especially note `ssl_session_timeout` if you -are running services over SSL/TLS as this will end sessions regardless of `session.cookie.lifetime`. Please adjust -that accordingly or disable `ssl_session_id` check `session.check.ssi = false` (in code) or -`set $session_check_ssi off;` (in Nginx configuration). As of 2.12 checking SSL session identifier check -(`$session_check_ssi` / `session.check.ssi`) is disabled by default because it was not reliable (most servers use -session tickets now), and it usually needed extra configuration. -You may want to add something like this to your Nginx SSL/TLS config (quite a huge cache in this example, 1 MB is -about 4.000 SSL sessions): +--- +-- Configuration +-- @section configuration -```nginx -ssl_session_cache shared:SSL:100m; -ssl_session_timeout 60m; -``` -Also note that the `ssl_session_id` may be `null` if the TLS tickets are enabled. You can disable tickets in Nginx -server with the configuration below: +--- +-- <custom> storage backend configuration +-- @field <field-name> TBD +-- @table configuration -```nginx -ssl_session_tickets off; -``` -Right now this is a workaround and may change in a future if we find alternative ways to have the added security -that we have with `ssl_session_id` with TLS tickets too. While TLS tickets are great, they also have effect on -(Perfect) Forward Secrecy, and it is adviced to disable tickets until the problems mentioned in -[The Sad State of Server-Side TLS Session Resumption Implementations](https://timtaubert.de/blog/2014/11/the-sad-state-of-server-side-tls-session-resumption-implementations/) -article are resolved. +--- +-- Create a <custom> storage. +-- +-- This creates a new shared memory storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration <custom> storage @{configuration} +-- @treturn table <custom> storage instance +function storage.new(configuration) + -- NYI + -- return setmetatable({}, metatable) +end -Here is a list of `lua-resty-session` related Nginx configuration variables that you can use to control -`lua-resty-session`: -```nginx -set $session_name session; -set $session_secret 623q4hR325t36VsCD3g567922IC0073T; -set $session_strategy default; -set $session_storage cookie; -set $session_hmac sha1; -set $session_cipher aes; -set $session_encoder base64; -set $session_serializer json; -set $session_compressor none; -set $session_cookie_persistent off; -set $session_cookie_discard 10; -set $session_cookie_idletime 0; -set $session_cookie_renew 600; -set $session_cookie_lifetime 3600; -set $session_cookie_path /; -set $session_cookie_domain openresty.org; -set $session_cookie_samesite Lax; -set $session_cookie_secure on; -set $session_cookie_httponly on; -set $session_cookie_delimiter |; -set $session_cookie_maxsize 4000; -set $session_check_ssi off; -set $session_check_ua on; -set $session_check_scheme on; -set $session_check_addr off; -set $session_random_length 16; -set $session_aes_mode cbc; -set $session_aes_size 256; -set $session_aes_hash sha512; -set $session_aes_rounds 1; +return storage ``` -## Changes - -The changes of every release of this module is recorded in [Changes.md](https://github.com/bungle/lua-resty-session/blob/master/Changes.md) file. - -## Roadmap - -* Add support for different schemes: - * Encrypt-and-MAC: The ciphertext is generated by encrypting the plaintext and then appending a MAC of the plaintext. - * MAC-then-encrypt: The ciphertext is generated by appending a MAC to the plaintext and then encrypting everything. - * Encrypt-then-MAC: The ciphertext is generated by encrypting the plaintext and then appending a MAC of the encrypted plaintext. - * Authenticated Encryption with Associated Data (AEAD) -* Add support for HMAC plugins -* Add support for `lua-resty-nettle` for more wide variety of encryption algorithms as a plugin. -* Implement cookieless server-side session support using `ssl_session_id` as a `session.id` (using a server-side storage). - -## See Also +Please check the existing implementations for the defails. And please +make a pull-request so that we can integrate it directly to library +for other users as well. -* [lua-resty-route](https://github.com/bungle/lua-resty-route) — Routing library -* [lua-resty-reqargs](https://github.com/bungle/lua-resty-reqargs) — Request arguments parser -* [lua-resty-template](https://github.com/bungle/lua-resty-template) — Templating engine -* [lua-resty-validation](https://github.com/bungle/lua-resty-validation) — Validation and filtering library -## License +# License `lua-resty-session` uses two clause BSD license. ``` -Copyright (c) 2014 – 2022 Aapo Talvensaari +Copyright (c) 2014 – 2023 Aapo Talvensaari, 2022 – 2023 Samuele Illuminati All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/config.ld b/config.ld new file mode 100644 index 00000000..7082fc28 --- /dev/null +++ b/config.ld @@ -0,0 +1,10 @@ +project = "resty.session" +description = "Session Library for OpenResty" +full_description = "`lua-resty-session` is a secure, and flexible session library for OpenResty" +title = "Session Library for OpenResty Documentation" +dir = "docs" +use_markdown_titles = true +package = "session" +format = "discount" +sort_modules=true +file = "./lib/resty" diff --git a/dev/Dockerfile b/dev/Dockerfile new file mode 100644 index 00000000..4abb52b4 --- /dev/null +++ b/dev/Dockerfile @@ -0,0 +1,17 @@ +FROM openresty/openresty:1.21.4.1-focal + +ENV DEBIAN_FRONTEND noninteractive + +USER root +RUN apt-get update && apt-get install -y gcc git + +RUN git clone https://github.com/Olivine-Labs/busted +RUN cd busted && luarocks make + +RUN luarocks install pgmoon +RUN luarocks install lua-resty-rsa +RUN luarocks install lua-resty-redis-connector +RUN luarocks install lua-resty-redis-cluster +RUN luarocks install inspect +RUN luarocks install lua_pack +RUN luarocks install LuaCov diff --git a/dist.ini b/dist.ini index e0ea6850..69b4fee2 100644 --- a/dist.ini +++ b/dist.ini @@ -1,7 +1,7 @@ name = lua-resty-session abstract = Session Library for OpenResty - Flexible and Secure -author = Aapo Talvensaari (@bungle) +author = Aapo Talvensaari (@bungle), Samuele Illuminati (@samugi) is_original = yes license = 2bsd repo_link = https://github.com/bungle/lua-resty-session -requires = openresty, openresty/lua-resty-string +requires = openresty diff --git a/docs/classes/resty.session.html b/docs/classes/resty.session.html new file mode 100644 index 00000000..7178c7d2 --- /dev/null +++ b/docs/classes/resty.session.html @@ -0,0 +1,906 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Session">Session </a></li> +<li><a href="#Constructors">Constructors </a></li> +</ul> + + +<h2>Classes</h2> +<ul class="nowrap"> + <li><strong>resty.session</strong></li> +</ul> +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis-cluster.html">resty.session.redis-cluster</a></li> + <li><a href="../modules/resty.session.redis-sentinel.html">resty.session.redis-sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Class <code>resty.session</code></h1> +<p>Session library provides HTTP session management capabilities for OpenResty based + applications, libraries and proxies.</p> +<p> +</p> + + +<h2><a href="#Session">Session </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#session.info:set">session.info:set (key, value)</a></td> + <td class="summary">Set a value in session information store.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session.info:get">session.info:get (key)</a></td> + <td class="summary">Get a value from session information store.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session.info:save">session.info:save ()</a></td> + <td class="summary">Save information.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:set">resty.session:set (key, value)</a></td> + <td class="summary">Set a value in session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:get">resty.session:get (key)</a></td> + <td class="summary">Get a value from session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:open">resty.session:open ()</a></td> + <td class="summary">Open a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:save">resty.session:save ()</a></td> + <td class="summary">Save the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:touch">resty.session:touch ()</a></td> + <td class="summary">Touch the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:refresh">resty.session:refresh ()</a></td> + <td class="summary">Refresh the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:logout">resty.session:logout ()</a></td> + <td class="summary">Logout the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:destroy">resty.session:destroy ()</a></td> + <td class="summary">Destroy the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#resty.session:close">resty.session:close ()</a></td> + <td class="summary">Close the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:hide">instance:hide ()</a></td> + <td class="summary">Hide the session.</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#resty.session.configuration">resty.session.configuration</a></td> + <td class="summary">Session configuration.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.init">module.init ([configuration])</a></td> + <td class="summary">Initialize the session library.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a new session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.open">module.open ([configuration])</a></td> + <td class="summary">Open a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.start">module.start ([configuration])</a></td> + <td class="summary">Start a session and refresh it as needed.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.logout">module.logout ([configuration])</a></td> + <td class="summary">Logout a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.destroy">module.destroy ([configuration])</a></td> + <td class="summary">Destroy a session.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Session"></a>Session </h2> + + <dl class="function"> + <dt> + <a name = "session.info:set"></a> + <strong>session.info:set (key, value)</strong> + </dt> + <dd> + Set a value in session information store. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + <pre><code>key +</code></pre> + + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + value + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "session.info:get"></a> + <strong>session.info:get (key)</strong> + </dt> + <dd> + Get a value from session information store. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + <pre><code>key +</code></pre> + + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + value + </ol> + + + + +</dd> + <dt> + <a name = "session.info:save"></a> + <strong>session.info:save ()</strong> + </dt> + <dd> + Save information. </p> + +<p> Only updates backend storage. Does not send a new cookie. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:set"></a> + <strong>resty.session:set (key, value)</strong> + </dt> + <dd> + Set a value in session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + <pre><code>key +</code></pre> + + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + value + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "resty.session:get"></a> + <strong>resty.session:get (key)</strong> + </dt> + <dd> + Get a value from session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + <pre><code>key +</code></pre> + + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + value + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:open"></a> + <strong>resty.session:open ()</strong> + </dt> + <dd> + Open a session. </p> + +<p> This can be used to open a session. It will either return an existing + session or a new session. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:save"></a> + <strong>resty.session:save ()</strong> + </dt> + <dd> + Save the session. </p> + +<p> Saves the session data and issues a new session cookie with a new session id. + When <code>remember</code> is enabled, it will also issue a new persistent cookie and + possibly save the data in backend store. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:touch"></a> + <strong>resty.session:touch ()</strong> + </dt> + <dd> + Touch the session. </p> + +<p> Updates idling offset of the session by sending an updated session cookie. + It only sends the client cookie and never calls any backend session store + APIs. Normally the <a href="../classes/resty.session.html#resty.session:refresh">session:refresh</a> is used to call this indirectly. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:refresh"></a> + <strong>resty.session:refresh ()</strong> + </dt> + <dd> + Refresh the session. </p> + +<p> Either saves the session (creating a new session id) or touches the session + depending on whether the rolling timeout is getting closer. The touch has + a threshold, by default one minute, so it may be skipped in some cases. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:logout"></a> + <strong>resty.session:logout ()</strong> + </dt> + <dd> + Logout the session. </p> + +<p> Logout either destroys the session or just clears the data for the current audience, + and saves it (logging out from the current audience). + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:destroy"></a> + <strong>resty.session:destroy ()</strong> + </dt> + <dd> + Destroy the session. </p> + +<p> Destroy the session and clear the cookies. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "resty.session:close"></a> + <strong>resty.session:close ()</strong> + </dt> + <dd> + Close the session. </p> + +<p> Just closes the session instance so that it cannot be used anymore. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:hide"></a> + <strong>instance:hide ()</strong> + </dt> + <dd> + Hide the session. </p> + +<p> Modifies the request headers by removing the session related + cookies. This is useful when you use the session library on + a proxy server and don&rsquo;t want the session cookies to be forwarded + to the upstream service. + + + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "resty.session.configuration"></a> + <strong>resty.session.configuration</strong> + </dt> + <dd> + Session configuration. + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">secret</span> + Secret used for the key derivation. The secret is hashed with SHA-256 before using it. E.g. <code>&quot;RaJKp8UQW1&quot;</code>. + </li> + <li><span class="parameter">secret_fallbacks</span> + Array of secrets that can be used as alternative secrets (when doing key rotation), E.g. <code>{ &quot;6RfrAYYzYq&quot;, &quot;MkbTkkyF9C&quot; }</code>. + </li> + <li><span class="parameter">ikm</span> + Initial key material (or ikm) can be specified directly (without using a secret) with exactly 32 bytes of data, e.g. <code>&quot;5ixIW4QVMk0dPtoIhn41Eh1I9enP2060&quot;</code> + </li> + <li><span class="parameter">ikm_fallbacks</span> + Array of initial key materials that can be used as alternative keys (when doing key rotation), E.g. <code>{ &quot;QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7&quot; }</code>. + </li> + <li><span class="parameter">cookie_prefix</span> + Cookie prefix, use <code>nil</code>, <code>&quot;__Host-&quot;</code> or <code>&quot;__Secure-&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_name</span> + Session cookie name, e.g. <code>&quot;session&quot;</code> (defaults to <code>&quot;session&quot;</code>) + </li> + <li><span class="parameter">cookie_path</span> + Cookie path, e.g. <code>&quot;/&quot;</code> (defaults to <code>&quot;/&quot;</code>) + </li> + <li><span class="parameter">cookie_domain</span> + Cookie domain, e.g. <code>&quot;example.com&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_http_only</span> + Mark cookie HTTP only, use <code>true</code> or <code>false</code> (defaults to <code>true</code>) + </li> + <li><span class="parameter">cookie_secure</span> + Mark cookie secure, use <code>nil</code>, <code>true</code> or <code>false</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_priority</span> + Cookie priority, use <code>nil</code>, <code>&quot;Low&quot;</code>, <code>&quot;Medium&quot;</code>, or <code>&quot;High&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_same_site</span> + Cookie same-site policy, use <code>nil</code>, <code>&quot;Lax&quot;</code>, <code>&quot;Strict&quot;</code>, or <code>&quot;None&quot;</code> (defaults to <code>&quot;Lax&quot;</code>) + </li> + <li><span class="parameter">cookie_same_party</span> + Mark cookie with same party flag, use <code>nil</code>, <code>true</code>, or <code>false</code> (default: <code>nil</code>) + </li> + <li><span class="parameter">cookie_partitioned</span> + Mark cookie with partitioned flag, use <code>nil</code>, <code>true</code>, or <code>false</code> (default: <code>nil</code>) + </li> + <li><span class="parameter">remember</span> + Enable or disable persistent sessions, use <code>nil</code>, <code>true</code>, or <code>false</code> (defaults to <code>false</code>) + </li> + <li><span class="parameter">remember_cookie_name</span> + Persistent session cookie name, e.g. <code>&quot;remember&quot;</code> (defaults to <code>&quot;remember&quot;</code>) + </li> + <li><span class="parameter">audience</span> + Session audience, e.g. <code>&quot;my-application&quot;</code> (defaults to <code>&quot;default&quot;</code>) + </li> + <li><span class="parameter">subject</span> + Session subject, e.g. <code>&quot;john.doe@example.com&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">stale_ttl</span> + When session is saved a new session is created, stale ttl specifies how long the old one can still be used, e.g. <code>10</code> (defaults to <code>10</code>) (in seconds) + </li> + <li><span class="parameter">idling_timeout</span> + Idling timeout specifies how long the session can be inactive until it is considered invalid, e.g. <code>900</code> (defaults to <code>900</code>, or 15 minutes) (in seconds) + </li> + <li><span class="parameter">rolling_timeout</span> + Rolling timeout specifies how long the session can be used until it needs to be renewed, e.g. <code>3600</code> (defaults to <code>3600</code>, or an hour) (in seconds) + </li> + <li><span class="parameter">absolute_timeout</span> + Absolute timeout limits how long the session can be renewed, until re-authentication is required, e.g. <code>86400</code> (defaults to <code>86400</code>, or a day) (in seconds) + </li> + <li><span class="parameter">remember_timeout</span> + Remember timeout specifies how long the persistent session is considered valid, e.g. <code>604800</code> (defaults to <code>604800</code>, or a week) (in seconds) + </li> + <li><span class="parameter">touch_threshold</span> + Touch threshold controls how frequently or infrequently the <a href="../classes/resty.session.html#resty.session:refresh">session:refresh</a> touches the cookie, e.g. <code>60</code> (defaults to <code>60</code>, or a minute) (in seconds) + </li> + <li><span class="parameter">compression_threshold</span> + Compression threshold controls when the data is deflated, e.g. <code>1024</code> (defaults to <code>1024</code>, or a kilobyte) (in bytes) + </li> + <li><span class="parameter">storage</span> + Storage is responsible of storing session data, use <code>nil</code> (data is stored in cookie), <a href="../modules/resty.session.dshm.html">dshm</a>, <a href="../modules/resty.session.file.html">file</a>, <a href="../modules/resty.session.memcached.html">memcached</a>, <a href="../modules/resty.session.mysql.html">mysql</a>, <a href="../modules/resty.session.postgres.html">postgres</a>, <a href="../modules/resty.session.redis.html">redis</a>, <a href="../modules/resty.session.redis-cluster.html">redis-cluster</a>, <a href="../modules/resty.session.redis-sentinel.html">redis-sentinel</a>, or <a href="../modules/resty.session.shm.html">shm</a>, or give a name of custom module (<code>&quot;custom.session.storage&quot;</code>), or a <a href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> that implements session storage interface (defaults to <code>nil</code>) + </li> + <li><span class="parameter">dshm</span> + Configuration for dshm storage, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">file</span> + Configuration for file storage, e.g. <code>{ path = &quot;/tmp&quot;, suffix = &quot;session&quot; }</code> + </li> + <li><span class="parameter">memcached</span> + Configuration for memcached storage, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">mysql</span> + Configuration for MySQL / MariaDB storage, e.g. <code>{ database = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">postgres</span> + Configuration for Postgres storage, e.g. <code>{ database = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">redis</span> + Configuration for Redis / Redis Sentinel / Redis Cluster storages, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">shm</span> + Configuration for shared memory storage, e.g. <code>{ zone = &quot;sessions&quot; }</code> + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "module.init"></a> + <strong>module.init ([configuration])</strong> + </dt> + <dd> + Initialize the session library. </p> + +<p> This function can be called on <a href="../classes/resty.session.html#module.init">init</a> or <code>init_worker</code> phases on OpenResty + to set global default configuration to all session instances created by this + library. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/resty.session.html#resty.session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.init({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a new session. </p> + +<p> This creates a new session instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/resty.session.html#resty.session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new() +<span class="comment">-- OR +</span><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.open"></a> + <strong>module.open ([configuration])</strong> + </dt> + <dd> + Open a session. </p> + +<p> This can be used to open a session, and it will either return an existing + session or a new session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/resty.session.html#resty.session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be opened</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session existed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="comment">-- OR +</span><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.start"></a> + <strong>module.start ([configuration])</strong> + </dt> + <dd> + Start a session and refresh it as needed. </p> + +<p> This can be used to start a session, and it will either return an existing + session or a new session. In case there is an existing session, the + session will be refreshed as well (as needed). + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/resty.session.html#resty.session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be logged out</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session was refreshed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.start() +<span class="comment">-- OR +</span><span class="keyword">local</span> session, err, exists, refreshed = <span class="global">require</span> <span class="string">"resty.session"</span>.start() + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.logout"></a> + <strong>module.logout ([configuration])</strong> + </dt> + <dd> + Logout a session. </p> + +<p> It logouts from a specific audience.</p> + +<p> A single session cookie may be shared between multiple audiences + (or applications), thus there is a need to be able to logout from + just a single audience while keeping the session for the other + audiences.</p> + +<p> When there is only a single audience, then this can be considered + equal to <a href="../classes/resty.session.html#resty.session:destroy">session.destroy</a>.</p> + +<p> When the last audience is logged out, the cookie will be destroyed + as well and invalidated on a client. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/resty.session.html#resty.session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> session exists for an audience and was logged out successfully, otherwise <code>false</code></li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why the session could not be logged out</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session was logged out, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.logout() +<span class="comment">-- OR +</span><span class="keyword">local</span> ok, err, exists, logged_out = <span class="global">require</span> <span class="string">"resty.session"</span>.logout({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.destroy"></a> + <strong>module.destroy ([configuration])</strong> + </dt> + <dd> + Destroy a session. </p> + +<p> It destroys the whole session and clears the cookies. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/resty.session.html#resty.session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> session exists and was destroyed successfully, otherwise <code>nil</code></li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be destroyed</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session was destroyed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.destroy() +<span class="comment">-- OR +</span><span class="keyword">local</span> ok, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.destroy({ + cookie_name = <span class="string">"auth"</span>, +})</pre> + </ul> + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2022-12-16 17:07:08 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/classes/session.html b/docs/classes/session.html new file mode 100644 index 00000000..1d66f2a1 --- /dev/null +++ b/docs/classes/session.html @@ -0,0 +1,545 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Functions">Functions</a></li> +<li><a href="#Tables">Tables</a></li> +<li><a href="#Methods">Methods</a></li> +</ul> + + +<h2>Classes</h2> +<ul class="nowrap"> + <li><strong>session</strong></li> +</ul> +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis-cluster.html">resty.session.redis-cluster</a></li> + <li><a href="../modules/resty.session.redis-sentinel.html">resty.session.redis-sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Class <code>session</code></h1> +<p>Session library provides HTTP session management capabilities for OpenResty based + applications, libraries and proxies.</p> +<p> +</p> + + +<h2><a href="#Functions">Functions</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#session_instance:open">session_instance:open ()</a></td> + <td class="summary">Opens session</p> + +<p> This can be used to open a session.</td> + </tr> +</table> +<h2><a href="#Tables">Tables</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#session.configuration">session.configuration</a></td> + <td class="summary">Session configuration options</td> + </tr> +</table> +<h2><a href="#Methods">Methods</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#session:init">session:init ([configuration])</a></td> + <td class="summary">Initializes session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session:new">session:new ([configuration])</a></td> + <td class="summary">Creates new session</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session:open">session:open ([configuration])</a></td> + <td class="summary">Opens session</p> + +<p> This can be used to open a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session:start">session:start ([configuration])</a></td> + <td class="summary">Starts the session and refreshes it as needed</p> + +<p> This can be used to start a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session:logout">session:logout ([configuration])</a></td> + <td class="summary">Logouts session</p> + +<p> It logouts from a specific audience.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#session:destroy">session:destroy ([configuration])</a></td> + <td class="summary">Destroys session</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Functions"></a>Functions</h2> + Methods + <dl class="function"> + <dt> + <a name = "session_instance:open"></a> + <strong>session_instance:open ()</strong> + </dt> + <dd> + Opens session</p> + +<p> This can be used to open a session. It will either return an existing + session or a new session. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be opened</li> + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Tables"></a>Tables</h2> + + <dl class="function"> + <dt> + <a name = "session.configuration"></a> + <strong>session.configuration</strong> + </dt> + <dd> + Session configuration options + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">secret</span> + Secret used for the key derivation. The secret is hashed with SHA-256 before using it. E.g. <code>&quot;RaJKp8UQW1&quot;</code>. + </li> + <li><span class="parameter">secret_fallbacks</span> + Array of secrets that can be used as alternative secrets (when doing key rotation), E.g. <code>{ &quot;6RfrAYYzYq&quot;, &quot;MkbTkkyF9C&quot; }</code>. + </li> + <li><span class="parameter">ikm</span> + Initial key material (or ikm) can be specified directly (without using a secret) with exactly 32 bytes of data, e.g. <code>&quot;5ixIW4QVMk0dPtoIhn41Eh1I9enP2060&quot;</code> + </li> + <li><span class="parameter">ikm_fallbacks</span> + Array of initial key materials that can be used as alternative keys (when doing key rotation), E.g. <code>{ &quot;QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7&quot; }</code>. + </li> + <li><span class="parameter">cookie_prefix</span> + Cookie prefix, use <code>nil</code>, <code>&quot;__Host-&quot;</code> or <code>&quot;__Secure-&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_name</span> + Session cookie name, e.g. <code>&quot;session&quot;</code> (defaults to <code>&quot;session&quot;</code>) + </li> + <li><span class="parameter">cookie_path</span> + Cookie path, e.g. <code>&quot;/&quot;</code> (defaults to <code>&quot;/&quot;</code>) + </li> + <li><span class="parameter">cookie_domain</span> + Cookie domain, e.g. <code>&quot;example.com&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_http_only</span> + Mark cookie HTTP only, use <code>true</code> or <code>false</code> (defaults to <code>true</code>) + </li> + <li><span class="parameter">cookie_secure</span> + Mark cookie secure, use <code>nil</code>, <code>true</code> or <code>false</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_priority</span> + Cookie priority, use <code>nil</code>, <code>&quot;Low&quot;</code>, <code>&quot;Medium&quot;</code>, or <code>&quot;High&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_same_site</span> + Cookie same-site policy, use <code>nil</code>, <code>&quot;Lax&quot;</code>, <code>&quot;Strict&quot;</code>, or <code>&quot;None&quot;</code> (defaults to <code>&quot;Lax&quot;</code>) + </li> + <li><span class="parameter">cookie_same_party</span> + Mark cookie with same party flag, use <code>nil</code>, <code>true</code>, or <code>false</code> (default: <code>nil</code>) + </li> + <li><span class="parameter">cookie_partitioned</span> + Mark cookie with partitioned flag, use <code>nil</code>, <code>true</code>, or <code>false</code> (default: <code>nil</code>) + </li> + <li><span class="parameter">remember</span> + Enable or disable persistent sessions, use <code>nil</code>, <code>true</code>, or <code>false</code> (defaults to <code>false</code>) + </li> + <li><span class="parameter">remember_cookie_name</span> + Persistent session cookie name, e.g. <code>&quot;remember&quot;</code> (defaults to <code>&quot;remember&quot;</code>) + </li> + <li><span class="parameter">audience</span> + Session audience, e.g. <code>&quot;my-application&quot;</code> (defaults to <code>&quot;default&quot;</code>) + </li> + <li><span class="parameter">subject</span> + Session subject, e.g. <code>&quot;john.doe@example.com&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">stale_ttl</span> + When session is saved a new session is created, stale ttl specifies how long the old one can still be used, e.g. <code>10</code> (defaults to <code>10</code>) (in seconds) + </li> + <li><span class="parameter">idling_timeout</span> + Idling timeout specifies how long the session can be inactive until it is considered invalid, e.g. <code>900</code> (defaults to <code>900</code>, or 15 minutes) (in seconds) + </li> + <li><span class="parameter">rolling_timeout</span> + Rolling timeout specifies how long the session can be used until it needs to be renewed, e.g. <code>3600</code> (defaults to <code>3600</code>, or an hour) (in seconds) + </li> + <li><span class="parameter">absolute_timeout</span> + Absolute timeout limits how long the session can be renewed, until re-authentication is required, e.g. <code>86400</code> (defaults to <code>86400</code>, or a day) (in seconds) + </li> + <li><span class="parameter">remember_timeout</span> + Remember timeout specifies how long the persistent session is considered valid, e.g. <code>604800</code> (defaults to <code>604800</code>, or a week) (in seconds) + </li> + <li><span class="parameter">storage</span> + Storage is responsible of storing session data, use <code>nil</code> (data is stored in cookie), <code>dshm</code>, <code>file</code>, <code>memcached</code>, <code>mysql</code>, <code>postgres</code>, <code>redis</code>, <code>redis-cluster</code>, <code>redis-sentinel</code>, or <code>shm</code>, or give a name of custom module (<code>&quot;custom.session.storage&quot;</code>), or a <a href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> that implements session storage interface (defaults to <code>nil</code>) + </li> + <li><span class="parameter">dshm</span> + Configuration for dshm storage, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">file</span> + Configuration for file storage, e.g. <code>{ path = &quot;/tmp&quot;, suffix = &quot;session&quot; }</code> + </li> + <li><span class="parameter">memcached</span> + Configuration for memcached storage, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">mysql</span> + Configuration for MySQL / MariaDB storage, e.g. <code>{ database = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">postgres</span> + Configuration for Postgres storage, e.g. <code>{ database = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">redis</span> + Configuration for Redis / Redis Sentinel / Redis Cluster storages, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">shm</span> + Configuration for shared memory storage, e.g. <code>{ zone = &quot;sessions&quot; }</code> + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Methods"></a>Methods</h2> + + <dl class="function"> + <dt> + <a name = "session:init"></a> + <strong>session:init ([configuration])</strong> + </dt> + <dd> + Initializes session library This function can be called on <a href="../classes/session.html#session:init">init</a> or <code>init_worker</code> phases on OpenResty + to set global default configuration to all session instances created by this + library. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/session.html#session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.init({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "session:new"></a> + <strong>session:new ([configuration])</strong> + </dt> + <dd> + Creates new session This creates a new session instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/session.html#session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new() +<span class="comment">-- OR +</span><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "session:open"></a> + <strong>session:open ([configuration])</strong> + </dt> + <dd> + Opens session</p> + +<p> This can be used to open a session. It will either return an existing + session or a new session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/session.html#session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be opened</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session existed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="comment">-- OR +</span><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "session:start"></a> + <strong>session:start ([configuration])</strong> + </dt> + <dd> + Starts the session and refreshes it as needed</p> + +<p> This can be used to start a session. It will either return an existing + session or a new session. In case there is an existing session, the + session will be refreshed as well (as needed). + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/session.html#session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be logged out</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session was refreshed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.start() +<span class="comment">-- OR +</span><span class="keyword">local</span> session, err, exists, refreshed = <span class="global">require</span> <span class="string">"resty.session"</span>.start() + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "session:logout"></a> + <strong>session:logout ([configuration])</strong> + </dt> + <dd> + Logouts session</p> + +<p> It logouts from a specific audience. </p> + +<p> A single session cookie may be shared between multiple audiences + (or applications), thus there is a need to be able to logout from + just a single audience while keeping the session for the other + audiences.</p> + +<p> When there is only a single audience, then this can be considered + equal to <a href="../classes/session.html#session:destroy">session.destroy</a>.</p> + +<p> When the last audience is logged out, the cookie will be destroyed + as well and invalidated on a client. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/session.html#session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> session exists for an audience and was logged out successfully, otherwise <code>false</code></li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why the session could not be logged out</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session was logged out, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.logout() +<span class="comment">-- OR +</span><span class="keyword">local</span> ok, err, exists, logged_out = <span class="global">require</span> <span class="string">"resty.session"</span>.logout({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "session:destroy"></a> + <strong>session:destroy ([configuration])</strong> + </dt> + <dd> + Destroys session It destroys the whole session and clears the cookies. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../classes/session.html#session.configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> session exists and was destroyed successfully, otherwise <code>nil</code></li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + information why session could not be destroyed</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session was destroyed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.destroy() +<span class="comment">-- OR +</span><span class="keyword">local</span> ok, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.destroy({ + cookie_name = <span class="string">"auth"</span>, +})</pre> + </ul> + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2022-12-16 00:35:34 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..50f17981 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,127 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + + + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="modules/resty.session.html">resty.session</a></li> + <li><a href="modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + + + <h2>Session Library for OpenResty</h2> + <p><code>lua-resty-session</code> is a secure, and flexible session library for OpenResty</p> + +<h2>Modules</h2> +<table class="module_list"> + <tr> + <td class="name" nowrap><a href="modules/resty.session.html">resty.session</a></td> + <td class="summary">Session library.</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.dshm.html">resty.session.dshm</a></td> + <td class="summary">Distributed Shared Memory (DSHM) backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.file.html">resty.session.file</a></td> + <td class="summary">File storage backend for session library.</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.file.thread.html">resty.session.file.thread</a></td> + <td class="summary">File storage backend worker thread module</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.file.utils.html">resty.session.file.utils</a></td> + <td class="summary">File storage utilities</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.memcached.html">resty.session.memcached</a></td> + <td class="summary">Memcached backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.mysql.html">resty.session.mysql</a></td> + <td class="summary">MySQL / MariaDB backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.postgres.html">resty.session.postgres</a></td> + <td class="summary">Postgres backend for session library.</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.redis.html">resty.session.redis</a></td> + <td class="summary">Redis backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></td> + <td class="summary">Redis Cluster backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.redis.common.html">resty.session.redis.common</a></td> + <td class="summary">Common Redis functions shared between Redis, + Redis Cluster and Redis Sentinel implementations.</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></td> + <td class="summary">Redis Sentinel backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.shm.html">resty.session.shm</a></td> + <td class="summary">Shared Memory (SHM) backend for session library</td> + </tr> + <tr> + <td class="name" nowrap><a href="modules/resty.session.utils.html">resty.session.utils</a></td> + <td class="summary">Common utilities for session library and storage backends</td> + </tr> +</table> + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/ldoc.css b/docs/ldoc.css new file mode 100644 index 00000000..52c4ad2b --- /dev/null +++ b/docs/ldoc.css @@ -0,0 +1,303 @@ +/* BEGIN RESET + +Copyright (c) 2010, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 2.8.2r1 +*/ +html { + color: #000; + background: #FFF; +} +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 { + margin: 0; + padding: 0; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +fieldset,img { + border: 0; +} +address,caption,cite,code,dfn,em,strong,th,var,optgroup { + font-style: inherit; + font-weight: inherit; +} +del,ins { + text-decoration: none; +} +li { + margin-left: 20px; +} +caption,th { + text-align: left; +} +h1,h2,h3,h4,h5,h6 { + font-size: 100%; + font-weight: bold; +} +q:before,q:after { + content: ''; +} +abbr,acronym { + border: 0; + font-variant: normal; +} +sup { + vertical-align: baseline; +} +sub { + vertical-align: baseline; +} +legend { + color: #000; +} +input,button,textarea,select,optgroup,option { + font-family: inherit; + font-size: inherit; + font-style: inherit; + font-weight: inherit; +} +input,button,textarea,select {*font-size:100%; +} +/* END RESET */ + +body { + margin-left: 1em; + margin-right: 1em; + font-family: arial, helvetica, geneva, sans-serif; + background-color: #ffffff; margin: 0px; +} + +code, tt { font-family: monospace; font-size: 1.1em; } +span.parameter { font-family:monospace; } +span.parameter:after { content:":"; } +span.types:before { content:"("; } +span.types:after { content:")"; } +.type { font-weight: bold; font-style:italic } + +body, p, td, th { font-size: .95em; line-height: 1.2em;} + +p, ul { margin: 10px 0 0 0px;} + +strong { font-weight: bold;} + +em { font-style: italic;} + +h1 { + font-size: 1.5em; + margin: 20px 0 20px 0; +} +h2, h3, h4 { margin: 15px 0 10px 0; } +h2 { font-size: 1.25em; } +h3 { font-size: 1.15em; } +h4 { font-size: 1.06em; } + +a:link { font-weight: bold; color: #004080; text-decoration: none; } +a:visited { font-weight: bold; color: #006699; text-decoration: none; } +a:link:hover { text-decoration: underline; } + +hr { + color:#cccccc; + background: #00007f; + height: 1px; +} + +blockquote { margin-left: 3em; } + +ul { list-style-type: disc; } + +p.name { + font-family: "Andale Mono", monospace; + padding-top: 1em; +} + +pre { + background-color: rgb(245, 245, 245); + border: 1px solid #C0C0C0; /* silver */ + padding: 10px; + margin: 10px 0 10px 0; + overflow: auto; + font-family: "Andale Mono", monospace; +} + +pre.example { + font-size: .85em; +} + +table.index { border: 1px #00007f; } +table.index td { text-align: left; vertical-align: top; } + +#container { + margin-left: 1em; + margin-right: 1em; + background-color: #f0f0f0; +} + +#product { + text-align: center; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; +} + +#product big { + font-size: 2em; +} + +#main { + background-color: #f0f0f0; + border-left: 2px solid #cccccc; +} + +#navigation { + float: left; + width: 14em; + vertical-align: top; + background-color: #f0f0f0; + overflow: visible; +} + +#navigation h2 { + background-color:#e7e7e7; + font-size:1.1em; + color:#000000; + text-align: left; + padding:0.2em; + border-top:1px solid #dddddd; + border-bottom:1px solid #dddddd; +} + +#navigation ul +{ + font-size:1em; + list-style-type: none; + margin: 1px 1px 10px 1px; +} + +#navigation li { + text-indent: -1em; + display: block; + margin: 3px 0px 0px 22px; +} + +#navigation li li a { + margin: 0px 3px 0px -1em; +} + +#content { + margin-left: 14em; + padding: 1em; + width: 700px; + border-left: 2px solid #cccccc; + border-right: 2px solid #cccccc; + background-color: #ffffff; +} + +#about { + clear: both; + padding: 5px; + border-top: 2px solid #cccccc; + background-color: #ffffff; +} + +@media print { + body { + font: 12pt "Times New Roman", "TimeNR", Times, serif; + } + a { font-weight: bold; color: #004080; text-decoration: underline; } + + #main { + background-color: #ffffff; + border-left: 0px; + } + + #container { + margin-left: 2%; + margin-right: 2%; + background-color: #ffffff; + } + + #content { + padding: 1em; + background-color: #ffffff; + } + + #navigation { + display: none; + } + pre.example { + font-family: "Andale Mono", monospace; + font-size: 10pt; + page-break-inside: avoid; + } +} + +table.module_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.module_list td { + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } +table.module_list td.summary { width: 100%; } + + +table.function_list { + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.function_list td { + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } +table.function_list td.summary { width: 100%; } + +ul.nowrap { + overflow:auto; + white-space:nowrap; +} + +dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} +dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} +dl.table h3, dl.function h3 {font-size: .95em;} + +/* stop sublists from having initial vertical space */ +ul ul { margin-top: 0px; } +ol ul { margin-top: 0px; } +ol ol { margin-top: 0px; } +ul ol { margin-top: 0px; } + +/* make the target distinct; helps when we're navigating to a function */ +a:target + * { + background-color: #FF9; +} + + +/* styles for prettification of source */ +pre .comment { color: #558817; } +pre .constant { color: #a8660d; } +pre .escape { color: #844631; } +pre .keyword { color: #aa5050; font-weight: bold; } +pre .library { color: #0e7c6b; } +pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } +pre .string { color: #8080ff; } +pre .number { color: #f8660d; } +pre .operator { color: #2239a8; font-weight: bold; } +pre .preprocessor, pre .prepro { color: #a33243; } +pre .global { color: #800080; } +pre .user-keyword { color: #800080; } +pre .prompt { color: #558817; } +pre .url { color: #272fc2; text-decoration: underline; } + diff --git a/docs/modules/resty.session.dshm.html b/docs/modules/resty.session.dshm.html new file mode 100644 index 00000000..5c219cb8 --- /dev/null +++ b/docs/modules/resty.session.dshm.html @@ -0,0 +1,397 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><strong>resty.session.dshm</strong></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.dshm</code></h1> +<p>Distributed Shared Memory (DSHM) backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Distributed shared memory storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a distributed shared memory storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Distributed shared memory storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + The prefix for the keys stored in DSHM. + </li> + <li><span class="parameter">suffix</span> + The suffix for the keys stored in DSHM. + </li> + <li><span class="parameter">host</span> + The host to connect (defaults to <code>&quot;127.0.0.1&quot;</code>). + </li> + <li><span class="parameter">port</span> + The port to connect (defaults to <code>4321</code>). + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSL (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">server_name</span> + The server name for the new TLS extension Server Name Indication (SNI). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a distributed shared memory storage. </p> + +<p> This creates a new distributed shared memory storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + DSHM storage <a href="../modules/resty.session.dshm.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + DSHM storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.file-thread.html b/docs/modules/resty.session.file-thread.html new file mode 100644 index 00000000..6e83417e --- /dev/null +++ b/docs/modules/resty.session.file-thread.html @@ -0,0 +1,195 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Functions">Functions</a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><strong>resty.session.file-thread</strong></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis-cluster.html">resty.session.redis-cluster</a></li> + <li><a href="../modules/resty.session.redis-sentinel.html">resty.session.redis-sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.file-thread</code></h1> +<p>File storage backend worker thread module</p> +<p> +</p> + + +<h2><a href="#Functions">Functions</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#set">set (path, content)</a></td> + <td class="summary">Store data in file.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#get">get (path)</a></td> + <td class="summary">Read data from a file.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#delete">delete (path)</a></td> + <td class="summary">Delete a file.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Functions"></a>Functions</h2> + + <dl class="function"> + <dt> + <a name = "set"></a> + <strong>set (path, content)</strong> + </dt> + <dd> + Store data in file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + <pre><code> file path +</code></pre> + + </li> + <li><span class="parameter">content</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file content + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "get"></a> + <strong>get (path)</strong> + </dt> + <dd> + Read data from a file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file to read + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + content</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "delete"></a> + <strong>delete (path)</strong> + </dt> + <dd> + Delete a file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file to read + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2022-12-23 14:06:58 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.file.html b/docs/modules/resty.session.file.html new file mode 100644 index 00000000..840b615f --- /dev/null +++ b/docs/modules/resty.session.file.html @@ -0,0 +1,367 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><strong>resty.session.file</strong></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.file</code></h1> +<p>File storage backend for session library.</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">File storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a file storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + File storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + File prefix for session file. + </li> + <li><span class="parameter">suffix</span> + File suffix (or extension without <code>.</code>) for session file. + </li> + <li><span class="parameter">pool</span> + Name of the thread pool under which file writing happens (available on Linux only). + </li> + <li><span class="parameter">path</span> + Path (or directory) under which session files are created. + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a file storage. </p> + +<p> This creates a new file storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + file storage <a href="../modules/resty.session.file.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + file storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.file.thread.html b/docs/modules/resty.session.file.thread.html new file mode 100644 index 00000000..b0c19459 --- /dev/null +++ b/docs/modules/resty.session.file.thread.html @@ -0,0 +1,331 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Functions">Functions</a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><strong>resty.session.file.thread</strong></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.file.thread</code></h1> +<p>File storage backend worker thread module</p> +<p> +</p> + + +<h2><a href="#Functions">Functions</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.set">module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.GET">module.GET (path, prefix, suffix, name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.delete">module.delete (path, prefix, suffix, name, key, current_time)</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.read_metadata">module.read_metadata (path, prefix, suffix, name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Functions"></a>Functions</h2> + + <dl class="function"> + <dt> + <a name = "module.set"></a> + <strong>module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the path where sessions are stored + </li> + <li><span class="parameter">prefix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the prefix for session files + </li> + <li><span class="parameter">suffix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the suffix for session files + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "module.GET"></a> + <strong>module.GET (path, prefix, suffix, name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the path where sessions are stored + </li> + <li><span class="parameter">prefix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the prefix for session files + </li> + <li><span class="parameter">suffix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the suffix for session files + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "module.delete"></a> + <strong>module.delete (path, prefix, suffix, name, key, current_time)</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the path where sessions are stored + </li> + <li><span class="parameter">prefix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the prefix for session files + </li> + <li><span class="parameter">suffix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the suffix for session files + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "module.read_metadata"></a> + <strong>module.read_metadata (path, prefix, suffix, name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the path where sessions are stored + </li> + <li><span class="parameter">prefix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the prefix for session files + </li> + <li><span class="parameter">suffix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the suffix for session files + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session audience + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session subject + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.file.utils.html b/docs/modules/resty.session.file.utils.html new file mode 100644 index 00000000..6c1dfa0e --- /dev/null +++ b/docs/modules/resty.session.file.utils.html @@ -0,0 +1,350 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Functions">Functions</a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><strong>resty.session.file.utils</strong></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.file.utils</code></h1> +<p>File storage utilities</p> +<p> +</p> + + +<h2><a href="#Functions">Functions</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#file_create">file_create (path, content)</a></td> + <td class="summary">Store data in file.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#file_append">file_append (path, data)</a></td> + <td class="summary">Append data in file.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#file_read">file_read (path)</a></td> + <td class="summary">Read data from a file.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#get_modification">get_modification (path)</a></td> + <td class="summary">Get the value modification time of a file.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#meta_get_key">meta_get_key (audience, subject)</a></td> + <td class="summary">Given an audience and a subject, generate a metadata key.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#validate_file_name">validate_file_name (prefix, suffix, name, filename)</a></td> + <td class="summary">Validate a file name.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#cleanup">cleanup (path, prefix, suffix, name, current_time)</a></td> + <td class="summary">Clean up expired session and metadata files.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Functions"></a>Functions</h2> + + <dl class="function"> + <dt> + <a name = "file_create"></a> + <strong>file_create (path, content)</strong> + </dt> + <dd> + Store data in file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file path + </li> + <li><span class="parameter">content</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file content + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "file_append"></a> + <strong>file_append (path, data)</strong> + </dt> + <dd> + Append data in file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file path + </li> + <li><span class="parameter">data</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file data + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "file_read"></a> + <strong>file_read (path)</strong> + </dt> + <dd> + Read data from a file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + file to read + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + content</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "get_modification"></a> + <strong>get_modification (path)</strong> + </dt> + <dd> + Get the value modification time of a file. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the path to the file + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "meta_get_key"></a> + <strong>meta_get_key (audience, subject)</strong> + </dt> + <dd> + Given an audience and a subject, generate a metadata key. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session audience + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session subject + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + metadata key + </ol> + + + + +</dd> + <dt> + <a name = "validate_file_name"></a> + <strong>validate_file_name (prefix, suffix, name, filename)</strong> + </dt> + <dd> + Validate a file name. + Run a few checks to try to determine if the file is managed by this library + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">prefix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the prefix for session files + </li> + <li><span class="parameter">suffix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the suffix for session files + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">filename</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the name of the file + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">true</span> or <span class="type">false</span></span> + whether the file is managed by the library or not + </ol> + + + + +</dd> + <dt> + <a name = "cleanup"></a> + <strong>cleanup (path, prefix, suffix, name, current_time)</strong> + </dt> + <dd> + Clean up expired session and metadata files. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the path where sessions are stored + </li> + <li><span class="parameter">prefix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the prefix for session files + </li> + <li><span class="parameter">suffix</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the suffix for session files + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">true</span> or <span class="type">false</span></span> + whether clean up completed + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.html b/docs/modules/resty.session.html new file mode 100644 index 00000000..6b8739ab --- /dev/null +++ b/docs/modules/resty.session.html @@ -0,0 +1,1307 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Session">Session </a></li> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Initialization">Initialization </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Helpers">Helpers </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><strong>resty.session</strong></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session</code></h1> +<p>Session library.</p> +<p> Session library provides HTTP session management capabilities for OpenResty based + applications, libraries and proxies.</p> + + +<h2><a href="#Session">Session </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance.info:set">instance.info:set (key, value)</a></td> + <td class="summary">Set a value in session information store.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance.info:get">instance.info:get (key)</a></td> + <td class="summary">Get a value from session information store.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance.info:save">instance.info:save ()</a></td> + <td class="summary">Save information.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_data">instance:set_data (data)</a></td> + <td class="summary">Set session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get_data">instance:get_data ()</a></td> + <td class="summary">Get session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (key, value)</a></td> + <td class="summary">Set a value in session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (key)</a></td> + <td class="summary">Get a value from session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_audience">instance:set_audience (audience)</a></td> + <td class="summary">Set session audience.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get_audience">instance:get_audience ()</a></td> + <td class="summary">Get session audience.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_subject">instance:set_subject (subject)</a></td> + <td class="summary">Set session subject.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get_subject">instance:get_subject ()</a></td> + <td class="summary">Get session subject.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get_property">instance:get_property ()</a></td> + <td class="summary">Get session property.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_remember">instance:set_remember (value)</a></td> + <td class="summary">Set persistent sessions on/off.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get_remember">instance:get_remember ()</a></td> + <td class="summary">Get state of persistent sessions.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:open">instance:open ()</a></td> + <td class="summary">Open a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:save">instance:save ()</a></td> + <td class="summary">Save the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:touch">instance:touch ()</a></td> + <td class="summary">Touch the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:refresh">instance:refresh ()</a></td> + <td class="summary">Refresh the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:logout">instance:logout ()</a></td> + <td class="summary">Logout the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:destroy">instance:destroy ()</a></td> + <td class="summary">Destroy the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:close">instance:close ()</a></td> + <td class="summary">Close the session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:clear_request_cookie">instance:clear_request_cookie ()</a></td> + <td class="summary">Clear the request session cookie.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_headers">instance:set_headers ([...])</a></td> + <td class="summary">Sets request and response headers.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_request_headers">instance:set_request_headers ([...])</a></td> + <td class="summary">Set request headers.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:set_response_headers">instance:set_response_headers ([...])</a></td> + <td class="summary">Set response headers.</td> + </tr> +</table> +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Session configuration.</td> + </tr> +</table> +<h2><a href="#Initialization">Initialization </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.init">module.init ([configuration])</a></td> + <td class="summary">Initialize the session library.</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a new session.</td> + </tr> +</table> +<h2><a href="#Helpers">Helpers </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.open">module.open ([configuration])</a></td> + <td class="summary">Open a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.start">module.start ([configuration])</a></td> + <td class="summary">Start a session and refresh it as needed.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.logout">module.logout ([configuration])</a></td> + <td class="summary">Logout a session.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.destroy">module.destroy ([configuration])</a></td> + <td class="summary">Destroy a session.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Session"></a>Session </h2> + + <dl class="function"> + <dt> + <a name = "instance.info:set"></a> + <strong>instance.info:set (key, value)</strong> + </dt> + <dd> + Set a value in session information store. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + key + </li> + <li><span class="parameter">value</span> + value + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "instance.info:get"></a> + <strong>instance.info:get (key)</strong> + </dt> + <dd> + Get a value from session information store. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + value + </ol> + + + + +</dd> + <dt> + <a name = "instance.info:save"></a> + <strong>instance.info:save ()</strong> + </dt> + <dd> + Save information. </p> + +<p> Only updates backend storage. Does not send a new cookie. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:set_data"></a> + <strong>instance:set_data (data)</strong> + </dt> + <dd> + Set session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">data</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + data + </li> + </ul> + + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="keyword">if</span> <span class="keyword">not</span> exists <span class="keyword">then</span> + session:set_data({ + cart = {}, + }) + session:save() +<span class="keyword">end</span></pre> + </ul> + +</dd> + <dt> + <a name = "instance:get_data"></a> + <strong>instance:get_data ()</strong> + </dt> + <dd> + Get session data. + + + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="keyword">if</span> exists <span class="keyword">then</span> + <span class="keyword">local</span> data = session:get_data() + ngx.req.set_header(<span class="string">"Authorization"</span>, <span class="string">"Bearer "</span> .. data.access_token) +<span class="keyword">end</span></pre> + </ul> + +</dd> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (key, value)</strong> + </dt> + <dd> + Set a value in session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + key + </li> + <li><span class="parameter">value</span> + value</p> + +<p> local session, err, exists = require &ldquo;resty.session&rdquo;.open() + if not exists then + session:set(&ldquo;access-token&rdquo;, &ldquo;eyJ&hellip;&rdquo;) + session:save() + end + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (key)</strong> + </dt> + <dd> + Get a value from session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="keyword">if</span> exists <span class="keyword">then</span> + <span class="keyword">local</span> access_token = session:get(<span class="string">"access-token"</span>) + ngx.req.set_header(<span class="string">"Authorization"</span>, <span class="string">"Bearer "</span> .. access_token) +<span class="keyword">end</span></pre> + </ul> + +</dd> + <dt> + <a name = "instance:set_audience"></a> + <strong>instance:set_audience (audience)</strong> + </dt> + <dd> + Set session audience. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + audience + </li> + </ul> + + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new() +session.set_audience(<span class="string">"my-service"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "instance:get_audience"></a> + <strong>instance:get_audience ()</strong> + </dt> + <dd> + Get session audience. + + + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + audience + </ol> + + + + +</dd> + <dt> + <a name = "instance:set_subject"></a> + <strong>instance:set_subject (subject)</strong> + </dt> + <dd> + Set session subject. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + subject + </li> + </ul> + + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new() +session.set_subject(<span class="string">"john@doe.com"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "instance:get_subject"></a> + <strong>instance:get_subject ()</strong> + </dt> + <dd> + Get session subject. + + + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + subject + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="keyword">if</span> exists <span class="keyword">then</span> + <span class="keyword">local</span> subject = session.get_subject() +<span class="keyword">end</span></pre> + </ul> + +</dd> + <dt> + <a name = "instance:get_property"></a> + <strong>instance:get_property ()</strong> + </dt> + <dd> + Get session property. </p> + +<p> Possible property names: + <em> <code>&quot;id&quot;</code>: 43 bytes session id (same as nonce, but base64 url-encoded) + </em> <code>&quot;nonce&quot;</code>: 32 bytes nonce (same as session id but in raw bytes) + <em> <code>&quot;audience&quot;</code>: Current session audience + </em> <code>&quot;subject&quot;</code>: Current session subject + <em> <code>&quot;timeout&quot;</code>: Closest timeout (in seconds) (what&rsquo;s left of it) + </em> <code>&quot;idling-timeout</code>&ldquo;<code>: Session idling timeout (in seconds) (what&apos;s left of it) + <em> </code>"rolling-timeout<code>&quot;</code>: Session rolling timeout (in seconds) (what&rsquo;s left of it) + </em> <code>&quot;absolute-timeout</code>&rdquo;<code>: Session absolute timeout (in seconds) (what's left of it)</code> + + + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">number</span></span> + metadata + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="keyword">if</span> exists <span class="keyword">then</span> + <span class="keyword">local</span> timeout = session.get_property(<span class="string">"timeout"</span>) +<span class="keyword">end</span></pre> + </ul> + +</dd> + <dt> + <a name = "instance:set_remember"></a> + <strong>instance:set_remember (value)</strong> + </dt> + <dd> + Set persistent sessions on/off. </p> + +<p> In many login forms user is given an option for &ldquo;remember me&rdquo;. + You can call this function based on what user selected. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> to enable persistent session, <code>false</code> to disable them + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "instance:get_remember"></a> + <strong>instance:get_remember ()</strong> + </dt> + <dd> + Get state of persistent sessions. + + + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">boolean</span></span> + <code>true</code> when persistent sessions are enabled, otherwise <code>false</code> + </ol> + + + + +</dd> + <dt> + <a name = "instance:open"></a> + <strong>instance:open ()</strong> + </dt> + <dd> + Open a session. </p> + +<p> This can be used to open a session. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:save"></a> + <strong>instance:save ()</strong> + </dt> + <dd> + Save the session. </p> + +<p> Saves the session data and issues a new session cookie with a new session id. + When <code>remember</code> is enabled, it will also issue a new persistent cookie and + possibly save the data in backend store. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:touch"></a> + <strong>instance:touch ()</strong> + </dt> + <dd> + Touch the session. </p> + +<p> Updates idling offset of the session by sending an updated session cookie. + It only sends the client cookie and never calls any backend session store + APIs. Normally the <a href="../modules/resty.session.html#instance:refresh">session:refresh</a> is used to call this indirectly. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:refresh"></a> + <strong>instance:refresh ()</strong> + </dt> + <dd> + Refresh the session. </p> + +<p> Either saves the session (creating a new session id) or touches the session + depending on whether the rolling timeout is getting closer, which means + by default when &frac34; of rolling timeout is spent &ndash; 45 minutes with default + rolling timeout of an hour. The touch has a threshold, by default one minute, + so it may be skipped in some cases (you can call <code>session:touch()</code> to force it). + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:logout"></a> + <strong>instance:logout ()</strong> + </dt> + <dd> + Logout the session. </p> + +<p> Logout either destroys the session or just clears the data for the current audience, + and saves it (logging out from the current audience). + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:destroy"></a> + <strong>instance:destroy ()</strong> + </dt> + <dd> + Destroy the session. </p> + +<p> Destroy the session and clear the cookies. + + + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:close"></a> + <strong>instance:close ()</strong> + </dt> + <dd> + Close the session. </p> + +<p> Just closes the session instance so that it cannot be used anymore. + + + + + + + +</dd> + <dt> + <a name = "instance:clear_request_cookie"></a> + <strong>instance:clear_request_cookie ()</strong> + </dt> + <dd> + Clear the request session cookie. </p> + +<p> Modifies the request headers by removing the session related + cookies. This is useful when you use the session library on + a proxy server and don&rsquo;t want the session cookies to be forwarded + to the upstream service. + + + + + + + +</dd> + <dt> + <a name = "instance:set_headers"></a> + <strong>instance:set_headers ([...])</strong> + </dt> + <dd> + Sets request and response headers. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">...</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + + + (<em>optional</em>) + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "instance:set_request_headers"></a> + <strong>instance:set_request_headers ([...])</strong> + </dt> + <dd> + Set request headers. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">...</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + + + (<em>optional</em>) + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "instance:set_response_headers"></a> + <strong>instance:set_response_headers ([...])</strong> + </dt> + <dd> + Set response headers. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">...</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + + + (<em>optional</em>) + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Session configuration. + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">secret</span> + Secret used for the key derivation. The secret is hashed with SHA-256 before using it. E.g. <code>&quot;RaJKp8UQW1&quot;</code>. + </li> + <li><span class="parameter">secret_fallbacks</span> + Array of secrets that can be used as alternative secrets (when doing key rotation), E.g. <code>{ &quot;6RfrAYYzYq&quot;, &quot;MkbTkkyF9C&quot; }</code>. + </li> + <li><span class="parameter">ikm</span> + Initial key material (or ikm) can be specified directly (without using a secret) with exactly 32 bytes of data. E.g. <code>&quot;5ixIW4QVMk0dPtoIhn41Eh1I9enP2060&quot;</code> + </li> + <li><span class="parameter">ikm_fallbacks</span> + Array of initial key materials that can be used as alternative keys (when doing key rotation), E.g. <code>{ &quot;QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7&quot; }</code>. + </li> + <li><span class="parameter">cookie_prefix</span> + Cookie prefix, use <code>nil</code>, <code>&quot;__Host-&quot;</code> or <code>&quot;__Secure-&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_name</span> + Session cookie name, e.g. <code>&quot;session&quot;</code> (defaults to <code>&quot;session&quot;</code>) + </li> + <li><span class="parameter">cookie_path</span> + Cookie path, e.g. <code>&quot;/&quot;</code> (defaults to <code>&quot;/&quot;</code>) + </li> + <li><span class="parameter">cookie_domain</span> + Cookie domain, e.g. <code>&quot;example.com&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_http_only</span> + Mark cookie HTTP only, use <code>true</code> or <code>false</code> (defaults to <code>true</code>) + </li> + <li><span class="parameter">cookie_secure</span> + Mark cookie secure, use <code>nil</code>, <code>true</code> or <code>false</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_priority</span> + Cookie priority, use <code>nil</code>, <code>&quot;Low&quot;</code>, <code>&quot;Medium&quot;</code>, or <code>&quot;High&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">cookie_same_site</span> + Cookie same-site policy, use <code>nil</code>, <code>&quot;Lax&quot;</code>, <code>&quot;Strict&quot;</code>, <code>&quot;None&quot;</code>, or <code>&quot;Default&quot;</code> (defaults to <code>&quot;Lax&quot;</code>) + </li> + <li><span class="parameter">cookie_same_party</span> + Mark cookie with same party flag, use <code>nil</code>, <code>true</code>, or <code>false</code> (default: <code>nil</code>) + </li> + <li><span class="parameter">cookie_partitioned</span> + Mark cookie with partitioned flag, use <code>nil</code>, <code>true</code>, or <code>false</code> (default: <code>nil</code>) + </li> + <li><span class="parameter">remember</span> + Enable or disable persistent sessions, use <code>nil</code>, <code>true</code>, or <code>false</code> (defaults to <code>false</code>) + </li> + <li><span class="parameter">remember_safety</span> + Remember cookie key derivation complexity, use <code>nil</code>, <code>&quot;None&quot;</code> (fast), <code>&quot;Low&quot;</code>, <code>&quot;Medium&quot;</code>, <code>&quot;High&quot;</code> or <code>&quot;Very High&quot;</code> (slow) (defaults to <code>&quot;Medium&quot;</code>) + </li> + <li><span class="parameter">remember_cookie_name</span> + Persistent session cookie name, e.g. <code>&quot;remember&quot;</code> (defaults to <code>&quot;remember&quot;</code>) + </li> + <li><span class="parameter">audience</span> + Session audience, e.g. <code>&quot;my-application&quot;</code> (defaults to <code>&quot;default&quot;</code>) + </li> + <li><span class="parameter">subject</span> + Session subject, e.g. <code>&quot;john.doe@example.com&quot;</code> (defaults to <code>nil</code>) + </li> + <li><span class="parameter">enforce_same_subject</span> + When set to <code>true</code>, audiences need to share the same subject. The library removes non-subject matching audience data on save. + </li> + <li><span class="parameter">stale_ttl</span> + When session is saved a new session is created, stale ttl specifies how long the old one can still be used, e.g. <code>10</code> (defaults to <code>10</code>) (in seconds) + </li> + <li><span class="parameter">idling_timeout</span> + Idling timeout specifies how long the session can be inactive until it is considered invalid, e.g. <code>900</code> (defaults to <code>900</code>, or 15 minutes) (in seconds) + </li> + <li><span class="parameter">rolling_timeout</span> + Rolling timeout specifies how long the session can be used until it needs to be renewed, e.g. <code>3600</code> (defaults to <code>3600</code>, or an hour) (in seconds) + </li> + <li><span class="parameter">absolute_timeout</span> + Absolute timeout limits how long the session can be renewed, until re-authentication is required, e.g. <code>86400</code> (defaults to <code>86400</code>, or a day) (in seconds) + </li> + <li><span class="parameter">remember_rolling_timeout</span> + Remember timeout specifies how long the persistent session is considered valid, e.g. <code>604800</code> (defaults to <code>604800</code>, or a week) (in seconds) + </li> + <li><span class="parameter">remember_absolute_timeout</span> + Remember absolute timeout limits how long the persistent session can be renewed, until re-authentication is required, e.g. <code>2592000</code> (defaults to <code>2592000</code>, or 30 days) (in seconds) + </li> + <li><span class="parameter">hash_storage_key</span> + Whether to hash or not the storage key. With storage key hashed it is impossible to decrypt data on server side without having a cookie too (defaults to <code>false</code>). + </li> + <li><span class="parameter">hash_subject</span> + Whether to hash or not the subject when <code>store_metadata</code> is enabled, e.g. for PII reasons (defaults to <code>false</code>). + </li> + <li><span class="parameter">store_metadata</span> + Whether to also store metadata of sessions, such as collecting data of sessions for a specific audience belonging to a specific subject (defaults to <code>false</code>). + </li> + <li><span class="parameter">touch_threshold</span> + Touch threshold controls how frequently or infrequently the <a href="../modules/resty.session.html#instance:refresh">session:refresh</a> touches the cookie, e.g. <code>60</code> (defaults to <code>60</code>, or a minute) (in seconds) + </li> + <li><span class="parameter">compression_threshold</span> + Compression threshold controls when the data is deflated, e.g. <code>1024</code> (defaults to <code>1024</code>, or a kilobyte) (in bytes) + </li> + <li><span class="parameter">request_headers</span> + Set of headers to send to upstream, use <code>id</code>, <code>audience</code>, <code>subject</code>, <code>timeout</code>, <code>idling-timeout</code>, <code>rolling-timeout</code>, <code>absolute-timeout</code>. E.g. <code>{ &quot;id&quot;, &quot;timeout&quot; }</code> will set <code>Session-Id</code> and <code>Session-Timeout</code> request headers when <a href="../modules/resty.session.html#instance:set_headers">set_headers</a> is called. + </li> + <li><span class="parameter">response_headers</span> + Set of headers to send to downstream, use <code>id</code>, <code>audience</code>, <code>subject</code>, <code>timeout</code>, <code>idling-timeout</code>, <code>rolling-timeout</code>, <code>absolute-timeout</code>. E.g. <code>{ &quot;id&quot;, &quot;timeout&quot; }</code> will set <code>Session-Id</code> and <code>Session-Timeout</code> response headers when <a href="../modules/resty.session.html#instance:set_headers">set_headers</a> is called. + </li> + <li><span class="parameter">storage</span> + Storage is responsible of storing session data, use <code>nil</code> or <code>&quot;cookie&quot;</code> (data is stored in cookie), <code>&quot;dshm&quot;</code>, <code>&quot;file&quot;</code>, <code>&quot;memcached&quot;</code>, <code>&quot;mysql&quot;</code>, <code>&quot;postgres&quot;</code>, <code>&quot;redis&quot;</code>, or <code>&quot;shm&quot;</code>, or give a name of custom module (<code>&quot;custom-storage&quot;</code>), or a <a href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> that implements session storage interface (defaults to <code>nil</code>) + </li> + <li><span class="parameter">dshm</span> + Configuration for dshm storage, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">file</span> + Configuration for file storage, e.g. <code>{ path = &quot;/tmp&quot;, suffix = &quot;session&quot; }</code> + </li> + <li><span class="parameter">memcached</span> + Configuration for memcached storage, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">mysql</span> + Configuration for MySQL / MariaDB storage, e.g. <code>{ database = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">postgres</span> + Configuration for Postgres storage, e.g. <code>{ database = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">redis</span> + Configuration for Redis / Redis Sentinel / Redis Cluster storages, e.g. <code>{ prefix = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">shm</span> + Configuration for shared memory storage, e.g. <code>{ zone = &quot;sessions&quot; }</code> + </li> + <li><span class="parameter">custom</span> + -storage"] Custom storage (loaded with <code>require &quot;custom-storage&quot;</code>) configuration + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Initialization"></a>Initialization </h2> + + <dl class="function"> + <dt> + <a name = "module.init"></a> + <strong>module.init ([configuration])</strong> + </dt> + <dd> + Initialize the session library. </p> + +<p> This function can be called on <a href="../modules/resty.session.html#module.init">init</a> or <code>init_worker</code> phases on OpenResty + to set global default configuration to all session instances created by this + library. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../modules/resty.session.html#configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.init({ + audience = <span class="string">"my-application"</span>, + storage = <span class="string">"redis"</span>, + redis = { + username = <span class="string">"session"</span>, + password = <span class="string">"storage"</span>, + }, +})</pre> + </ul> + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a new session. </p> + +<p> This creates a new session instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../modules/resty.session.html#configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new() +<span class="comment">-- OR +</span><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.new({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> +</dl> + <h2 class="section-header "><a name="Helpers"></a>Helpers </h2> + + <dl class="function"> + <dt> + <a name = "module.open"></a> + <strong>module.open ([configuration])</strong> + </dt> + <dd> + Open a session. </p> + +<p> This can be used to open a session, and it will either return an existing + session or a new session. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../modules/resty.session.html#configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session existed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.open() +<span class="comment">-- OR +</span><span class="keyword">local</span> session, err, exists = <span class="global">require</span> <span class="string">"resty.session"</span>.open({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.start"></a> + <strong>module.start ([configuration])</strong> + </dt> + <dd> + Start a session and refresh it as needed. </p> + +<p> This can be used to start a session, and it will either return an existing + session or a new session. In case there is an existing session, the + session will be refreshed as well (as needed). + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../modules/resty.session.html#configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session instance</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code>, if session was refreshed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> session = <span class="global">require</span> <span class="string">"resty.session"</span>.start() +<span class="comment">-- OR +</span><span class="keyword">local</span> session, err, exists, refreshed = <span class="global">require</span> <span class="string">"resty.session"</span>.start({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.logout"></a> + <strong>module.logout ([configuration])</strong> + </dt> + <dd> + Logout a session. </p> + +<p> It logouts from a specific audience.</p> + +<p> A single session cookie may be shared between multiple audiences + (or applications), thus there is a need to be able to logout from + just a single audience while keeping the session for the other + audiences.</p> + +<p> When there is only a single audience, then this can be considered + equal to <a href="../modules/resty.session.html#module.destroy">session.destroy</a>.</p> + +<p> When the last audience is logged out, the cookie will be destroyed + as well and invalidated on a client. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../modules/resty.session.html#configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> session exists for an audience and was logged out successfully, otherwise <code>false</code></li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session was logged out, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.logout() +<span class="comment">-- OR +</span><span class="keyword">local</span> ok, err, exists, logged_out = <span class="global">require</span> <span class="string">"resty.session"</span>.logout({ + audience = <span class="string">"my-application"</span>, +})</pre> + </ul> + +</dd> + <dt> + <a name = "module.destroy"></a> + <strong>module.destroy ([configuration])</strong> + </dt> + <dd> + Destroy a session. </p> + +<p> It destroys the whole session and clears the cookies. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session <a href="../modules/resty.session.html#configuration">configuration</a> overrides + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> session exists and was destroyed successfully, otherwise <code>nil</code></li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session existed, otherwise <code>false</code></li> + <li> + <span class="types"><span class="type">boolean</span></span> + <code>true</code> if session was destroyed, otherwise <code>false</code></li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="global">require</span> <span class="string">"resty.session"</span>.destroy() +<span class="comment">-- OR +</span><span class="keyword">local</span> ok, err, exists, destroyed = <span class="global">require</span> <span class="string">"resty.session"</span>.destroy({ + cookie_name = <span class="string">"auth"</span>, +})</pre> + </ul> + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.memcached.html b/docs/modules/resty.session.memcached.html new file mode 100644 index 00000000..d62f0be5 --- /dev/null +++ b/docs/modules/resty.session.memcached.html @@ -0,0 +1,400 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><strong>resty.session.memcached</strong></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.memcached</code></h1> +<p>Memcached backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Distributed shared memory storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a memcached storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Distributed shared memory storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + Prefix for the keys stored in memcached. + </li> + <li><span class="parameter">suffix</span> + Suffix for the keys stored in memcached. + </li> + <li><span class="parameter">host</span> + The host to connect (defaults to <code>&quot;127.0.0.1&quot;</code>). + </li> + <li><span class="parameter">port</span> + The port to connect (defaults to <code>11211</code>). + </li> + <li><span class="parameter">socket</span> + The socket file to connect to (defaults to <code>nil</code>). + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSL (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">server_name</span> + The server name for the new TLS extension Server Name Indication (SNI). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a memcached storage. </p> + +<p> This creates a new memcached storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + memcached storage <a href="../modules/resty.session.memcached.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + memcached storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.mysql.html b/docs/modules/resty.session.mysql.html new file mode 100644 index 00000000..7e867f0c --- /dev/null +++ b/docs/modules/resty.session.mysql.html @@ -0,0 +1,480 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +<li><a href="#Database">Database </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><strong>resty.session.mysql</strong></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.mysql</code></h1> +<p>MySQL / MariaDB backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Postgres storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a MySQL / MariaDB storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> +<h2><a href="#Database">Database </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#sessions">sessions</a></td> + <td class="summary">Sessions table.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#metadata">metadata</a></td> + <td class="summary">Sessions metadata table.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Postgres storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">host</span> + The host to connect (defaults to <code>&quot;127.0.0.1&quot;</code>). + </li> + <li><span class="parameter">port</span> + The port to connect (defaults to <code>3306</code>). + </li> + <li><span class="parameter">socket</span> + The socket file to connect to (defaults to <code>nil</code>). + </li> + <li><span class="parameter">username</span> + The database username to authenticate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">password</span> + Password for authentication, may be required depending on server configuration. + </li> + <li><span class="parameter">charset</span> + The character set used on the MySQL connection (defaults to <code>&quot;ascii&quot;</code>). + </li> + <li><span class="parameter">database</span> + The database name to connect. + </li> + <li><span class="parameter">table_name</span> + Name of database table to which to store session data (defaults to <code>&quot;sessions&quot;</code>). + </li> + <li><span class="parameter">table_name_meta</span> + Name of database meta data table to which to store session meta data (defaults to <code>&quot;sessions_meta&quot;</code>). + </li> + <li><span class="parameter">max_packet_size</span> + The upper limit for the reply packets sent from the MySQL server (defaults to 1 MB). + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSL (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a MySQL / MariaDB storage. </p> + +<p> This creates a new MySQL / MariaDB storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + mysql/mariadb storage <a href="../modules/resty.session.mysql.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + mysql/mariadb storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Database"></a>Database </h2> + + <dl class="function"> + <dt> + <a name = "sessions"></a> + <strong>sessions</strong> + </dt> + <dd> + Sessions table. </p> + +<p> Database table that stores session data. + + + + + + + <h3>Usage:</h3> + <ul> + <pre class="example">CREATE TABLE IF NOT EXISTS sessions ( + sid CHAR(<span class="number">43</span>) PRIMARY KEY, + name VARCHAR(<span class="number">255</span>), + data MEDIUMTEXT, + exp DATETIME, + INDEX (exp) +) CHARACTER SET ascii;</pre> + </ul> + +</dd> + <dt> + <a name = "metadata"></a> + <strong>metadata</strong> + </dt> + <dd> + Sessions metadata table. </p> + +<p> This is only needed if you want to store session metadata. + + + + + + + <h3>Usage:</h3> + <ul> + <pre class="example">CREATE TABLE IF NOT EXISTS sessions_meta ( + aud VARCHAR(<span class="number">255</span>), + sub VARCHAR(<span class="number">255</span>), + sid CHAR(<span class="number">43</span>), + PRIMARY KEY (aud, sub, sid), + CONSTRAINT FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE ON UPDATE CASCADE +) CHARACTER SET ascii;</pre> + </ul> + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.postgres.html b/docs/modules/resty.session.postgres.html new file mode 100644 index 00000000..033b737a --- /dev/null +++ b/docs/modules/resty.session.postgres.html @@ -0,0 +1,476 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +<li><a href="#Database">Database </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><strong>resty.session.postgres</strong></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.postgres</code></h1> +<p>Postgres backend for session library.</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Postgres storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a Postgres storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> +<h2><a href="#Database">Database </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#sessions">sessions</a></td> + <td class="summary">Sessions table.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#metadata">metadata</a></td> + <td class="summary">Sessions metadata table.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Postgres storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">host</span> + The host to connect (defaults to <code>&quot;127.0.0.1&quot;</code>). + </li> + <li><span class="parameter">port</span> + The port to connect (defaults to <code>5432</code>). + </li> + <li><span class="parameter">application</span> + Set the name of the connection as displayed in pg_stat_activity (defaults to <code>&quot;pgmoon&quot;</code>). + </li> + <li><span class="parameter">username</span> + The database username to authenticate (defaults to <code>&quot;postgres&quot;</code>). + </li> + <li><span class="parameter">password</span> + Password for authentication, may be required depending on server configuration. + </li> + <li><span class="parameter">database</span> + The database name to connect. + </li> + <li><span class="parameter">table_name</span> + Name of database table to which to store session data (can be <code>database schema</code> prefixed) (defaults to <code>&quot;sessions&quot;</code>). + </li> + <li><span class="parameter">table_name_meta</span> + Name of database meta data table to which to store session meta data (can be <code>database schema</code> prefixed) (defaults to <code>&quot;sessions_meta&quot;</code>). + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSL (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">ssl_required</span> + Abort the connection if the server does not support SSL connections (defaults to <code>nil</code>). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a Postgres storage. </p> + +<p> This creates a new Postgres storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + postgres storage <a href="../modules/resty.session.postgres.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + postgres storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Database"></a>Database </h2> + + <dl class="function"> + <dt> + <a name = "sessions"></a> + <strong>sessions</strong> + </dt> + <dd> + Sessions table. </p> + +<p> Database table that stores session data. + + + + + + + <h3>Usage:</h3> + <ul> + <pre class="example">CREATE TABLE IF NOT EXISTS sessions ( + sid TEXT PRIMARY KEY, + name TEXT, + data TEXT, + exp TIMESTAMP WITH TIME ZONE +); +CREATE INDEX ON sessions (exp);</pre> + </ul> + +</dd> + <dt> + <a name = "metadata"></a> + <strong>metadata</strong> + </dt> + <dd> + Sessions metadata table. </p> + +<p> This is only needed if you want to store session metadata. + + + + + + + <h3>Usage:</h3> + <ul> + <pre class="example">CREATE TABLE IF NOT EXISTS sessions_meta ( + aud TEXT, + sub TEXT, + sid TEXT REFERENCES sessions (sid) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (aud, sub, sid) +);</pre> + </ul> + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.redis-cluster.html b/docs/modules/resty.session.redis-cluster.html new file mode 100644 index 00000000..04b67b02 --- /dev/null +++ b/docs/modules/resty.session.redis-cluster.html @@ -0,0 +1,413 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file-thread.html">resty.session.file-thread</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><strong>resty.session.redis-cluster</strong></li> + <li><a href="../modules/resty.session.redis-sentinel.html">resty.session.redis-sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.redis-cluster</code></h1> +<p>Redis Cluster backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Redis Cluster storage backend configuration</td> + </tr> + <tr> + <td class="name" nowrap><a href="#nodes">nodes</a></td> + <td class="summary">Cluster Nodes</td> + </tr> + <tr> + <td class="name" nowrap><a href="#node">node</a></td> + <td class="summary">Cluster Node</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a Redis Cluster storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Redis Cluster storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + prefix for the keys stored in redis + </li> + <li><span class="parameter">suffix</span> + suffix for the keys stored in redis + </li> + <li><span class="parameter">name</span> + redis cluster name + </li> + <li><span class="parameter">nodes</span> + redis cluster nodes + </li> + <li><span class="parameter">lock_zone</span> + shared dictionary name for locks + </li> + <li><span class="parameter">lock_prefix</span> + shared dictionary name prefix for lock + </li> + <li><span class="parameter">max_redirections</span> + maximum retry attempts for redirection + </li> + <li><span class="parameter">max_connection_attempts</span> + maximum retry attempts for connection + </li> + <li><span class="parameter">max_connection_timeout</span> + maximum connection timeout in total among the retries + </li> + <li><span class="parameter">username</span> + the database username to authenticate + </li> + <li><span class="parameter">password</span> + password for authentication + </li> + <li><span class="parameter">connect_timeout</span> + controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method + </li> + <li><span class="parameter">send_timeout</span> + controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method + </li> + <li><span class="parameter">read_timeout</span> + controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method + </li> + <li><span class="parameter">keepalive_timeout</span> + controls the default maximal idle time of the connections in the connection pool + </li> + <li><span class="parameter">pool</span> + a custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + the size of the connection pool, + </li> + <li><span class="parameter">backlog</span> + a queue size to use when the connection pool is full (configured with @pool_size) + </li> + <li><span class="parameter">ssl</span> + enable ssl (defaults to <code>false</code>) + </li> + <li><span class="parameter">ssl_verify</span> + verify server certificate (defaults to <code>nil</code>) + </li> + <li><span class="parameter">server_name</span> + the server name for the new TLS extension Server Name Indication (SNI) + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "nodes"></a> + <strong>nodes</strong> + </dt> + <dd> + Cluster Nodes An array of cluster nodes. + + + + + + + +</dd> + <dt> + <a name = "node"></a> + <strong>node</strong> + </dt> + <dd> + Cluster Node + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">ip</span> + the ip address to connect (defaults to <code>&quot;127.0.0.1&quot;</code>) + </li> + <li><span class="parameter">port</span> + the port to connect (defaults to <code>6379</code>) + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a Redis Cluster storage. </p> + +<p> This creates a new Redis Cluster storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis cluster storage <a href="../modules/resty.session.redis-cluster.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis cluster storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + </li> + <li><span class="parameter">remember</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2022-12-23 14:06:58 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.redis-sentinel.html b/docs/modules/resty.session.redis-sentinel.html new file mode 100644 index 00000000..6571d7a8 --- /dev/null +++ b/docs/modules/resty.session.redis-sentinel.html @@ -0,0 +1,410 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file-thread.html">resty.session.file-thread</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis-cluster.html">resty.session.redis-cluster</a></li> + <li><strong>resty.session.redis-sentinel</strong></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.redis-sentinel</code></h1> +<p>Redis Sentinel backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Redis Sentinel storage backend configuration</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sentinels">sentinels</a></td> + <td class="summary">Sentinels</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sentinel">sentinel</a></td> + <td class="summary">Sentinel</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a Redis Sentinel storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Redis Sentinel storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + prefix for the keys stored in redis + </li> + <li><span class="parameter">suffix</span> + suffix for the keys stored in redis + </li> + <li><span class="parameter">master</span> + name of master + </li> + <li><span class="parameter">role</span> + <code>&quot;master&quot;</code> or <code>&quot;slave&quot;</code> + </li> + <li><span class="parameter">sentinels</span> + redis sentinels + </li> + <li><span class="parameter">sentinel_username</span> + optional sentinel username + </li> + <li><span class="parameter">sentinel_password</span> + optional sentinel password + </li> + <li><span class="parameter">username</span> + the database username to authenticate + </li> + <li><span class="parameter">password</span> + password for authentication + </li> + <li><span class="parameter">database</span> + the database to connect + </li> + <li><span class="parameter">connect_timeout</span> + controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method + </li> + <li><span class="parameter">send_timeout</span> + controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method + </li> + <li><span class="parameter">read_timeout</span> + controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method + </li> + <li><span class="parameter">keepalive_timeout</span> + controls the default maximal idle time of the connections in the connection pool + </li> + <li><span class="parameter">pool</span> + a custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + the size of the connection pool, + </li> + <li><span class="parameter">backlog</span> + a queue size to use when the connection pool is full (configured with @pool_size) + </li> + <li><span class="parameter">ssl</span> + enable ssl (defaults to <code>false</code>) + </li> + <li><span class="parameter">ssl_verify</span> + verify server certificate (defaults to <code>nil</code>) + </li> + <li><span class="parameter">server_name</span> + the server name for the new TLS extension Server Name Indication (SNI) + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "sentinels"></a> + <strong>sentinels</strong> + </dt> + <dd> + Sentinels An array of sentinels. + + + + + + + +</dd> + <dt> + <a name = "sentinel"></a> + <strong>sentinel</strong> + </dt> + <dd> + Sentinel + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">host</span> + the host to connect + </li> + <li><span class="parameter">port</span> + the port to connect + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a Redis Sentinel storage. </p> + +<p> This creates a new Redis Sentinel storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis sentinel storage <a href="../modules/resty.session.redis-sentinel.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis sentinel storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + </li> + <li><span class="parameter">remember</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2022-12-23 14:06:58 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.redis.cluster.html b/docs/modules/resty.session.redis.cluster.html new file mode 100644 index 00000000..f3dbd228 --- /dev/null +++ b/docs/modules/resty.session.redis.cluster.html @@ -0,0 +1,463 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><strong>resty.session.redis.cluster</strong></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.redis.cluster</code></h1> +<p>Redis Cluster backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Redis Cluster storage backend configuration</td> + </tr> + <tr> + <td class="name" nowrap><a href="#nodes">nodes</a></td> + <td class="summary">Cluster Nodes</td> + </tr> + <tr> + <td class="name" nowrap><a href="#node">node</a></td> + <td class="summary">Cluster Node</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a Redis Cluster storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Redis Cluster storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + Prefix for the keys stored in redis. + </li> + <li><span class="parameter">suffix</span> + Suffix for the keys stored in redis. + </li> + <li><span class="parameter">name</span> + Redis cluster name. + </li> + <li><span class="parameter">nodes</span> + Redis cluster nodes. + </li> + <li><span class="parameter">lock_zone</span> + Shared dictionary name for locks. + </li> + <li><span class="parameter">lock_prefix</span> + Shared dictionary name prefix for lock. + </li> + <li><span class="parameter">max_redirections</span> + Maximum retry attempts for redirection. + </li> + <li><span class="parameter">max_connection_attempts</span> + Maximum retry attempts for connection. + </li> + <li><span class="parameter">max_connection_timeout</span> + Maximum connection timeout in total among the retries. + </li> + <li><span class="parameter">username</span> + The database username to authenticate. + </li> + <li><span class="parameter">password</span> + Password for authentication. + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + controls The default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + controls The default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSL (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">server_name</span> + The server name for the new TLS extension Server Name Indication (SNI). + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "nodes"></a> + <strong>nodes</strong> + </dt> + <dd> + Cluster Nodes An array of cluster nodes. + + + + + + + +</dd> + <dt> + <a name = "node"></a> + <strong>node</strong> + </dt> + <dd> + Cluster Node + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">ip</span> + The IP address to connect (defaults to <code>&quot;127.0.0.1&quot;</code>). + </li> + <li><span class="parameter">port</span> + The port to connect (defaults to <code>6379</code>). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a Redis Cluster storage. </p> + +<p> This creates a new Redis Cluster storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis cluster storage <a href="../modules/resty.session.redis.cluster.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis cluster storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.redis.common.html b/docs/modules/resty.session.redis.common.html new file mode 100644 index 00000000..1a4625a4 --- /dev/null +++ b/docs/modules/resty.session.redis.common.html @@ -0,0 +1,321 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Functions">Functions</a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><strong>resty.session.redis.common</strong></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.redis.common</code></h1> +<p>Common Redis functions shared between Redis, + Redis Cluster and Redis Sentinel implementations.</p> +<p> +</p> + + +<h2><a href="#Functions">Functions</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.SET">module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.GET">module.GET (storage, red, name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.UNLINK">module.UNLINK (storage, red, name, key, current_time[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#module.READ_METADATA">module.READ_METADATA (storage, red, name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Functions"></a>Functions</h2> + + <dl class="function"> + <dt> + <a name = "module.SET"></a> + <strong>module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">storage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the storage + </li> + <li><span class="parameter">red</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the redis instance + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "module.GET"></a> + <strong>module.GET (storage, red, name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">storage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the storage + </li> + <li><span class="parameter">red</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the redis instance + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "module.UNLINK"></a> + <strong>module.UNLINK (storage, red, name, key, current_time[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">storage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the storage + </li> + <li><span class="parameter">red</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the redis instance + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "module.READ_METADATA"></a> + <strong>module.READ_METADATA (storage, red, name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">storage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the storage + </li> + <li><span class="parameter">red</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + the redis instance + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.redis.html b/docs/modules/resty.session.redis.html new file mode 100644 index 00000000..15f3e74a --- /dev/null +++ b/docs/modules/resty.session.redis.html @@ -0,0 +1,409 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><strong>resty.session.redis</strong></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.redis</code></h1> +<p>Redis backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Redis storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a Redis storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Redis storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + Prefix for the keys stored in Redis. + </li> + <li><span class="parameter">suffix</span> + Suffix for the keys stored in Redis. + </li> + <li><span class="parameter">host</span> + The host to connect (defaults to <code>&quot;127.0.0.1&quot;</code>). + </li> + <li><span class="parameter">port</span> + The port to connect (defaults to <code>6379</code>). + </li> + <li><span class="parameter">socket</span> + The socket file to connect to (defaults to <code>nil</code>). + </li> + <li><span class="parameter">username</span> + The database username to authenticate. + </li> + <li><span class="parameter">password</span> + Password for authentication. + </li> + <li><span class="parameter">database</span> + The database to connect. + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSL (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">server_name</span> + The server name for the new TLS extension Server Name Indication (SNI). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a Redis storage. </p> + +<p> This creates a new Redis storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis storage <a href="../modules/resty.session.redis.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.redis.sentinel.html b/docs/modules/resty.session.redis.sentinel.html new file mode 100644 index 00000000..867f76ea --- /dev/null +++ b/docs/modules/resty.session.redis.sentinel.html @@ -0,0 +1,460 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><strong>resty.session.redis.sentinel</strong></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.redis.sentinel</code></h1> +<p>Redis Sentinel backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Redis Sentinel storage backend configuration</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sentinels">sentinels</a></td> + <td class="summary">Sentinels</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sentinel">sentinel</a></td> + <td class="summary">Sentinel</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a Redis Sentinel storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Redis Sentinel storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + Prefix for the keys stored in redis. + </li> + <li><span class="parameter">suffix</span> + Suffix for the keys stored in redis. + </li> + <li><span class="parameter">master</span> + Name of master. + </li> + <li><span class="parameter">role</span> + <code>&quot;master&quot;</code> or <code>&quot;slave&quot;</code>. + </li> + <li><span class="parameter">sentinels</span> + Redis Sentinels. + </li> + <li><span class="parameter">sentinel_username</span> + Optional sentinel username. + </li> + <li><span class="parameter">sentinel_password</span> + Optional sentinel password. + </li> + <li><span class="parameter">username</span> + The database username to authenticate. + </li> + <li><span class="parameter">password</span> + Password for authentication. + </li> + <li><span class="parameter">database</span> + The database to connect. + </li> + <li><span class="parameter">connect_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>connect</code> method. + </li> + <li><span class="parameter">send_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>send</code> method. + </li> + <li><span class="parameter">read_timeout</span> + Controls the default timeout value used in TCP/unix-domain socket object&rsquo;s <code>receive</code> method. + </li> + <li><span class="parameter">keepalive_timeout</span> + Controls the default maximal idle time of the connections in the connection pool. + </li> + <li><span class="parameter">pool</span> + A custom name for the connection pool being used. + </li> + <li><span class="parameter">pool_size</span> + The size of the connection pool. + </li> + <li><span class="parameter">backlog</span> + A queue size to use when the connection pool is full (configured with @pool_size). + </li> + <li><span class="parameter">ssl</span> + Enable SSK (defaults to <code>false</code>). + </li> + <li><span class="parameter">ssl_verify</span> + Verify server certificate (defaults to <code>nil</code>). + </li> + <li><span class="parameter">server_name</span> + The server name for the new TLS extension Server Name Indication (SNI). + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "sentinels"></a> + <strong>sentinels</strong> + </dt> + <dd> + Sentinels An array of sentinels. + + + + + + + +</dd> + <dt> + <a name = "sentinel"></a> + <strong>sentinel</strong> + </dt> + <dd> + Sentinel + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">host</span> + The host to connect. + </li> + <li><span class="parameter">port</span> + The port to connect. + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a Redis Sentinel storage. </p> + +<p> This creates a new Redis Sentinel storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis sentinel storage <a href="../modules/resty.session.redis.sentinel.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + redis sentinel storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.shm.html b/docs/modules/resty.session.shm.html new file mode 100644 index 00000000..3f0e40a2 --- /dev/null +++ b/docs/modules/resty.session.shm.html @@ -0,0 +1,364 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Configuration">Configuration </a></li> +<li><a href="#Constructors">Constructors </a></li> +<li><a href="#Storage">Storage </a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><strong>resty.session.shm</strong></li> + <li><a href="../modules/resty.session.utils.html">resty.session.utils</a></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.shm</code></h1> +<p>Shared Memory (SHM) backend for session library</p> +<p> +</p> + + +<h2><a href="#Configuration">Configuration </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#configuration">configuration</a></td> + <td class="summary">Shared memory storage backend configuration</td> + </tr> +</table> +<h2><a href="#Constructors">Constructors </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#module.new">module.new ([configuration])</a></td> + <td class="summary">Create a SHM storage.</td> + </tr> +</table> +<h2><a href="#Storage">Storage </a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#instance:set">instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</a></td> + <td class="summary">Store session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:get">instance:get (name, key)</a></td> + <td class="summary">Retrieve session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:delete">instance:delete (name, key[, metadata])</a></td> + <td class="summary">Delete session data.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#instance:read_metadata">instance:read_metadata (name, audience, subject, current_time)</a></td> + <td class="summary">Read session metadata.</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Configuration"></a>Configuration </h2> + + <dl class="function"> + <dt> + <a name = "configuration"></a> + <strong>configuration</strong> + </dt> + <dd> + Shared memory storage backend configuration + + + <h3>Fields:</h3> + <ul> + <li><span class="parameter">prefix</span> + Prefix for the keys stored in SHM. + </li> + <li><span class="parameter">suffix</span> + Suffix for the keys stored in SHM. + </li> + <li><span class="parameter">zone</span> + A name of shared memory zone (defaults to <code>sessions</code>). + </li> + </ul> + + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Constructors"></a>Constructors </h2> + + <dl class="function"> + <dt> + <a name = "module.new"></a> + <strong>module.new ([configuration])</strong> + </dt> + <dd> + Create a SHM storage. </p> + +<p> This creates a new shared memory storage instance. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + shm storage <a href="../modules/resty.session.shm.html#configuration">configuration</a> + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + shm storage instance + </ol> + + + + +</dd> +</dl> + <h2 class="section-header "><a name="Storage"></a>Storage </h2> + + <dl class="function"> + <dt> + <a name = "instance:set"></a> + <strong>instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata], remember)</strong> + </dt> + <dd> + Store session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session value + </li> + <li><span class="parameter">ttl</span> + <span class="types"><span class="type">number</span></span> + session ttl + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + <li><span class="parameter">old_key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + old session id + (<em>optional</em>) + </li> + <li><span class="parameter">stale_ttl</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + stale ttl + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + table of metadata + (<em>optional</em>) + </li> + <li><span class="parameter">remember</span> + <span class="types"><span class="type">boolean</span></span> + whether storing persistent session or not + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">true</span> or <span class="type">nil</span></span> + ok</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:get"></a> + <strong>instance:get (name, key)</strong> + </dt> + <dd> + Retrieve session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:delete"></a> + <strong>instance:delete (name, key[, metadata])</strong> + </dt> + <dd> + Delete session data. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">metadata</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session meta data + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">boolean</span> or <span class="type">nil</span></span> + session data</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> + <dt> + <a name = "instance:read_metadata"></a> + <strong>instance:read_metadata (name, audience, subject, current_time)</strong> + </dt> + <dd> + Read session metadata. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + cookie name + </li> + <li><span class="parameter">audience</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + session key + </li> + <li><span class="parameter">current_time</span> + <span class="types"><span class="type">number</span></span> + current time + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + session metadata</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + error message</li> + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/docs/modules/resty.session.utils.html b/docs/modules/resty.session.utils.html new file mode 100644 index 00000000..0bea37c1 --- /dev/null +++ b/docs/modules/resty.session.utils.html @@ -0,0 +1,1340 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +<head> + <title>Session Library for OpenResty Documentation</title> + <link rel="stylesheet" href="../ldoc.css" type="text/css" /> +</head> +<body> + +<div id="container"> + +<div id="product"> + <div id="product_logo"></div> + <div id="product_name"><big><b></b></big></div> + <div id="product_description"></div> +</div> <!-- id="product" --> + + +<div id="main"> + + +<!-- Menu --> + +<div id="navigation"> +<br/> +<h1>resty.session</h1> + +<ul> + <li><a href="../index.html">Index</a></li> +</ul> + +<h2>Contents</h2> +<ul> +<li><a href="#Functions">Functions</a></li> +</ul> + + +<h2>Modules</h2> +<ul class="nowrap"> + <li><a href="../modules/resty.session.html">resty.session</a></li> + <li><a href="../modules/resty.session.dshm.html">resty.session.dshm</a></li> + <li><a href="../modules/resty.session.file.html">resty.session.file</a></li> + <li><a href="../modules/resty.session.file.thread.html">resty.session.file.thread</a></li> + <li><a href="../modules/resty.session.file.utils.html">resty.session.file.utils</a></li> + <li><a href="../modules/resty.session.memcached.html">resty.session.memcached</a></li> + <li><a href="../modules/resty.session.mysql.html">resty.session.mysql</a></li> + <li><a href="../modules/resty.session.postgres.html">resty.session.postgres</a></li> + <li><a href="../modules/resty.session.redis.html">resty.session.redis</a></li> + <li><a href="../modules/resty.session.redis.cluster.html">resty.session.redis.cluster</a></li> + <li><a href="../modules/resty.session.redis.common.html">resty.session.redis.common</a></li> + <li><a href="../modules/resty.session.redis.sentinel.html">resty.session.redis.sentinel</a></li> + <li><a href="../modules/resty.session.shm.html">resty.session.shm</a></li> + <li><strong>resty.session.utils</strong></li> +</ul> + +</div> + +<div id="content"> + +<h1>Module <code>resty.session.utils</code></h1> +<p>Common utilities for session library and storage backends</p> +<p> +</p> + + +<h2><a href="#Functions">Functions</a></h2> +<table class="function_list"> + <tr> + <td class="name" nowrap><a href="#bpack">bpack (size, value)</a></td> + <td class="summary">Binary pack unsigned integer.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#bunpack">bunpack (size, value)</a></td> + <td class="summary">Binary unpack unsigned integer.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#trim">trim (value)</a></td> + <td class="summary">Trim whitespace from the start and from the end of string.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#encode_json">encode_json (value)</a></td> + <td class="summary">JSON encode value.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#decode_json">decode_json (value)</a></td> + <td class="summary">JSON decode value.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#encode_base64url">encode_base64url (value)</a></td> + <td class="summary">Base64 URL encode value.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#decode_base64url">decode_base64url (value)</a></td> + <td class="summary">Base64 URL decode value</td> + </tr> + <tr> + <td class="name" nowrap><a href="#base64_size">base64_size (size)</a></td> + <td class="summary">Base64 size from original size (without padding).</td> + </tr> + <tr> + <td class="name" nowrap><a href="#deflate">deflate (data)</a></td> + <td class="summary">Compress the data with deflate algorithm.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#inflate">inflate (data)</a></td> + <td class="summary">Decompress the data compressed with deflate algorithm.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#rand_bytes">rand_bytes (length)</a></td> + <td class="summary">Generate crypto random bytes.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sha256">sha256 (value)</a></td> + <td class="summary">Calculates SHA-256 hash of the value.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#derive_hkdf_sha256">derive_hkdf_sha256 (ikm, nonce, usage, size)</a></td> + <td class="summary">Derive a new key using HKDF with SHA-256.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#derive_pbkdf2_hmac_sha256">derive_pbkdf2_hmac_sha256 (pass, salt, usage, size, iterations)</a></td> + <td class="summary">Derive a new key using PBKDF2 with SHA-256.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#derive_aes_gcm_256_key_and_iv">derive_aes_gcm_256_key_and_iv (ikm, nonce[, safety])</a></td> + <td class="summary">Derive a new AES-256 GCM-mode key and initialization vector.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#derive_hmac_sha256_key">derive_hmac_sha256_key (ikm, nonce)</a></td> + <td class="summary">Derive HMAC SHA-256 key for message authentication using HDKF with SHA-256.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#encrypt_aes_256_gcm">encrypt_aes_256_gcm (key, iv, plaintext, aad)</a></td> + <td class="summary">Encrypt plain text using AES-256 in GCM-mode.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#decrypt_aes_256_gcm">decrypt_aes_256_gcm (key, iv, plaintext, aad, tag)</a></td> + <td class="summary">Decrypt ciphertext using AES-256 in GCM-mode.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#hmac_sha256">hmac_sha256 (key, value)</a></td> + <td class="summary">Calculate message authentication code with HMAC with SHA-256.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#load_storage">load_storage (storage[, configuration])</a></td> + <td class="summary">Loads session storage and creates a new instance using session configuration.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#errmsg">errmsg ([err], msg, ...)</a></td> + <td class="summary">Helper to format error messages.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#get_name">get_name (storage, name, key, subject)</a></td> + <td class="summary">Helper to create a delimited key.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#set_flag">set_flag (flags, flag)</a></td> + <td class="summary">Helper to turn on a flag.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#unset_flag">unset_flag (flags, flag)</a></td> + <td class="summary">Helper to turn off a flag.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#has_flag">has_flag (flags, flag)</a></td> + <td class="summary">Helper to check if flag is enabled.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#meta_get_value">meta_get_value (key, exp)</a></td> + <td class="summary">Helper to get the value used to store metadata for a certain aud and sub + Empty exp means the session id has been invalidated</td> + </tr> + <tr> + <td class="name" nowrap><a href="#meta_get_next">meta_get_next (val, index)</a></td> + <td class="summary">Function to extract the next key and exp from a serialized + metadata list, starting from index</td> + </tr> + <tr> + <td class="name" nowrap><a href="#meta_get_latest">meta_get_latest (sessions)</a></td> + <td class="summary">Function to filter out the latest valid key:exp from a + serialized list, used to store session metadata</td> + </tr> +</table> + +<br/> +<br/> + + + <h2 class="section-header "><a name="Functions"></a>Functions</h2> + + <dl class="function"> + <dt> + <a name = "bpack"></a> + <strong>bpack (size, value)</strong> + </dt> + <dd> + <p>Binary pack unsigned integer. </p> + +<p> Returns binary packed version of an integer in little endian unsigned format.</p> + +<p> Size can be:</p> + +<ul> +<li><code>1</code>, pack input as a little endian unsigned char (<code>&lt;C</code>)</li> +<li><code>2</code>, pack input as a little endian unsigned short (<code>&lt;S</code>)</li> +<li><code>3</code>, pack input as a little endian unsigned integer (truncated) (<code>&lt;I</code>)</li> +<li><code>4</code>, pack input as a little endian unsigned integer (<code>&lt;I</code>)</li> +<li><code>5</code>, pack input as a little endian unsigned long (truncated) (<code>&lt;L</code>)</li> +<li><code>6</code>, pack input as a little endian unsigned long (truncated) (<code>&lt;L</code>)</li> +<li><code>7</code>, pack input as a little endian unsigned long (truncated) (<code>&lt;L</code>)</li> +<li><code>8</code>, pack input as a little endian unsigned long (<code>&lt;L</code>)</li> +</ul> + + + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">size</span> + <span class="types"><span class="type">number</span></span> + size of binary packed output + </li> + <li><span class="parameter">value</span> + <span class="types"><span class="type">number</span></span> + value to binary pack + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + binary packed value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> packed_128 = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.bpack(<span class="number">1</span>, <span class="number">128</span>) +<span class="keyword">local</span> packed_now = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.bpack(<span class="number">8</span>, ngx.time())</pre> + </ul> + +</dd> + <dt> + <a name = "bunpack"></a> + <strong>bunpack (size, value)</strong> + </dt> + <dd> + <p>Binary unpack unsigned integer. </p> + +<p> Returns number from a little endian unsigned binary packed format.</p> + +<p> Size can be:</p> + +<ul> +<li><code>1</code>, unpack input from little endian unsigned char (<code>&lt;C</code>)</li> +<li><code>2</code>, unpack input from little endian unsigned short (<code>&lt;S</code>)</li> +<li><code>3</code>, unpack input from little endian unsigned integer (truncated) (<code>&lt;I</code>)</li> +<li><code>4</code>, unpack input from little endian unsigned integer (<code>&lt;I</code>)</li> +<li><code>5</code>, unpack input from little endian unsigned integer (truncated) (<code>&lt;L</code>)</li> +<li><code>6</code>, unpack input from little endian unsigned integer (truncated) (<code>&lt;L</code>)</li> +<li><code>7</code>, unpack input from little endian unsigned integer (truncated) (<code>&lt;L</code>)</li> +<li><code>8</code>, unpack input from little endian unsigned long (<code>&lt;L</code>)</li> +</ul> + + + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">size</span> + <span class="types"><span class="type">number</span></span> + size of binary packed output + </li> + <li><span class="parameter">value</span> + <span class="types"><span class="type">number</span></span> + value to binary pack + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">number</span></span> + binary unpacked value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> value = <span class="number">128</span> +<span class="keyword">local</span> packed_value = utils.bpack(<span class="number">1</span>, value) +<span class="keyword">local</span> unpacked_value = utils.bunpack(<span class="number">1</span>, packed_value) +<span class="global">print</span>(value == unpacked_value) <span class="comment">-- true</span></pre> + </ul> + +</dd> + <dt> + <a name = "trim"></a> + <strong>trim (value)</strong> + </dt> + <dd> + <p>Trim whitespace from the start and from the end of string. </p> + +<p> Characters that are trimmed:</p> + +<ul> +<li>space <code>&quot; &quot;</code></li> +<li>tab <code>&quot;\t&quot;</code></li> +<li>carriage return <code>&quot;\r&quot;</code></li> +<li>line feed <code>&quot;\n&quot;</code></li> +<li>vertical tab <code>&quot;\v&quot;</code></li> +<li>form feed <code>&quot;\f&quot;</code></li> +</ul> + + + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + string to trim + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + a whitespace trimmed string + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> trimmed = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.trim(<span class="string">" hello world "</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "encode_json"></a> + <strong>encode_json (value)</strong> + </dt> + <dd> + JSON encode value. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><span class="type">any</span></span> + value to json encode + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + json encoded value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> json = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.encode_json({ hello = <span class="string">"world"</span> })</pre> + </ul> + +</dd> + <dt> + <a name = "decode_json"></a> + <strong>decode_json (value)</strong> + </dt> + <dd> + JSON decode value. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + string to json decode + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">any</span></span> + json decoded value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> tbl = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.decode_json(<span class="string">'{ "hello": "world" }'</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "encode_base64url"></a> + <strong>encode_base64url (value)</strong> + </dt> + <dd> + Base64 URL encode value. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + string to base64 url encode + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + base64 url encoded value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> encoded = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.encode_base64url(<span class="string">"test"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "decode_base64url"></a> + <strong>decode_base64url (value)</strong> + </dt> + <dd> + Base64 URL decode value + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + string to base64 url decode + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + base64 url decoded value + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> encoded = utils.encode_base64url(<span class="string">"test"</span>) +<span class="keyword">local</span> decoded = utils.decode_base64url(encoded)</pre> + </ul> + +</dd> + <dt> + <a name = "base64_size"></a> + <strong>base64_size (size)</strong> + </dt> + <dd> + Base64 size from original size (without padding). + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">size</span> + <span class="types"><span class="type">number</span></span> + original size + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">number</span></span> + base64 url encoded size without padding + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> test = <span class="string">"test"</span> +<span class="keyword">local</span> b64len = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.base64_size(#test)</pre> + </ul> + +</dd> + <dt> + <a name = "deflate"></a> + <strong>deflate (data)</strong> + </dt> + <dd> + Compress the data with deflate algorithm. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">data</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + data to deflate + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + deflated data + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> test = <span class="string">"test"</span> +<span class="keyword">local</span> deflated = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.deflate((<span class="string">"a"</span>):rep(<span class="number">100</span>))</pre> + </ul> + +</dd> + <dt> + <a name = "inflate"></a> + <strong>inflate (data)</strong> + </dt> + <dd> + Decompress the data compressed with deflate algorithm. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">data</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + data to inflate + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + inflated data + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> deflated = utils.deflate((<span class="string">"a"</span>):rep(<span class="number">100</span>)) +<span class="keyword">local</span> inflated = utils.inflate(deflated)</pre> + </ul> + +</dd> + <dt> + <a name = "rand_bytes"></a> + <strong>rand_bytes (length)</strong> + </dt> + <dd> + Generate crypto random bytes. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">length</span> + <span class="types"><span class="type">number</span></span> + how many bytes of random data to generate + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + random bytes</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> bytes = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.rand_bytes(<span class="number">32</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "sha256"></a> + <strong>sha256 (value)</strong> + </dt> + <dd> + Calculates SHA-256 hash of the value. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + value from which to calculate hash + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + sha-256 hash (32 bytes)</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> hash, err = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.sha256(<span class="string">"hello world"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "derive_hkdf_sha256"></a> + <strong>derive_hkdf_sha256 (ikm, nonce, usage, size)</strong> + </dt> + <dd> + Derive a new key using HKDF with SHA-256. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">ikm</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + initial key material + </li> + <li><span class="parameter">nonce</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + nonce + </li> + <li><span class="parameter">usage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + e.g. <code>&quot;encryption&quot;</code> or <code>&quot;authentication&quot;</code> + </li> + <li><span class="parameter">size</span> + <span class="types"><span class="type">number</span></span> + how many bytes to return + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + key material</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> ikm = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> nonce = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err = utils.derive_hkdf_sha256(ikm, nonce, <span class="string">"encryption"</span>, <span class="number">32</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "derive_pbkdf2_hmac_sha256"></a> + <strong>derive_pbkdf2_hmac_sha256 (pass, salt, usage, size, iterations)</strong> + </dt> + <dd> + Derive a new key using PBKDF2 with SHA-256. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">pass</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + password + </li> + <li><span class="parameter">salt</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + salt + </li> + <li><span class="parameter">usage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + e.g. <code>&quot;encryption&quot;</code> or <code>&quot;authentication&quot;</code> + </li> + <li><span class="parameter">size</span> + <span class="types"><span class="type">number</span></span> + how many bytes to return + </li> + <li><span class="parameter">iterations</span> + <span class="types"><span class="type">number</span></span> + how many iterations to run, e.g. <code>10000</code> + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + key material</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> pass = <span class="string">"my-super-secret-password"</span> +<span class="keyword">local</span> salt = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err = utils.derive_pbkdf2_hmac_sha256(pass, salt, <span class="string">"encryption"</span>, <span class="number">32</span>, <span class="number">10000</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "derive_aes_gcm_256_key_and_iv"></a> + <strong>derive_aes_gcm_256_key_and_iv (ikm, nonce[, safety])</strong> + </dt> + <dd> + Derive a new AES-256 GCM-mode key and initialization vector. </p> + +<p> Safety can be: + <em> <code>nil</code> or <code>&quot;None&quot;</code>: key and iv will be derived using HKDF with SHA-256 + </em> <code>Low</code>: key and iv will be derived using PBKDF2 with SHA-256 (1.000 iterations) + <em> <code>Medium</code>: key and iv will be derived using PBKDF2 with SHA-256 (10.000 iterations) + </em> <code>High</code>: key and iv will be derived using PBKDF2 with SHA-256 (100.000 iterations) + * <code>Very High</code>: key and iv will be derived using PBKDF2 with SHA-256 (1.000.000 iterations) + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">ikm</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + initial key material + </li> + <li><span class="parameter">nonce</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + nonce + </li> + <li><span class="parameter">safety</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + safety of key generation + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + key</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + initialization vector</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> ikm = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> nonce = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce, <span class="string">"Medium"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "derive_hmac_sha256_key"></a> + <strong>derive_hmac_sha256_key (ikm, nonce)</strong> + </dt> + <dd> + Derive HMAC SHA-256 key for message authentication using HDKF with SHA-256. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">ikm</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + initial key material + </li> + <li><span class="parameter">nonce</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + nonce + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + key</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> ikm = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> nonce = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err = utils.derive_hmac_sha256_key(ikm, nonce)</pre> + </ul> + +</dd> + <dt> + <a name = "encrypt_aes_256_gcm"></a> + <strong>encrypt_aes_256_gcm (key, iv, plaintext, aad)</strong> + </dt> + <dd> + Encrypt plain text using AES-256 in GCM-mode. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + encryption key + </li> + <li><span class="parameter">iv</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + initialization vector + </li> + <li><span class="parameter">plaintext</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + plain text + </li> + <li><span class="parameter">aad</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + additional authenticated data + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + ciphertext</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + authentication tag</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> ikm = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> nonce = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce) +<span class="keyword">local</span> enc, err, tag = utils.encrypt_aes_256_gcm(key, iv, <span class="string">"hello"</span>, <span class="string">"john@doe.com"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "decrypt_aes_256_gcm"></a> + <strong>decrypt_aes_256_gcm (key, iv, plaintext, aad, tag)</strong> + </dt> + <dd> + Decrypt ciphertext using AES-256 in GCM-mode. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + encryption key + </li> + <li><span class="parameter">iv</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + initialization vector + </li> + <li><span class="parameter">plaintext</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + plain text + </li> + <li><span class="parameter">aad</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + additional authenticated data + </li> + <li><span class="parameter">tag</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + authentication tag + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + ciphertext</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> ikm = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> nonce = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce) +<span class="keyword">local</span> enc, err, tag = utils.encrypt_aes_256_gcm(key, iv, <span class="string">"hello"</span>, <span class="string">"john@doe.com"</span>) +<span class="keyword">local</span> out, err = utils.decrypt_aes_256_gcm(key, iv, ciphertext, <span class="string">"john@doe.com"</span>, tag)</pre> + </ul> + +</dd> + <dt> + <a name = "hmac_sha256"></a> + <strong>hmac_sha256 (key, value)</strong> + </dt> + <dd> + Calculate message authentication code with HMAC with SHA-256. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + key + </li> + <li><span class="parameter">value</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + value + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + message authentication code (32 bytes)</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> ikm = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> nonce = utils.rand_bytes(<span class="number">32</span>) +<span class="keyword">local</span> key, err = utils.derive_hmac_sha256_key(ikm, nonce) +<span class="keyword">local</span> mac, err = utils.hmac_sha256(key, <span class="string">"hello"</span>)</pre> + </ul> + +</dd> + <dt> + <a name = "load_storage"></a> + <strong>load_storage (storage[, configuration])</strong> + </dt> + <dd> + Loads session storage and creates a new instance using session configuration. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">storage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + name of the storage to load + </li> + <li><span class="parameter">configuration</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + session configuration + (<em>optional</em>) + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">nil</span></span> + instance of session storage</li> + <li> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message</li> + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> postgres = <span class="global">require</span> <span class="string">"resty.session.utils"</span>.load_storage(<span class="string">"postgres"</span>, { + postgres = { + host = <span class="string">"127.0.0.1"</span>, + } +})</pre> + </ul> + +</dd> + <dt> + <a name = "errmsg"></a> + <strong>errmsg ([err], msg, ...)</strong> + </dt> + <dd> + Helper to format error messages. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">err</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + a possible error coming from underlying library + (<em>optional</em>) + </li> + <li><span class="parameter">msg</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a> or <span class="type">nil</span></span> + error message + </li> + <li><span class="parameter">...</span> + <span class="types"><span class="type">any</span></span> + arguments for formatting the msg + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + formatted error message + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> utils = <span class="global">require</span> <span class="string">"resty.session.utils"</span> +<span class="keyword">local</span> test = <span class="string">"aaaa"</span> +<span class="keyword">local</span> data, err = utils.deflate(test) +<span class="keyword">if</span> <span class="keyword">not</span> data <span class="keyword">then</span> + <span class="global">print</span>(utils.errmsg(err, <span class="string">"unable to deflate data '%s'"</span>, test) +<span class="keyword">end</span></pre> + </ul> + +</dd> + <dt> + <a name = "get_name"></a> + <strong>get_name (storage, name, key, subject)</strong> + </dt> + <dd> + Helper to create a delimited key. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">storage</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + a storage implementation + </li> + <li><span class="parameter">name</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + name + </li> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + key + </li> + <li><span class="parameter">subject</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + subject + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + formatted and delimited name + </ol> + + + + +</dd> + <dt> + <a name = "set_flag"></a> + <strong>set_flag (flags, flag)</strong> + </dt> + <dd> + Helper to turn on a flag. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">flags</span> + <span class="types"><span class="type">number</span></span> + flags on which the flag is applied + </li> + <li><span class="parameter">flag</span> + <span class="types"><span class="type">number</span></span> + flag that is applied + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">number</span></span> + flags with the flag applied + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> flags = <span class="number">0x0000</span> +<span class="keyword">local</span> FLAG_DOG = <span class="number">0x001</span> +flags = utils.set_flag(flags, FLAG_DOG)</pre> + </ul> + +</dd> + <dt> + <a name = "unset_flag"></a> + <strong>unset_flag (flags, flag)</strong> + </dt> + <dd> + Helper to turn off a flag. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">flags</span> + <span class="types"><span class="type">number</span></span> + flags on which the flag is removed + </li> + <li><span class="parameter">flag</span> + <span class="types"><span class="type">number</span></span> + flag that is removed + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">number</span></span> + flags with the flag removed + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> options = <span class="number">0x0000</span> +<span class="keyword">local</span> FLAG_DOG = <span class="number">0x001</span> +flags = utils.set_flag(options, FLAG_DOG) +flags = utils.unset_flag(options, FLAG_DOG)</pre> + </ul> + +</dd> + <dt> + <a name = "has_flag"></a> + <strong>has_flag (flags, flag)</strong> + </dt> + <dd> + Helper to check if flag is enabled. + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">flags</span> + <span class="types"><span class="type">number</span></span> + flags on which the flag is checked + </li> + <li><span class="parameter">flag</span> + <span class="types"><span class="type">number</span></span> + flag that is checked + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><span class="type">boolean</span></span> + true if flag has is present, otherwise false + </ol> + + + + <h3>Usage:</h3> + <ul> + <pre class="example"><span class="keyword">local</span> flags = <span class="number">0x0000</span> +<span class="keyword">local</span> FLAG_DOG = <span class="number">0x001</span> +<span class="keyword">local</span> FLAG_BONE = <span class="number">0x010</span> +flags = utils.set_flag(flags, FLAG_DOG) +flags = utils.set_flag(flags, FLAG_BONE) +<span class="global">print</span>(utils.has_flag(flags, FLAG_BONE)</pre> + </ul> + +</dd> + <dt> + <a name = "meta_get_value"></a> + <strong>meta_get_value (key, exp)</strong> + </dt> + <dd> + Helper to get the value used to store metadata for a certain aud and sub + Empty exp means the session id has been invalidated + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">key</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + storage key + </li> + <li><span class="parameter">exp</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + expiration + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + the value to store in the metadata collection + </ol> + + + + +</dd> + <dt> + <a name = "meta_get_next"></a> + <strong>meta_get_next (val, index)</strong> + </dt> + <dd> + Function to extract the next key and exp from a serialized + metadata list, starting from index + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">val</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + list of key:exp; + </li> + <li><span class="parameter">index</span> + <span class="types"><span class="type">number</span></span> + start index + </li> + </ul> + + <h3>Returns:</h3> + <ol> + <li> + <span class="types"><span class="type">key</span></span> + string session id</li> + <li> + <span class="types"><span class="type">err</span></span> + string error</li> + <li> + <span class="types"><span class="type">exp</span></span> + number expiration</li> + <li> + <span class="types"><span class="type">index</span></span> + number|nil index of the cursor</li> + </ol> + + + + +</dd> + <dt> + <a name = "meta_get_latest"></a> + <strong>meta_get_latest (sessions)</strong> + </dt> + <dd> + Function to filter out the latest valid key:exp from a + serialized list, used to store session metadata + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">sessions</span> + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> + list of key:exp; + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> + valid sessions and their exp + </ol> + + + + +</dd> +</dl> + + +</div> <!-- id="content" --> +</div> <!-- id="main" --> +<div id="about"> +<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> +<i style="float:right;">Last updated 2023-02-01 21:57:55 </i> +</div> <!-- id="about" --> +</div> <!-- id="container" --> +</body> +</html> diff --git a/lib/resty/session.lua b/lib/resty/session.lua index 12dfe538..0e95df06 100644 --- a/lib/resty/session.lua +++ b/lib/resty/session.lua @@ -1,771 +1,2836 @@ -local require = require - -local random = require "resty.random" - -local ngx = ngx -local var = ngx.var -local time = ngx.time -local header = ngx.header -local http_time = ngx.http_time -local set_header = ngx.req.set_header -local clear_header = ngx.req.clear_header -local concat = table.concat -local ceil = math.ceil -local max = math.max -local find = string.find -local gsub = string.gsub -local byte = string.byte -local sub = string.sub -local type = type -local pcall = pcall -local tonumber = tonumber +--- +-- Session library. +-- +-- Session library provides HTTP session management capabilities for OpenResty based +-- applications, libraries and proxies. +-- +-- @module resty.session + + +local require = require + + +local table_new = require "table.new" +local isempty = require "table.isempty" +local buffer = require "string.buffer" +local utils = require "resty.session.utils" + + +local clear_request_header = ngx.req.clear_header +local set_request_header = ngx.req.set_header local setmetatable = setmetatable -local getmetatable = getmetatable -local bytes = random.bytes - -local UNDERSCORE = byte("_") -local EXPIRE_FLAGS = "; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0" - -local COOKIE_PARTS = { - DEFAULT = { - n = 3, - "id", - "expires", -- may also contain: `expires:usebefore` - "hash" - }, - cookie = { - n = 4, - "id", - "expires", -- may also contain: `expires:usebefore` - "data", - "hash", - }, +local http_time = ngx.http_time +local tonumber = tonumber +local select = select +local assert = assert +local remove = table.remove +local header = ngx.header +local error = error +local floor = math.ceil +local time = ngx.time +local byte = string.byte +local type = type +local sub = string.sub +local fmt = string.format +local var = ngx.var +local log = ngx.log +local max = math.max +local min = math.min + + +local derive_aes_gcm_256_key_and_iv = utils.derive_aes_gcm_256_key_and_iv +local derive_hmac_sha256_key = utils.derive_hmac_sha256_key +local encrypt_aes_256_gcm = utils.encrypt_aes_256_gcm +local decrypt_aes_256_gcm = utils.decrypt_aes_256_gcm +local encode_base64url = utils.encode_base64url +local decode_base64url = utils.decode_base64url +local load_storage = utils.load_storage +local encode_json = utils.encode_json +local decode_json = utils.decode_json +local base64_size = utils.base64_size +local hmac_sha256 = utils.hmac_sha256 +local rand_bytes = utils.rand_bytes +local unset_flag = utils.unset_flag +local set_flag = utils.set_flag +local has_flag = utils.has_flag +local inflate = utils.inflate +local deflate = utils.deflate +local bunpack = utils.bunpack +local errmsg = utils.errmsg +local sha256 = utils.sha256 +local bpack = utils.bpack +local trim = utils.trim + + +local NOTICE = ngx.NOTICE +local WARN = ngx.WARN + + +local KEY_SIZE = 32 + + +--[ HEADER ----------------------------------------------------------------------------------------------------] || [ PAYLOAD --] +--[ Type || Flags || Session ID || Creation Time || Rolling Offset || Data Size || Tag || Idling Offset || Mac ] || [ Data ] +--[ 1B || 2B || 32B || 5B || 4B || 3B || 16B || 3B || 16B ] || [ *B ] + + +local COOKIE_TYPE_SIZE = 1 -- 1 +local FLAGS_SIZE = 2 -- 3 +local SID_SIZE = 32 -- 35 +local CREATION_TIME_SIZE = 5 -- 40 +local ROLLING_OFFSET_SIZE = 4 -- 44 +local DATA_SIZE = 3 -- 47 +local TAG_SIZE = 16 -- 63 +local IDLING_OFFSET_SIZE = 3 -- 66 +local MAC_SIZE = 16 -- 82 + + +local HEADER_TAG_SIZE = COOKIE_TYPE_SIZE + FLAGS_SIZE + SID_SIZE + CREATION_TIME_SIZE + ROLLING_OFFSET_SIZE + DATA_SIZE +local HEADER_TOUCH_SIZE = HEADER_TAG_SIZE + TAG_SIZE +local HEADER_MAC_SIZE = HEADER_TOUCH_SIZE + IDLING_OFFSET_SIZE +local HEADER_SIZE = HEADER_MAC_SIZE + MAC_SIZE +local HEADER_ENCODED_SIZE = base64_size(HEADER_SIZE) + + +local COOKIE_TYPE = bpack(COOKIE_TYPE_SIZE, 1) + + +local MAX_COOKIE_SIZE = 4096 +local MAX_COOKIES = 9 +local MAX_COOKIES_SIZE = MAX_COOKIES * MAX_COOKIE_SIZE -- 36864 bytes +local MAX_CREATION_TIME = 2 ^ (CREATION_TIME_SIZE * 8) - 1 -- ~34789 years +local MAX_ROLLING_OFFSET = 2 ^ (ROLLING_OFFSET_SIZE * 8) - 1 -- ~136 years +local MAX_IDLING_OFFSET = 2 ^ (IDLING_OFFSET_SIZE * 8) - 1 -- ~194 days +local MAX_DATA_SIZE = 2 ^ (DATA_SIZE * 8) - 1 -- 16777215 bytes +local MAX_TTL = 34560000 -- 400 days +-- see: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-11#section-4.1.2.1 + + +local FLAGS_NONE = 0x0000 +local FLAG_STORAGE = 0x0001 +local FLAG_FORGET = 0x0002 +local FLAG_DEFLATE = 0x0010 + + +local DEFAULT_AUDIENCE = "default" +local DEFAULT_SUBJECT +local DEFAULT_ENFORCE_SAME_SUBJECT = false +local DEFAULT_META = {} +local DEFAULT_IKM +local DEFAULT_IKM_FALLBACKS +local DEFAULT_HASH_STORAGE_KEY = false +local DEFAULT_HASH_SUBJECT = false +local DEFAULT_STORE_METADATA = false +local DEFAULT_TOUCH_THRESHOLD = 60 -- 1 minute +local DEFAULT_COMPRESSION_THRESHOLD = 1024 -- 1 kB +local DEFAULT_REQUEST_HEADERS +local DEFAULT_RESPONSE_HEADERS + + +local DEFAULT_COOKIE_NAME = "session" +local DEFAULT_COOKIE_PATH = "/" +local DEFAULT_COOKIE_SAME_SITE = "Lax" +local DEFAULT_COOKIE_SAME_PARTY +local DEFAULT_COOKIE_PRIORITY +local DEFAULT_COOKIE_PARTITIONED +local DEFAULT_COOKIE_HTTP_ONLY = true +local DEFAULT_COOKIE_PREFIX +local DEFAULT_COOKIE_DOMAIN +local DEFAULT_COOKIE_SECURE + + +local DEFAULT_REMEMBER_COOKIE_NAME = "remember" +local DEFAULT_REMEMBER_SAFETY = "Medium" +local DEFAULT_REMEMBER_META = false +local DEFAULT_REMEMBER = false + + +local DEFAULT_STALE_TTL = 10 -- 10 seconds +local DEFAULT_IDLING_TIMEOUT = 900 -- 15 minutes +local DEFAULT_ROLLING_TIMEOUT = 3600 -- 1 hour +local DEFAULT_ABSOLUTE_TIMEOUT = 86400 -- 1 day +local DEFAULT_REMEMBER_ROLLING_TIMEOUT = 604800 -- 1 week +local DEFAULT_REMEMBER_ABSOLUTE_TIMEOUT = 2592000 -- 30 days + + +local DEFAULT_STORAGE + + +local STATE_NEW = "new" +local STATE_OPEN = "open" +local STATE_CLOSED = "closed" + + +local AT_BYTE = byte("@") +local EQUALS_BYTE = byte("=") +local SEMICOLON_BYTE = byte(";") + + +local COOKIE_EXPIRE_FLAGS = "; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0" + + +local HEADER_BUFFER = buffer.new(HEADER_SIZE) +local FLAGS_BUFFER = buffer.new(128) +local DATA_BUFFER = buffer.new(MAX_COOKIES_SIZE) +local HIDE_BUFFER = buffer.new(256) + + +local DATA = table_new(2, 0) + + +local HEADERS = { + id = "Session-Id", + audience = "Session-Audience", + subject = "Session-Subject", + timeout = "Session-Timeout", + idling_timeout = "Session-Idling-Timeout", + ["idling-timeout"] = "Session-Idling-Timeout", + rolling_timeout = "Session-Rolling-Timeout", + ["rolling-timeout"] = "Session-Rolling-Timeout", + absolute_timeout = "Session-Absolute-Timeout", + ["absolute-timeout"] = "Session-Absolute-Timeout", } -local function enabled(value) - if value == nil then - return nil + +local function set_response_header(name, value) + header[name] = value +end + + +local function sha256_storage_key(sid) + local key, err = sha256(sid) + if not key then + return nil, errmsg(err, "unable to sha256 hash session id") + end + + if SID_SIZE ~= 32 then + key = sub(key, 1, SID_SIZE) + end + + key, err = encode_base64url(key) + if not key then + return nil, errmsg(err, "unable to base64url encode session id") + end + + return key +end + + +local function sha256_subject(subject) + local hashed_subject, err = sha256(subject) + if not hashed_subject then + return nil, errmsg(err, "unable to sha256 hash subject") + end + + hashed_subject, err = encode_base64url(sub(hashed_subject, 1, 16)) + if not hashed_subject then + return nil, errmsg(err, "unable to base64url encode subject") + end + + return hashed_subject +end + + +local function calculate_mac(ikm, nonce, msg) + local mac_key, err = derive_hmac_sha256_key(ikm, nonce) + if not mac_key then + return nil, errmsg(err, "unable to derive session message authentication key") + end + + local mac, err = hmac_sha256(mac_key, msg) + if not mac then + return nil, errmsg(err, "unable to calculate session message authentication code") + end + + if MAC_SIZE ~= 32 then + return sub(mac, 1, MAC_SIZE) + end + + return mac +end + + +local function calculate_cookie_chunks(cookie_name_size, data_size) + local space_needed = cookie_name_size + 1 + HEADER_ENCODED_SIZE + data_size + if space_needed > MAX_COOKIES_SIZE then + return nil, "cookie size limit exceeded" + end + + if space_needed <= MAX_COOKIE_SIZE then + return 1 + end + + for i = 2, MAX_COOKIES do + space_needed = space_needed + cookie_name_size + 2 + if space_needed > MAX_COOKIES_SIZE then + return nil, "cookie size limit exceeded" + elseif space_needed <= (MAX_COOKIE_SIZE * i) then + return i + end + end + + return nil, "cookie size limit exceeded" +end + + +local function merge_cookies(cookies, cookie_name_size, cookie_name, cookie_data) + if not cookies then + return cookie_data + end + + if type(cookies) == "string" then + if byte(cookies, cookie_name_size + 1) == EQUALS_BYTE and + sub(cookies, 1, cookie_name_size) == cookie_name + then + return cookie_data end - return value == true - or value == "1" - or value == "true" - or value == "on" + return { cookies, cookie_data } + end + + if type(cookies) ~= "table" then + return nil, "unable to merge session cookies with response cookies" + end + + local count = #cookies + for i = 1, count do + if byte(cookies[i], cookie_name_size + 1) == EQUALS_BYTE and + sub(cookies[i], 1, cookie_name_size) == cookie_name + then + cookies[i] = cookie_data + return cookies + end + + if i == count then + cookies[i+1] = cookie_data + return cookies + end + end end -local function ifnil(value, default) - if value == nil then - return default + +local function get_store_metadata(self) + if not self.store_metadata then + return + end + + local data = self.data + local count = #data + if count == 1 then + local audience = data[1][2] + local subject = data[1][3] + if audience and subject then + audience = encode_base64url(audience) + subject = self.hash_subject(subject) + return { + audiences = { audience }, + subjects = { subject }, + } + end + + return + end + + local audiences + local subjects + local index = 0 + for i = 1, count do + local audience = data[i][2] + local subject = data[i][3] + if audience and subject then + audience = encode_base64url(audience) + self.hash_subject(subject) + if not audiences then + audiences = table_new(count, 0) + subjects = table_new(count, 0) + end + + index = index + 1 + audiences[index] = audience + subjects[index] = subject end + end + + if not audiences then + return + end - return enabled(value) + return { + audiences = audiences, + subjects = subjects, + } end -local function prequire(prefix, package, default) - if type(package) == "table" then - return package, package.name + +local function get_property(self, name) + if name == "id" then + local sid = self.meta.sid + if not sid then + return end - local ok, module = pcall(require, prefix .. package) - if not ok then - return require(prefix .. default), default + return encode_base64url(sid) + + elseif name == "nonce" then + return self.meta.sid + + elseif name == "audience" then + return self.data[self.data_index][2] + + elseif name == "subject" then + return self.data[self.data_index][3] + + elseif name == "timeout" then + local timeout + local meta = self.meta + + if self.idling_timeout > 0 then + timeout = self.idling_timeout - (meta.timestamp - meta.creation_time - meta.rolling_offset - meta.idling_offset) + end + + if self.rolling_timeout > 0 then + local t = self.rolling_timeout - (meta.timestamp - meta.creation_time - meta.rolling_offset) + timeout = timeout and min(t, timeout) or t + end + + if self.absolute_timeout > 0 then + local t = self.absolute_timeout - (meta.timestamp - meta.creation_time) + timeout = timeout and min(t, timeout) or t + end + + return timeout + + elseif name == "idling-timeout" or name == "idling_timeout" then + local idling_timeout = self.idling_timeout + if idling_timeout == 0 then + return + end + + local meta = self.meta + return idling_timeout - (meta.timestamp - meta.creation_time - meta.rolling_offset - meta.idling_offset) + + elseif name == "rolling-timeout" or name == "rolling_timeout" then + local rolling_timeout = self.rolling_timeout + if rolling_timeout == 0 then + return end - return module, package + local meta = self.meta + return rolling_timeout - (meta.timestamp - meta.creation_time - meta.rolling_offset) + + elseif name == "absolute-timeout" or name == "absolute_timeout" then + local absolute_timeout = self.absolute_timeout + if absolute_timeout == 0 then + return + end + + local meta = self.meta + return absolute_timeout - (meta.timestamp - meta.creation_time) + + else + return self.meta[name] + end end -local function is_session_cookie(cookie, name, name_len) - if not cookie or cookie == "" then - return false, nil + +local function open(self, remember, meta_only) + local storage = self.storage + local current_time = time() + local cookie_name + if remember then + cookie_name = self.remember_cookie_name + else + cookie_name = self.cookie_name + end + + local cookie = var["cookie_" .. cookie_name] + if not cookie then + return nil, "missing session cookie" + end + + local header_decoded do + header_decoded = sub(cookie, 1, HEADER_ENCODED_SIZE) + if #header_decoded ~= HEADER_ENCODED_SIZE then + return nil, "invalid session header" + end + local err + header_decoded, err = decode_base64url(header_decoded) + if not header_decoded then + return nil, errmsg(err, "unable to base64url decode session header") + end + end + + HEADER_BUFFER:set(header_decoded) + + local cookie_type do + cookie_type = HEADER_BUFFER:get(COOKIE_TYPE_SIZE) + if #cookie_type ~= COOKIE_TYPE_SIZE then + return nil, "invalid session cookie type" + end + if cookie_type ~= COOKIE_TYPE then + return nil, "invalid session cookie type" end + end - cookie = gsub(cookie, "^%s+", "") - if cookie == "" then - return false, nil + local flags do + flags = HEADER_BUFFER:get(FLAGS_SIZE) + if #flags ~= FLAGS_SIZE then + return nil, "invalid session flags" end - cookie = gsub(cookie, "%s+$", "") - if cookie == "" then - return false, nil + flags = bunpack(FLAGS_SIZE, flags) + + if storage then + if not has_flag(flags, FLAG_STORAGE) then + return nil, "invalid session flags" + end + elseif has_flag(flags, FLAG_STORAGE) then + return nil, "invalid session flags" end + end - local eq_pos = find(cookie, "=", 1, true) - if not eq_pos then - return false, cookie + local sid do + sid = HEADER_BUFFER:get(SID_SIZE) + if #sid ~= SID_SIZE then + return nil, "invalid session id" end + end - local cookie_name = sub(cookie, 1, eq_pos - 1) - if cookie_name == "" then - return false, cookie + local creation_time do + creation_time = HEADER_BUFFER:get(CREATION_TIME_SIZE) + if #creation_time ~= CREATION_TIME_SIZE then + return nil, "invalid session creation time" end - cookie_name = gsub(cookie_name, "%s+$", "") - if cookie_name == "" then - return false, cookie + creation_time = bunpack(CREATION_TIME_SIZE, creation_time) + if not creation_time or creation_time < 0 or creation_time > MAX_CREATION_TIME then + return nil, "invalid session creation time" end - if cookie_name ~= name then - if find(cookie_name, name, 1, true) ~= 1 then - return false, cookie - end + local absolute_elapsed = current_time - creation_time + if absolute_elapsed > MAX_ROLLING_OFFSET then + return nil, "session lifetime exceeded" + end - if byte(cookie_name, name_len + 1) ~= UNDERSCORE then - return false, cookie + if remember then + local remember_absolute_timeout = self.remember_absolute_timeout + if remember_absolute_timeout ~= 0 then + if absolute_elapsed > remember_absolute_timeout then + return nil, "session remember absolute timeout exceeded" end + end - if not tonumber(sub(cookie_name, name_len + 2), 10) then - return false, cookie + else + local absolute_timeout = self.absolute_timeout + if absolute_timeout ~= 0 then + if absolute_elapsed > absolute_timeout then + return nil, "session absolute timeout exceeded" end + end end + end - return true, cookie -end - -local function set_cookie(session, value, expires) - if ngx.headers_sent then - return nil, "attempt to set session cookie after sending out response headers" + local rolling_offset do + rolling_offset = HEADER_BUFFER:get(ROLLING_OFFSET_SIZE) + if #rolling_offset ~= ROLLING_OFFSET_SIZE then + return nil, "invalid session rolling offset" end - value = value or "" + rolling_offset = bunpack(ROLLING_OFFSET_SIZE, rolling_offset) + if not rolling_offset or rolling_offset < 0 or rolling_offset > MAX_ROLLING_OFFSET then + return nil, "invalid session rolling offset" + end - local cookie = session.cookie - local output = {} + local rolling_elapsed = current_time - creation_time - rolling_offset - local i = 3 + if remember then + local remember_rolling_timeout = self.remember_rolling_timeout + if remember_rolling_timeout ~= 0 then + if rolling_elapsed > remember_rolling_timeout then + return nil, "session remember rolling timeout exceeded" + end + end - -- build cookie parameters, elements 1+2 will be set later - if expires then - -- we're expiring/deleting the data, so set an expiry in the past - output[i] = EXPIRE_FLAGS - elseif cookie.persistent then - -- persistent cookies have an expiry - output[i] = "; Expires=" .. http_time(session.expires) .. "; Max-Age=" .. cookie.lifetime else - -- just to reserve index 3 for expiry as cookie might get smaller, - -- and some cookies need to be expired. - output[i] = "" + local rolling_timeout = self.rolling_timeout + if rolling_timeout ~= 0 then + if rolling_elapsed > rolling_timeout then + return nil, "session rolling timeout exceeded" + end + end end + end - if cookie.domain and cookie.domain ~= "localhost" and cookie.domain ~= "" then - i = i + 1 - output[i] = "; Domain=" .. cookie.domain + local data_size do + data_size = HEADER_BUFFER:get(DATA_SIZE) + if #data_size ~= DATA_SIZE then + return nil, "invalid session data size" end - i = i + 1 - output[i] = "; Path=" .. (cookie.path or "/") + data_size = bunpack(DATA_SIZE, data_size) + if not data_size or data_size < 0 or data_size > MAX_DATA_SIZE then + return nil, "invalid session data size" + end + end - if cookie.samesite == "Lax" - or cookie.samesite == "Strict" - or cookie.samesite == "None" - then - i = i + 1 - output[i] = "; SameSite=" .. cookie.samesite + local tag do + tag = HEADER_BUFFER:get(TAG_SIZE) + if #tag ~= TAG_SIZE then + return nil, "invalid session tag" end + end - if cookie.secure then - i = i + 1 - output[i] = "; Secure" + local idling_offset do + idling_offset = HEADER_BUFFER:get(IDLING_OFFSET_SIZE) + if #idling_offset ~= IDLING_OFFSET_SIZE then + return nil, "invalid session idling offset" end - if cookie.httponly then - i = i + 1 - output[i] = "; HttpOnly" + idling_offset = bunpack(IDLING_OFFSET_SIZE, idling_offset) + if not idling_offset or idling_offset < 0 or idling_offset > MAX_IDLING_OFFSET then + return nil, "invalid session idling offset" end - -- How many chunks do we need? - local cookie_parts - local cookie_chunks - if expires then - -- expiring cookie, so deleting data. Do not measure data, but use - -- existing chunk count to make sure we clear all of them - cookie_parts = cookie.chunks or 1 + if remember then + if idling_offset ~= 0 then + return nil, "invalid session idling offset" + end + else - -- calculate required chunks from data - cookie_chunks = max(ceil(#value / cookie.maxsize), 1) - cookie_parts = max(cookie_chunks, cookie.chunks or 1) - end - - local cookie_header = header["Set-Cookie"] - for j = 1, cookie_parts do - -- create numbered chunk names if required - local chunk_name = { session.name } - if j > 1 then - chunk_name[2] = "_" - chunk_name[3] = j - chunk_name[4] = "=" - else - chunk_name[2] = "=" + local idling_timeout = self.idling_timeout + if idling_timeout ~= 0 then + local idling_elapsed = current_time - creation_time - rolling_offset - idling_offset + if idling_elapsed > idling_timeout then + return nil, "session idling timeout exceeded" end - chunk_name = concat(chunk_name) - output[1] = chunk_name + end + end + end - if expires then - -- expiring cookie, so deleting data; clear it - output[2] = "" - elseif j > cookie_chunks then - -- less chunks than before, clearing excess cookies - output[2] = "" - output[3] = EXPIRE_FLAGS + local ikm do + ikm = self.ikm + local mac = HEADER_BUFFER:get(MAC_SIZE) + if #mac ~= MAC_SIZE then + return nil, "invalid session message authentication code" + end - else - -- grab the piece for the current chunk - local sp = j * cookie.maxsize - (cookie.maxsize - 1) - if j < cookie_chunks then - output[2] = sub(value, sp, sp + (cookie.maxsize - 1)) .. "0" - else - output[2] = sub(value, sp) + local msg = sub(header_decoded, 1, HEADER_MAC_SIZE) + local expected_mac, err = calculate_mac(ikm, sid, msg) + if mac ~= expected_mac then + local fallback_keys = self.ikm_fallbacks + if fallback_keys then + local count = #fallback_keys + if count > 0 then + for i = 1, count do + ikm = fallback_keys[i] + expected_mac, err = calculate_mac(ikm, sid, msg) + if mac == expected_mac then + break end - end - -- build header value and add it to the header table/string - -- replace existing chunk-name, or append - local cookie_content = concat(output) - local header_type = type(cookie_header) - if header_type == "table" then - local found = false - local cookie_count = #cookie_header - for cookie_index = 1, cookie_count do - if find(cookie_header[cookie_index], chunk_name, 1, true) == 1 then - cookie_header[cookie_index] = cookie_content - found = true - break - end + if i == count then + return nil, errmsg(err, "invalid session message authentication code") end - if not found then - cookie_header[cookie_count + 1] = cookie_content - end - elseif header_type == "string" and find(cookie_header, chunk_name, 1, true) ~= 1 then - cookie_header = { cookie_header, cookie_content } + end + else - cookie_header = cookie_content + return nil, errmsg(err, "invalid session message authentication code") end + + else + return nil, errmsg(err, "invalid session message authentication code") + end end + end + + local data_index = self.data_index + local audience = self.data[data_index][2] + local initial_chunk, ciphertext, ciphertext_encoded, info_data do + if storage then + local key, err = self.hash_storage_key(sid) + if not key then + return nil, err + end + + local data, err = storage:get(cookie_name, key, current_time) + if not data then + return nil, errmsg(err, "unable to load session") + end + + data, err = decode_json(data) + if not data then + return nil, errmsg(err, "unable to json decode session") + end + + ciphertext = data[1] + ciphertext_encoded = ciphertext + info_data = data[2] + if info_data then + info_data, err = decode_base64url(info_data) + if not info_data then + return nil, errmsg(err, "unable to base64url decode session info") + end - header["Set-Cookie"] = cookie_header + info_data, err = decode_json(info_data) + if not info_data then + return nil, errmsg(err, "unable to json decode session info") + end - return true -end + if not info_data[audience] then + info_data[audience] = self.info.data and self.info.data[audience] or nil + end + end -local function get_cookie(session, i) - local cookie_name = { "cookie_", session.name } - if i then - cookie_name[3] = "_" - cookie_name[4] = i else - i = 1 + local cookie_chunks, err = calculate_cookie_chunks(#cookie_name, data_size) + if not cookie_chunks then + return nil, err + end + + if cookie_chunks == 1 then + initial_chunk = sub(cookie, -data_size) + ciphertext = initial_chunk + + else + initial_chunk = sub(cookie, HEADER_ENCODED_SIZE + 1) + DATA_BUFFER:reset():put(initial_chunk) + for i = 2, cookie_chunks do + local chunk = var["cookie_" .. cookie_name .. i] + if not chunk then + return nil, errmsg(err, "missing session cookie chunk") + end + + DATA_BUFFER:put(chunk) + end + + ciphertext = DATA_BUFFER:get() + end + end + + if #ciphertext ~= data_size then + return nil, "invalid session payload" end - local cookie = var[concat(cookie_name)] - if not cookie then - return nil + local err + ciphertext, err = decode_base64url(ciphertext) + if not ciphertext then + return nil, errmsg(err, "unable to base64url decode session data") end + end + + if remember then + self.remember_meta = { + timestamp = current_time, + flags = flags, + sid = sid, + creation_time = creation_time, + rolling_offset = rolling_offset, + data_size = data_size, + idling_offset = idling_offset, + ikm = ikm, + header = header_decoded, + initial_chunk = initial_chunk, + ciphertext = ciphertext_encoded, + } - session.cookie.chunks = i + else + self.meta = { + timestamp = current_time, + flags = flags, + sid = sid, + creation_time = creation_time, + rolling_offset = rolling_offset, + data_size = data_size, + idling_offset = idling_offset, + ikm = ikm, + header = header_decoded, + initial_chunk = initial_chunk, + ciphertext = ciphertext_encoded, + } + end - local cookie_size = #cookie - if cookie_size <= session.cookie.maxsize then - return cookie + if meta_only then + return true + end + + local aes_key, err, iv + if remember then + aes_key, err, iv = derive_aes_gcm_256_key_and_iv(ikm, sid, self.remember_safety) + else + aes_key, err, iv = derive_aes_gcm_256_key_and_iv(ikm, sid) + end + + if not aes_key then + return nil, errmsg(err, "unable to derive session decryption key") + end + + local aad = sub(header_decoded, 1, HEADER_TAG_SIZE) + local plaintext, err = decrypt_aes_256_gcm(aes_key, iv, ciphertext, aad, tag) + if not plaintext then + return nil, errmsg(err, "unable to decrypt session data") + end + + local data do + if has_flag(flags, FLAG_DEFLATE) then + plaintext, err = inflate(plaintext) + if not plaintext then + return nil, errmsg(err, "unable to inflate session data") + end + end + + data, err = decode_json(plaintext) + if not data then + return nil, errmsg(err, "unable to json decode session data") + end + end + + if storage then + self.info.data = info_data + end + + local audience_index + local count = #data + for i = 1, count do + if data[i][2] == audience then + audience_index = i + break end + end - return concat{ sub(cookie, 1, session.cookie.maxsize), get_cookie(session, i + 1) or "" } + if not audience_index then + data[count + 1] = self.data[data_index] + self.state = STATE_NEW + self.data = data + self.data_index = count + 1 + return nil, "missing session audience", true + end + + self.state = STATE_OPEN + self.data = data + self.data_index = audience_index + + return true end -local function set_usebefore(session) - local usebefore = session.usebefore - local idletime = session.cookie.idletime - if idletime == 0 then -- usebefore is disabled - if usebefore then - session.usebefore = nil - return true +local function save(self, state, remember) + local cookie_name + local meta + if remember then + cookie_name = self.remember_cookie_name + meta = self.remember_meta or {} + else + cookie_name = self.cookie_name + meta = self.meta + end + + local cookie_name_size = #cookie_name + local storage = self.storage + local flags = self.flags + + if storage then + flags = set_flag(flags, FLAG_STORAGE) + else + flags = unset_flag(flags, FLAG_STORAGE) + end + + local sid, err = rand_bytes(SID_SIZE) + if not sid then + return nil, errmsg(err, "unable to generate session id") + end + + local current_time = time() + local rolling_offset + + local creation_time = meta.creation_time + if creation_time then + rolling_offset = current_time - creation_time + if rolling_offset > MAX_ROLLING_OFFSET then + return nil, "session maximum rolling offset exceeded" + end + + else + creation_time = current_time + rolling_offset = 0 + end + + if creation_time > MAX_CREATION_TIME then + -- this should only happen at around year 36759 (most likely a clock problem) + return nil, "session maximum creation time exceeded" + end + + do + local meta_flags = meta.flags + if meta_flags and has_flag(meta_flags, FLAG_FORGET) then + flags = set_flag(flags, FLAG_FORGET) + end + end + + local data, data_size, cookie_chunks do + data = self.data + if self.enforce_same_subject then + local count = #data + if count > 1 then + local subject = data[self.data_index][3] + for i = count, 1, -1 do + if data[i][3] ~= subject then + remove(data, i) + end + end + end + end + + data, err = encode_json(data) + if not data then + return nil, errmsg(err, "unable to json encode session data") + end + + data_size = #data + + local compression_threshold = self.compression_threshold + if compression_threshold ~= 0 and data_size > compression_threshold then + local deflated_data, err = deflate(data) + if not deflated_data then + log(NOTICE, "[session] unable to deflate session data (", err , ")") + + else + if deflated_data then + local deflated_size = #deflated_data + if deflated_size < data_size then + flags = set_flag(flags, FLAG_DEFLATE) + data = deflated_data + data_size = deflated_size + end end + end + end + + data_size = base64_size(data_size) - return false + if storage then + if data_size > MAX_DATA_SIZE then + return nil, "session maximum data size exceeded" + end + + cookie_chunks = 1 + else + cookie_chunks, err = calculate_cookie_chunks(cookie_name_size, data_size) + if not cookie_chunks then + return nil, err + end + end + end + + local idling_offset = 0 + + local packed_flags = bpack(FLAGS_SIZE, flags) + local packed_data_size = bpack(DATA_SIZE, data_size) + local packed_creation_time = bpack(CREATION_TIME_SIZE, creation_time) + local packed_rolling_offset = bpack(ROLLING_OFFSET_SIZE, rolling_offset) + local packed_idling_offset = bpack(IDLING_OFFSET_SIZE, idling_offset) + + HEADER_BUFFER:reset() + HEADER_BUFFER:put(COOKIE_TYPE, packed_flags, sid, packed_creation_time, packed_rolling_offset, packed_data_size) + + local ikm = self.ikm + local aes_key, iv + if remember then + aes_key, err, iv = derive_aes_gcm_256_key_and_iv(ikm, sid, self.remember_safety) + else + aes_key, err, iv = derive_aes_gcm_256_key_and_iv(ikm, sid) + end + + if not aes_key then + return nil, errmsg(err, "unable to derive session encryption key") + end + + local ciphertext, err, tag = encrypt_aes_256_gcm(aes_key, iv, data, HEADER_BUFFER:tostring()) + if not ciphertext then + return nil, errmsg(err, "unable to encrypt session data") + end + + HEADER_BUFFER:put(tag, packed_idling_offset) + + local mac, err = calculate_mac(ikm, sid, HEADER_BUFFER:tostring()) + if not mac then + return nil, err + end + + local header_decoded = HEADER_BUFFER:put(mac):get() + local header_encoded, err = encode_base64url(header_decoded) + if not header_encoded then + return nil, errmsg(err, "unable to base64url encode session header") + end + + local payload, err = encode_base64url(ciphertext) + if not payload then + return nil, errmsg(err, "unable to base64url encode session data") + end + + local cookies = header["Set-Cookie"] + local cookie_flags = self.cookie_flags + + local initial_chunk + local ciphertext_encoded + + local remember_flags + if remember then + local max_age = self.remember_rolling_timeout + if max_age == 0 or max_age > MAX_TTL then + max_age = MAX_TTL end - usebefore = usebefore or 0 + local expires = http_time(creation_time + max_age) + remember_flags = fmt("; Expires=%s; Max-Age=%d", expires, max_age) + end - local new_usebefore = session.now + idletime - if new_usebefore - usebefore > 60 then - session.usebefore = new_usebefore - return true + if cookie_chunks == 1 then + local cookie_data + if storage then + ciphertext_encoded = payload + if remember then + cookie_data = fmt("%s=%s%s%s", cookie_name, header_encoded, cookie_flags, remember_flags) + else + cookie_data = fmt("%s=%s%s", cookie_name, header_encoded, cookie_flags) + end + + else + initial_chunk = payload + if remember then + cookie_data = fmt("%s=%s%s%s%s", cookie_name, header_encoded, payload, cookie_flags, remember_flags) + else + cookie_data = fmt("%s=%s%s%s", cookie_name, header_encoded, payload, cookie_flags) + end end - return false -end + cookies, err = merge_cookies(cookies, cookie_name_size, cookie_name, cookie_data) + if not cookies then + return nil, err + end -local function save(session, close) - session.expires = session.now + session.cookie.lifetime + else + DATA_BUFFER:set(payload) - set_usebefore(session) + initial_chunk = DATA_BUFFER:get(MAX_COOKIE_SIZE - HEADER_ENCODED_SIZE - cookie_name_size - 1) - local cookie, err = session.strategy.save(session, close) - if not cookie then - return nil, err or "unable to save session cookie" + local cookie_data + if remember then + cookie_data = fmt("%s=%s%s%s%s", cookie_name, header_encoded, initial_chunk, cookie_flags, remember_flags) + else + cookie_data = fmt("%s=%s%s%s", cookie_name, header_encoded, initial_chunk, cookie_flags) end - return set_cookie(session, cookie) -end + cookies, err = merge_cookies(cookies, cookie_name_size, cookie_name, cookie_data) + if not cookies then + return nil, err + end -local function touch(session, close) - if set_usebefore(session) then - -- usebefore was updated, so set cookie - local cookie, err = session.strategy.touch(session, close) - if not cookie then - return nil, err or "unable to touch session cookie" - end + for i = 2, cookie_chunks do + local name = fmt("%s%d", cookie_name, i) + cookie_data = DATA_BUFFER:get(MAX_COOKIE_SIZE - cookie_name_size - 2) + if remember then + cookie_data = fmt("%s=%s%s%s", name, cookie_data, cookie_flags, remember_flags) + else + cookie_data = fmt("%s=%s%s", name, cookie_data, cookie_flags) + end + cookies, err = merge_cookies(cookies, cookie_name_size + 1, name, cookie_data) + if not cookies then + return nil, err + end + end + end - return set_cookie(session, cookie) + if storage then + local key, err = self.hash_storage_key(sid) + if not key then + return nil, err end - if close then - local ok, err = session.strategy.close(session) - if not ok then + DATA[1] = payload + + local info_data = self.info.data + if info_data then + info_data, err = encode_json(info_data) + if not info_data then + return nil, errmsg(err, "unable to json encode session info") + end + + info_data, err = encode_base64url(info_data) + if not info_data then + return nil, errmsg(err, "unable to base64url encode session info") + end + + DATA[2] = info_data + + else + DATA[2] = nil + end + + data, err = encode_json(DATA) + if not data then + return nil, errmsg(err, "unable to json encode session data") + end + + local old_sid = meta.sid + local old_key + if old_sid then + old_key, err = self.hash_storage_key(old_sid) + if not old_key then + log(WARN, "[session] ", err) + end + end + + local ttl = remember and self.remember_rolling_timeout or self.rolling_timeout + if ttl == 0 or ttl > MAX_TTL then + ttl = MAX_TTL + end + + local store_metadata = get_store_metadata(self) + + local ok, err = storage:set(cookie_name, key, data, ttl, current_time, old_key, self.stale_ttl, store_metadata, remember) + if not ok then + return nil, errmsg(err, "unable to store session data") + end + + else + local old_data_size = meta.data_size + if old_data_size then + local old_cookie_chunks = calculate_cookie_chunks(cookie_name_size, old_data_size) + if old_cookie_chunks and old_cookie_chunks > cookie_chunks then + for i = cookie_chunks + 1, old_cookie_chunks do + local name = fmt("%s%d", cookie_name, i) + local cookie_data = fmt("%s=%s%s", name, cookie_flags, COOKIE_EXPIRE_FLAGS) + cookies, err = merge_cookies(cookies, cookie_name_size + 1, name, cookie_data) + if not cookies then return nil, err + end end + end end + end + + header["Set-Cookie"] = cookies + + if remember then + self.remember_meta = { + timestamp = current_time, + flags = flags, + sid = sid, + creation_time = creation_time, + rolling_offset = rolling_offset, + data_size = data_size, + idling_offset = idling_offset, + ikm = ikm, + header = header_decoded, + initial_chunk = initial_chunk, + ciphertext = ciphertext_encoded, + } - return true + else + self.state = state or STATE_OPEN + self.meta = { + timestamp = current_time, + flags = flags, + sid = sid, + creation_time = creation_time, + rolling_offset = rolling_offset, + data_size = data_size, + idling_offset = idling_offset, + ikm = ikm, + header = header_decoded, + initial_chunk = initial_chunk, + ciphertext = ciphertext_encoded, + } + end + + return true end -local function regenerate(session, flush) - if session.strategy.destroy then - session.strategy.destroy(session) - elseif session.strategy.close then - session.strategy.close(session) - end - - if flush then - session.data = {} - end - - session.id = session:identifier() -end - -local secret = bytes(32, true) or bytes(32) -local defaults - -local function init() - defaults = { - name = var.session_name or "session", - identifier = var.session_identifier or "random", - strategy = var.session_strategy or "default", - storage = var.session_storage or "cookie", - serializer = var.session_serializer or "json", - compressor = var.session_compressor or "none", - encoder = var.session_encoder or "base64", - cipher = var.session_cipher or "aes", - hmac = var.session_hmac or "sha1", - cookie = { - path = var.session_cookie_path or "/", - domain = var.session_cookie_domain, - samesite = var.session_cookie_samesite or "Lax", - secure = enabled(var.session_cookie_secure), - httponly = enabled(var.session_cookie_httponly or true), - persistent = enabled(var.session_cookie_persistent or false), - discard = tonumber(var.session_cookie_discard, 10) or 10, - renew = tonumber(var.session_cookie_renew, 10) or 600, - lifetime = tonumber(var.session_cookie_lifetime, 10) or 3600, - idletime = tonumber(var.session_cookie_idletime, 10) or 0, - maxsize = tonumber(var.session_cookie_maxsize, 10) or 4000, - - }, check = { - ssi = enabled(var.session_check_ssi or false), - ua = enabled(var.session_check_ua or true), - scheme = enabled(var.session_check_scheme or true), - addr = enabled(var.session_check_addr or false) - } - } - defaults.secret = var.session_secret or secret + +local function save_info(self, data, remember) + local cookie_name + local meta + if remember then + cookie_name = self.remember_cookie_name + meta = self.remember_meta or {} + else + cookie_name = self.cookie_name + meta = self.meta + end + + local key, err = self.hash_storage_key(meta.sid) + if not key then + return nil, err + end + + DATA[1] = meta.ciphertext + DATA[2] = data + + data, err = encode_json(DATA) + if not data then + return nil, errmsg(err, "unable to json encode session data") + end + + local current_time = time() + + local ttl = self.rolling_timeout + if ttl == 0 or ttl > MAX_TTL then + ttl = MAX_TTL + end + ttl = max(ttl - (current_time - meta.creation_time - meta.rolling_offset), 1) + local ok, err = self.storage:set(cookie_name, key, data, ttl, current_time) + if not ok then + return nil, errmsg(err, "unable to store session info") + end end -local session = { - _VERSION = "3.10" -} -session.__index = session +local function destroy(self, remember) + local cookie_name + local meta + if remember then + cookie_name = self.remember_cookie_name + meta = self.remember_meta or {} + else + cookie_name = self.cookie_name + meta = self.meta + end -function session:get_cookie() - return get_cookie(self) -end + local cookie_name_size = #cookie_name + local storage = self.storage -function session:parse_cookie(value) - local cookie - local cookie_parts = COOKIE_PARTS[self.cookie.storage] or COOKIE_PARTS.DEFAULT + local cookie_chunks = 1 + local data_size = meta.data_size + if not storage and data_size then + local err + cookie_chunks, err = calculate_cookie_chunks(cookie_name_size, data_size) + if not cookie_chunks then + return nil, err + end + end + + local cookie_flags = self.cookie_flags + local cookie_data = fmt("%s=%s%s", cookie_name, cookie_flags, COOKIE_EXPIRE_FLAGS) + local cookies, err = merge_cookies(header["Set-Cookie"], cookie_name_size, cookie_name, cookie_data) + if not cookies then + return nil, err + end + + if cookie_chunks > 1 then + for i = 2, cookie_chunks do + local name = fmt("%s%d", cookie_name, i) + cookie_data = fmt("%s=%s%s", name, cookie_flags, COOKIE_EXPIRE_FLAGS) + cookies, err = merge_cookies(cookies, cookie_name_size + 1, name, cookie_data) + if not cookies then + return nil, err + end + end + end - local count = 1 - local pos = 1 + if storage then + local sid = meta.sid + if sid then + local key, err = self.hash_storage_key(sid) + if not key then + return nil, err + end + + local ok, err = storage:delete(cookie_name, key, meta.timestamp, get_store_metadata(self)) + if not ok then + return nil, errmsg(err, "unable to destroy session") + end + end + end - local p_pos = find(value, "|", 1, true) - while p_pos do - if count > (cookie_parts.n - 1) then - return nil, "too many session cookie parts" + header["Set-Cookie"] = cookies + + self.state = STATE_CLOSED + + return true +end + + +local function clear_request_cookie(self, remember) + local cookies = var.http_cookie + if not cookies or cookies == "" then + return + end + + local cookie_name + if remember then + cookie_name = self.remember_cookie_name + else + cookie_name = self.cookie_name + end + + local cookie_name_size = #cookie_name + + local cookie_chunks + if self.storage then + cookie_chunks = 1 + else + local data_size = remember and self.remember_meta.data_size or self.meta.data_size + cookie_chunks = calculate_cookie_chunks(cookie_name_size, data_size) or 1 + end + + HIDE_BUFFER:reset() + + local size = #cookies + local name + local skip = false + local start = 1 + for i = 1, size do + local b = byte(cookies, i) + if name then + if b == SEMICOLON_BYTE or i == size then + if not skip then + local value + if b == SEMICOLON_BYTE then + value = trim(sub(cookies, start, i - 1)) + else + value = trim(sub(cookies, start)) + end + + if value ~= "" then + HIDE_BUFFER:put(value) + end + + if i ~= size then + HIDE_BUFFER:put("; ") + end end - if not cookie then - cookie = {} + + if i == size then + break end - if count == 2 then - local cookie_part = sub(value, pos, p_pos - 1) - local c_pos = find(cookie_part, ":", 2, true) - if c_pos then - cookie.expires = tonumber(sub(cookie_part, 1, c_pos - 1), 10) - if not cookie.expires then - return nil, "invalid session cookie expiry" - end - - cookie.usebefore = tonumber(sub(cookie_part, c_pos + 1), 10) - if not cookie.usebefore then - return nil, "invalid session cookie usebefore" - end - else - cookie.expires = tonumber(cookie_part, 10) - if not cookie.expires then - return nil, "invalid session cookie expiry" - end + name = nil + start = i + 1 + skip = false + end + + else + if b == EQUALS_BYTE or b == SEMICOLON_BYTE then + name = sub(cookies, start, i - 1) + elseif i == size then + name = sub(cookies, start, i) + end + + if name then + name = trim(name) + if b == SEMICOLON_BYTE or i == size then + if name ~= "" then + HIDE_BUFFER:put(name) + if i ~= size then + HIDE_BUFFER:put(";") end + + elseif i == size then + break + end + + name = nil + else - local name = cookie_parts[count] + if name == cookie_name then + skip = true + + elseif cookie_chunks > 1 then + local chunk_number = tonumber(sub(name, -1), 10) + if chunk_number and chunk_number > 1 and chunk_number <= cookie_chunks + and sub(name, 1, -2) == cookie_name + then + skip = true + end + end - local cookie_part = self.encoder.decode(sub(value, pos, p_pos - 1)) - if not cookie_part then - return nil, "unable to decode session cookie part (" .. name .. ")" + if not skip then + if name ~= "" then + HIDE_BUFFER:put(name) end - cookie[name] = cookie_part + if b == EQUALS_BYTE then + HIDE_BUFFER:put("=") + end + end end - count = count + 1 - pos = p_pos + 1 + start = i + 1 + end + end + end + + if #HIDE_BUFFER == 0 then + clear_request_header("Cookie") + else + set_request_header("Cookie", HIDE_BUFFER:get()) + end + + return true +end + + + +local function get_remember(self) + local flags = self.meta.flags + if flags and has_flag(flags, FLAG_FORGET) then + return false + end + + if has_flag(self.flags, FLAG_FORGET) then + return false + end + + return self.remember +end + + +local function set_property_header(self, property_header, set_header) + local name = HEADERS[property_header] + if not name then + return + end - p_pos = find(value, "|", pos, true) + local value = get_property(self, property_header) + if not value then + return + end + + set_header(name, value) +end + + +local function set_property_headers(self, headers, set_header) + if not headers then + return + end + + local count = #headers + if count == 0 then + return + end + + for i = 1, count do + set_property_header(self, headers[i], set_header) + end +end + + +local function set_property_headers_vararg(self, set_header, count, ...) + if count == 1 then + local header_or_headers = select(1, ...) + if type(header_or_headers) == "table" then + return set_property_headers(self, header_or_headers, set_header) end - if count ~= cookie_parts.n then - return nil, "invalid number of session cookie parts" + return set_property_header(self, header_or_headers, set_header) + end + + for i = 1, count do + local property_header = select(i, ...) + set_property_header(self, property_header, set_header) + end +end + + +--- +-- Session +-- @section instance + + +local fake_info_mt = {} + + +function fake_info_mt:set(key, value) + local session = self.session + assert(session.state ~= STATE_CLOSED, "unable to set session info on closed session") + session.data[session.data_index][1]["@" .. key] = value +end + + +function fake_info_mt:get(key) + local session = self.session + assert(session.state ~= STATE_CLOSED, "unable to get session info on closed session") + return session.data[session.data_index][1]["@" .. key] +end + + +function fake_info_mt:save() + local session = self.session + assert(session.state == STATE_OPEN, "unable to save session info on nonexistent or closed session") + return session:save() +end + + +fake_info_mt.__index = fake_info_mt + + +local fake_info = {} + + +function fake_info.new(session) + return setmetatable({ + session = session, + data = false, + }, fake_info_mt) +end + + +local info_mt = {} + + +info_mt.__index = info_mt + + +--- +-- Set a value in session information store. +-- +-- @function instance.info:set +-- @tparam string key key +-- @param value value +function info_mt:set(key, value) + local session = self.session + assert(session.state ~= STATE_CLOSED, "unable to set session info on closed session") + local audience = session.data[session.data_index][2] + local data = self.data + if data then + if data[audience] then + data[audience][key] = value + + else + data[audience] = { + [key] = value, + } end - local name = cookie_parts[count] + else + self.data = { + [audience] = { + [key] = value, + }, + } + end +end + - local cookie_part = self.encoder.decode(sub(value, pos)) - if not cookie_part then - return nil, "unable to decode session cookie part (" .. name .. ")" +--- +-- Get a value from session information store. +-- +-- @function instance.info:get +-- @tparam string key key +-- @return value +function info_mt:get(key) + local session = self.session + assert(session.state ~= STATE_CLOSED, "unable to get session info on closed session") + local data = self.data + if not data then + return + end + + local audience = session.data[session.data_index][2] + data = self.data[audience] + if not data then + return + end + + return data[key] +end + + +--- +-- Save information. +-- +-- Only updates backend storage. Does not send a new cookie. +-- +-- @function instance.info:save +-- @treturn true|nil ok +-- @treturn string error message +function info_mt:save() + local session = self.session + assert(session.state == STATE_OPEN, "unable to save session info on nonexistent or closed session") + local data = self.data + if not data then + return true + end + + local err + data, err = encode_json(data) + if not data then + return nil, errmsg(err, "unable to json encode session info") + end + + data, err = encode_base64url(data) + if not data then + return nil, errmsg(err, "unable to base64url encode session info") + end + + local ok, err = save_info(session, data) + if not ok then + return nil, err + end + + if get_remember(session) then + if not session.remember_meta then + local remembered = open(self, true, true) + if not remembered then + return save(session, nil, true) + end end - cookie[name] = cookie_part + return save_info(session, data, true) + end + + return true +end + + +local info = {} + + +function info.new(session) + return setmetatable({ + session = session, + data = false, + }, info_mt) +end + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Set session data. +-- +-- @function instance:set_data +-- @tparam table data data +-- +-- @usage +-- local session, err, exists = require "resty.session".open() +-- if not exists then +-- session:set_data({ +-- cart = {}, +-- }) +-- session:save() +-- end +function metatable:set_data(data) + assert(self.state ~= STATE_CLOSED, "unable to set session data on closed session") + assert(type(data) == "table", "invalid session data") + self.data[self.data_index][1] = data +end + + +--- +-- Get session data. +-- +-- @function instance:get_data +-- @treturn table value +-- +-- @usage +-- local session, err, exists = require "resty.session".open() +-- if exists then +-- local data = session:get_data() +-- ngx.req.set_header("Authorization", "Bearer " .. data.access_token) +-- end +function metatable:get_data() + assert(self.state ~= STATE_CLOSED, "unable to set session data on closed session") + return self.data[self.data_index][1] +end + + +--- +-- Set a value in session. +-- +-- @function instance:set +-- @tparam string key key +-- @param value value +-- +-- local session, err, exists = require "resty.session".open() +-- if not exists then +-- session:set("access-token", "eyJ...") +-- session:save() +-- end +function metatable:set(key, value) + assert(self.state ~= STATE_CLOSED, "unable to set session data value on closed session") + if self.storage or byte(key, 1) ~= AT_BYTE then + self.data[self.data_index][1][key] = value + else + self.data[self.data_index][1]["$" .. key] = value + end +end + + +--- +-- Get a value from session. +-- +-- @function instance:get +-- @tparam string key key +-- @return value +-- +-- @usage +-- local session, err, exists = require "resty.session".open() +-- if exists then +-- local access_token = session:get("access-token") +-- ngx.req.set_header("Authorization", "Bearer " .. access_token) +-- end +function metatable:get(key) + assert(self.state ~= STATE_CLOSED, "unable to get session data value on closed session") + if self.storage or byte(key, 1) ~= AT_BYTE then + return self.data[self.data_index][1][key] + else + return self.data[self.data_index][1]["$" .. key] + end +end + + +--- +-- Set session audience. +-- +-- @function instance:set_audience +-- @tparam string audience audience +-- +-- @usage +-- local session = require "resty.session".new() +-- session.set_audience("my-service") +function metatable:set_audience(audience) + assert(self.state ~= STATE_CLOSED, "unable to set audience on closed session") + + local data = self.data + local data_index = self.data_index + local current_audience = data[data_index][2] + if audience == current_audience then + return + end + + local info_data = self.info.data + if info_data then + info_data[audience] = info_data[current_audience] + info_data[current_audience] = nil + end + + local count = #data + if count == 1 then + data[1][2] = audience + return + end + + local previous_index + for i = 1, count do + if data[i][2] == audience then + previous_index = i + break + end + end + + data[data_index][2] = audience + + if not previous_index or previous_index == data_index then + return + end + + remove(data, previous_index) + + if previous_index < data_index then + self.data_index = data_index - 1 + end +end + + +--- +-- Get session audience. +-- +-- @function instance:get_audience +-- @treturn string audience +function metatable:get_audience() + assert(self.state ~= STATE_CLOSED, "unable to get audience on closed session") + return self.data[self.data_index][2] +end + + +--- +-- Set session subject. +-- +-- @function instance:set_subject +-- @tparam string subject subject +-- +-- @usage +-- local session = require "resty.session".new() +-- session.set_subject("john@doe.com") +function metatable:set_subject(subject) + assert(self.state ~= STATE_CLOSED, "unable to set subject on closed session") + self.data[self.data_index][3] = subject +end + + +--- +-- Get session subject. +-- +-- @function instance:get_subject +-- @treturn string subject +-- +-- @usage +-- local session, err, exists = require "resty.session".open() +-- if exists then +-- local subject = session.get_subject() +-- end +function metatable:get_subject() + assert(self.state ~= STATE_CLOSED, "unable to get subject on closed session") + return self.data[self.data_index][3] +end + + +--- +-- Get session property. +-- +-- Possible property names: +-- * `"id"`: 43 bytes session id (same as nonce, but base64 url-encoded) +-- * `"nonce"`: 32 bytes nonce (same as session id but in raw bytes) +-- * `"audience"`: Current session audience +-- * `"subject"`: Current session subject +-- * `"timeout"`: Closest timeout (in seconds) (what's left of it) +-- * `"idling-timeout`"`: Session idling timeout (in seconds) (what's left of it) +-- * `"rolling-timeout`"`: Session rolling timeout (in seconds) (what's left of it) +-- * `"absolute-timeout`"`: Session absolute timeout (in seconds) (what's left of it) +-- +-- @function instance:get_property +-- @treturn string|number metadata +-- +-- @usage +-- local session, err, exists = require "resty.session".open() +-- if exists then +-- local timeout = session.get_property("timeout") +-- end +function metatable:get_property(name) + assert(self.state ~= STATE_CLOSED, "unable to get session property on closed session") + return get_property(self, name) +end + + +--- +-- Set persistent sessions on/off. +-- +-- In many login forms user is given an option for "remember me". +-- You can call this function based on what user selected. +-- +-- @function instance:set_remember +-- @tparam boolean value `true` to enable persistent session, `false` to disable them +function metatable:set_remember(value) + assert(self.state ~= STATE_CLOSED, "unable to set remember on closed session") + assert(type(value) == "boolean", "invalid remember value") + if value == false then + set_flag(self.flags, FLAG_FORGET) + else + unset_flag(self.flags, FLAG_FORGET) + end + + self.remember = value +end + + +--- +-- Get state of persistent sessions. +-- +-- @function instance:get_remember +-- @treturn boolean `true` when persistent sessions are enabled, otherwise `false` +function metatable:get_remember() + assert(self.state ~= STATE_CLOSED, "unable to get remember on closed session") + return get_remember(self) +end + + +--- +-- Open a session. +-- +-- This can be used to open a session. +-- +-- @function instance:open +-- @treturn true|nil ok +-- @treturn string error message +function metatable:open() + local exists, err, audience_error = open(self) + if exists then + return true + end + + if audience_error then + return nil, err + end + + if not self.remember then + return nil, err + end + + local remembered = open(self, true) + if not remembered then + return nil, err + end + + local ok, err = save(self, nil, true) + if not ok then + return nil, err + end + + self.state = STATE_NEW + self.meta = DEFAULT_META + + local ok, err = save(self, STATE_OPEN) + if not ok then + return nil, err + end + + return true +end + + +--- +-- Save the session. +-- +-- Saves the session data and issues a new session cookie with a new session id. +-- When `remember` is enabled, it will also issue a new persistent cookie and +-- possibly save the data in backend store. +-- +-- @function instance:save +-- @treturn true|nil ok +-- @treturn string error message +function metatable:save() + assert(self.state ~= STATE_CLOSED, "unable to save closed session") + + local ok, err = save(self) + if not ok then + return nil, err + end + + if get_remember(self) then + if not self.remember_meta then + open(self, true, true) + end - if not cookie.id then - return nil, "missing session cookie id" + local ok, err = save(self, nil, true) + if not ok then + log(WARN, "[session] ", err) end + end + + return true +end + + +--- +-- Touch the session. +-- +-- Updates idling offset of the session by sending an updated session cookie. +-- It only sends the client cookie and never calls any backend session store +-- APIs. Normally the `session:refresh` is used to call this indirectly. +-- +-- @function instance:touch +-- @treturn true|nil ok +-- @treturn string error message +function metatable:touch() + assert(self.state == STATE_OPEN, "unable to touch nonexistent or closed session") + + local meta = self.meta + local idling_offset = min(time() - meta.creation_time - meta.rolling_offset, MAX_IDLING_OFFSET) + + HEADER_BUFFER:reset():put(sub(meta.header, 1, HEADER_TOUCH_SIZE), + bpack(IDLING_OFFSET_SIZE, idling_offset)) + + local mac, err = calculate_mac(meta.ikm, meta.sid, HEADER_BUFFER:tostring()) + if not mac then + return nil, err + end + + local payload_header = HEADER_BUFFER:put(mac):get() + + meta.idling_offset = idling_offset + meta.header = payload_header + + payload_header, err = encode_base64url(payload_header) + if not payload_header then + return nil, errmsg(err, "unable to base64url encode session header") + end + + local cookie_flags = self.cookie_flags + local cookie_name = self.cookie_name + local cookie_data + if self.storage then + cookie_data = fmt("%s=%s%s", cookie_name, payload_header, cookie_flags) + else + cookie_data = fmt("%s=%s%s%s", cookie_name, payload_header, meta.initial_chunk, cookie_flags) + end + + header["Set-Cookie"] = merge_cookies(header["Set-Cookie"], #cookie_name, cookie_name, cookie_data) + + return true +end - if not cookie.expires then - return nil, "missing session cookie expiry" + +--- +-- Refresh the session. +-- +-- Either saves the session (creating a new session id) or touches the session +-- depending on whether the rolling timeout is getting closer, which means +-- by default when 3/4 of rolling timeout is spent - 45 minutes with default +-- rolling timeout of an hour. The touch has a threshold, by default one minute, +-- so it may be skipped in some cases (you can call `session:touch()` to force it). +-- +-- @function instance:refresh +-- @treturn true|nil ok +-- @treturn string error message +function metatable:refresh() + assert(self.state == STATE_OPEN, "unable to refresh nonexistent or closed session") + + local rolling_timeout = self.rolling_timeout + local idling_timeout = self.idling_timeout + if rolling_timeout == 0 and idling_timeout == 0 then + return true + end + + local meta = self.meta + local rolling_elapsed = meta.timestamp - meta.creation_time - meta.rolling_offset + + if rolling_timeout > 0 then + if rolling_elapsed > floor(rolling_timeout * 0.75) then + -- TODO: in case session was modified before calling this function, the possible remember me cookie needs to be saved too? + return save(self) end + end - if cookie.expires <= self.now then - return nil, "session cookie has expired" + if idling_timeout > 0 then + local idling_elapsed = rolling_elapsed - meta.idling_offset + if idling_elapsed > self.touch_threshold then + return self:touch() end + end + + return true +end + - if cookie.usebefore and cookie.usebefore <= self.now then - return nil, "session cookie idle time has passed" +--- +-- Logout the session. +-- +-- Logout either destroys the session or just clears the data for the current audience, +-- and saves it (logging out from the current audience). +-- +-- @function instance:logout +-- @treturn true|nil ok +-- @treturn string error message +function metatable:logout() + assert(self.state == STATE_OPEN, "unable to logout nonexistent or closed session") + + local data = self.data + if #data == 1 then + return self:destroy() + end + + local data_index = self.data_index + local info_store = self.info + local info_data = info_store and info_store.data + if info_data then + local audience = data[data_index][2] + info_data[audience] = nil + if isempty(info_data) then + info_store.data = false end + end - if not cookie.hash then - return nil, "missing session cookie signature" + remove(data, data_index) + + local ok, err = save(self, STATE_CLOSED) + if not ok then + return nil, err + end + + if get_remember(self) then + if not self.remember_meta then + open(self, true, true) + end + local ok, err = save(self, nil, true) + if not ok then + log(WARN, "[session] ", err) end + end - return cookie + return true end -function session.new(opts) - if opts and getmetatable(opts) == session then - return opts + +--- +-- Destroy the session. +-- +-- Destroy the session and clear the cookies. +-- +-- @function instance:destroy +-- @treturn true|nil ok +-- @treturn string error message +function metatable:destroy() + assert(self.state == STATE_OPEN, "unable to destroy nonexistent or closed session") + + local ok, err = destroy(self) + if not ok then + return nil, err + end + + if get_remember(self) then + if not self.remember_meta then + local remembered = open(self, true, true) + if not remembered then + return true + end end - if not defaults then - init() + ok, err = destroy(self, true) + if not ok then + return nil, err end + end - opts = type(opts) == "table" and opts or defaults + return true +end - local cookie = opts.cookie or defaults.cookie - local name = opts.name or defaults.name - local sec = opts.secret or defaults.secret - local secure - local path - local domain - if find(name, "__Host-", 1, true) == 1 then - secure = true - path = "/" - else - if find(name, "__Secure-", 1, true) == 1 then - secure = true - else - secure = ifnil(cookie.secure, defaults.cookie.secure) - end +--- +-- Close the session. +-- +-- Just closes the session instance so that it cannot be used anymore. +-- +-- @function instance:close +function metatable:close() + self.state = STATE_CLOSED +end - domain = cookie.domain or defaults.cookie.domain - path = cookie.path or defaults.cookie.path - end - - local check = opts.check or defaults.check - - local ide, iden = prequire("resty.session.identifiers.", opts.identifier or defaults.identifier, "random") - local ser, sern = prequire("resty.session.serializers.", opts.serializer or defaults.serializer, "json") - local com, comn = prequire("resty.session.compressors.", opts.compressor or defaults.compressor, "none") - local enc, encn = prequire("resty.session.encoders.", opts.encoder or defaults.encoder, "base64") - local cip, cipn = prequire("resty.session.ciphers.", opts.cipher or defaults.cipher, "aes") - local sto, ston = prequire("resty.session.storage.", opts.storage or defaults.storage, "cookie") - local str, strn = prequire("resty.session.strategies.", opts.strategy or defaults.strategy, "default") - local hma, hman = prequire("resty.session.hmac.", opts.hmac or defaults.hmac, "sha1") - - local self = { - now = time(), - name = name, - secret = sec, - identifier = ide, - serializer = ser, - strategy = str, - encoder = enc, - hmac = hma, - cookie = { - storage = ston, - encoder = enc, - path = path, - domain = domain, - secure = secure, - samesite = cookie.samesite or defaults.cookie.samesite, - httponly = ifnil(cookie.httponly, defaults.cookie.httponly), - persistent = ifnil(cookie.persistent, defaults.cookie.persistent), - discard = tonumber(cookie.discard, 10) or defaults.cookie.discard, - renew = tonumber(cookie.renew, 10) or defaults.cookie.renew, - lifetime = tonumber(cookie.lifetime, 10) or defaults.cookie.lifetime, - idletime = tonumber(cookie.idletime, 10) or defaults.cookie.idletime, - maxsize = tonumber(cookie.maxsize, 10) or defaults.cookie.maxsize, - }, check = { - ssi = ifnil(check.ssi, defaults.check.ssi), - ua = ifnil(check.ua, defaults.check.ua), - scheme = ifnil(check.scheme, defaults.check.scheme), - addr = ifnil(check.addr, defaults.check.addr), - } - } - if self.cookie.idletime > 0 and self.cookie.discard > self.cookie.idletime then - -- if using idletime, then the discard period must be less or equal - self.cookie.discard = self.cookie.idletime + +--- +-- Clear the request session cookie. +-- +-- Modifies the request headers by removing the session related +-- cookies. This is useful when you use the session library on +-- a proxy server and don't want the session cookies to be forwarded +-- to the upstream service. +-- +-- @function instance:clear_request_cookie +function metatable:clear_request_cookie() + assert(self.state == STATE_OPEN, "unable to hide nonexistent or closed session") + + local ok = clear_request_cookie(self) + if not ok then + log(NOTICE, "[session] unable to clear session request cookie") + end + + if get_remember(self) then + ok = clear_request_cookie(self, true) + if not ok then + log(NOTICE, "[session] unable to clear persistent session request cookie") end + end +end - if iden and not self[iden] then self[iden] = opts[iden] end - if sern and not self[sern] then self[sern] = opts[sern] end - if comn and not self[comn] then self[comn] = opts[comn] end - if encn and not self[encn] then self[encn] = opts[encn] end - if cipn and not self[cipn] then self[cipn] = opts[cipn] end - if ston and not self[ston] then self[ston] = opts[ston] end - if strn and not self[strn] then self[strn] = opts[strn] end - if hman and not self[hman] then self[hman] = opts[hman] end - self.cipher = cip.new(self) - self.storage = sto.new(self) - self.compressor = com.new(self) +--- +-- Sets request and response headers. +-- +-- @function instance:set_headers +-- @tparam[opt] string ... +function metatable:set_headers(...) + assert(self.state == STATE_OPEN, "unable to set request/response headers of nonexistent or closed session") - return setmetatable(self, session) + local count = select("#", ...) + if count == 0 then + set_property_headers(self, self.request_headers, set_request_header) + set_property_headers(self, self.response_headers, set_response_header) + return + end + + set_property_headers_vararg(self, set_request_header, count, ...) + set_property_headers_vararg(self, set_response_header, count, ...) end -function session.open(opts, keep_lock) - local self = opts - if self and getmetatable(self) == session then - if self.opened then - return self, self.present - end + +--- +-- Set request headers. +-- +-- @function instance:set_request_headers +-- @tparam[opt] string ... +function metatable:set_request_headers(...) + assert(self.state == STATE_OPEN, "unable to set request headers of nonexistent or closed session") + + local count = select("#", ...) + if count == 0 then + set_property_headers(self, self.request_headers, set_request_header) + return + end + + set_property_headers_vararg(self, set_request_header, count, ...) +end + + +--- +-- Set response headers. +-- +-- @function instance:set_response_headers +-- @tparam[opt] string ... +function metatable:set_response_headers(...) + assert(self.state == STATE_OPEN, "unable to set response headers of nonexistent or closed session") + + local count = select("#", ...) + if count == 0 then + set_property_headers(self, self.response_headers, set_response_header) + return + end + + set_property_headers_vararg(self, set_response_header, count, ...) +end + + + +local session = { + _VERSION = "4.0.0", + metatable = metatable, +} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Session configuration. +-- @field secret Secret used for the key derivation. The secret is hashed with SHA-256 before using it. E.g. `"RaJKp8UQW1"`. +-- @field secret_fallbacks Array of secrets that can be used as alternative secrets (when doing key rotation), E.g. `{ "6RfrAYYzYq", "MkbTkkyF9C" }`. +-- @field ikm Initial key material (or ikm) can be specified directly (without using a secret) with exactly 32 bytes of data. E.g. `"5ixIW4QVMk0dPtoIhn41Eh1I9enP2060"` +-- @field ikm_fallbacks Array of initial key materials that can be used as alternative keys (when doing key rotation), E.g. `{ "QvPtlPKxOKdP5MCu1oI3lOEXIVuDckp7" }`. +-- @field cookie_prefix Cookie prefix, use `nil`, `"__Host-"` or `"__Secure-"` (defaults to `nil`) +-- @field cookie_name Session cookie name, e.g. `"session"` (defaults to `"session"`) +-- @field cookie_path Cookie path, e.g. `"/"` (defaults to `"/"`) +-- @field cookie_domain Cookie domain, e.g. `"example.com"` (defaults to `nil`) +-- @field cookie_http_only Mark cookie HTTP only, use `true` or `false` (defaults to `true`) +-- @field cookie_secure Mark cookie secure, use `nil`, `true` or `false` (defaults to `nil`) +-- @field cookie_priority Cookie priority, use `nil`, `"Low"`, `"Medium"`, or `"High"` (defaults to `nil`) +-- @field cookie_same_site Cookie same-site policy, use `nil`, `"Lax"`, `"Strict"`, `"None"`, or `"Default"` (defaults to `"Lax"`) +-- @field cookie_same_party Mark cookie with same party flag, use `nil`, `true`, or `false` (default: `nil`) +-- @field cookie_partitioned Mark cookie with partitioned flag, use `nil`, `true`, or `false` (default: `nil`) +-- @field remember Enable or disable persistent sessions, use `nil`, `true`, or `false` (defaults to `false`) +-- @field remember_safety Remember cookie key derivation complexity, use `nil`, `"None"` (fast), `"Low"`, `"Medium"`, `"High"` or `"Very High"` (slow) (defaults to `"Medium"`) +-- @field remember_cookie_name Persistent session cookie name, e.g. `"remember"` (defaults to `"remember"`) +-- @field audience Session audience, e.g. `"my-application"` (defaults to `"default"`) +-- @field subject Session subject, e.g. `"john.doe@example.com"` (defaults to `nil`) +-- @field enforce_same_subject When set to `true`, audiences need to share the same subject. The library removes non-subject matching audience data on save. +-- @field stale_ttl When session is saved a new session is created, stale ttl specifies how long the old one can still be used, e.g. `10` (defaults to `10`) (in seconds) +-- @field idling_timeout Idling timeout specifies how long the session can be inactive until it is considered invalid, e.g. `900` (defaults to `900`, or 15 minutes) (in seconds) +-- @field rolling_timeout Rolling timeout specifies how long the session can be used until it needs to be renewed, e.g. `3600` (defaults to `3600`, or an hour) (in seconds) +-- @field absolute_timeout Absolute timeout limits how long the session can be renewed, until re-authentication is required, e.g. `86400` (defaults to `86400`, or a day) (in seconds) +-- @field remember_rolling_timeout Remember timeout specifies how long the persistent session is considered valid, e.g. `604800` (defaults to `604800`, or a week) (in seconds) +-- @field remember_absolute_timeout Remember absolute timeout limits how long the persistent session can be renewed, until re-authentication is required, e.g. `2592000` (defaults to `2592000`, or 30 days) (in seconds) +-- @field hash_storage_key Whether to hash or not the storage key. With storage key hashed it is impossible to decrypt data on server side without having a cookie too (defaults to `false`). +-- @field hash_subject Whether to hash or not the subject when `store_metadata` is enabled, e.g. for PII reasons (defaults to `false`). +-- @field store_metadata Whether to also store metadata of sessions, such as collecting data of sessions for a specific audience belonging to a specific subject (defaults to `false`). +-- @field touch_threshold Touch threshold controls how frequently or infrequently the `session:refresh` touches the cookie, e.g. `60` (defaults to `60`, or a minute) (in seconds) +-- @field compression_threshold Compression threshold controls when the data is deflated, e.g. `1024` (defaults to `1024`, or a kilobyte) (in bytes) +-- @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 dshm Configuration for dshm storage, e.g. `{ prefix = "sessions" }` +-- @field file Configuration for file storage, e.g. `{ path = "/tmp", suffix = "session" }` +-- @field memcached Configuration for memcached storage, e.g. `{ prefix = "sessions" }` +-- @field mysql Configuration for MySQL / MariaDB storage, e.g. `{ database = "sessions" }` +-- @field postgres Configuration for Postgres storage, e.g. `{ database = "sessions" }` +-- @field redis Configuration for Redis / Redis Sentinel / Redis Cluster storages, e.g. `{ prefix = "sessions" }` +-- @field shm Configuration for shared memory storage, e.g. `{ zone = "sessions" }` +-- @field ["custom-storage"] Custom storage (loaded with `require "custom-storage"`) configuration +-- @table configuration + + +--- +-- Initialization +-- @section initialization + + +--- +-- Initialize the session library. +-- +-- This function can be called on `init` or `init_worker` phases on OpenResty +-- to set global default configuration to all session instances created by this +-- library. +-- +-- @function module.init +-- @tparam[opt] table configuration session @{configuration} overrides +-- +-- @usage +-- require "resty.session".init({ +-- audience = "my-application", +-- storage = "redis", +-- redis = { +-- username = "session", +-- password = "storage", +-- }, +-- }) +function session.init(configuration) + if configuration then + local ikm = configuration.ikm + if ikm then + assert(#ikm == KEY_SIZE, "invalid ikm size") + DEFAULT_IKM = ikm + else - self = session.new(opts) + local secret = configuration.secret + if secret then + DEFAULT_IKM = assert(sha256(secret)) + end end - if self.cookie.secure == nil then - self.cookie.secure = var.scheme == "https" or var.https == "on" - end + local ikm_fallbacks = configuration.ikm_fallbacks + if ikm_fallbacks then + local count = #ikm_fallbacks + for i = 1, count do + assert(#ikm_fallbacks[i] == KEY_SIZE, "invalid ikm size in ikm_fallbacks") + end - self.now = time() - self.key = concat { - self.check.ssi and var.ssl_session_id or "", - self.check.ua and var.http_user_agent or "", - self.check.addr and var.remote_addr or "", - self.check.scheme and var.scheme or "", - } + DEFAULT_IKM_FALLBACKS = ikm_fallbacks - self.opened = true + else + local secret_fallbacks = configuration.secret_fallbacks + if secret_fallbacks then + local count = #secret_fallbacks + if count > 0 then + DEFAULT_IKM_FALLBACKS = table_new(count, 0) + for i = 1, count do + DEFAULT_IKM_FALLBACKS[i] = assert(sha256(secret_fallbacks[i])) + end - local err - local cookie = self:get_cookie() - if cookie then - cookie, err = self:parse_cookie(cookie) - if cookie then - local ok - ok, err = self.strategy.open(self, cookie, keep_lock) - if ok then - return self, true - end + else + DEFAULT_IKM_FALLBACKS = nil end + end end - regenerate(self, true) + local request_headers = configuration.request_headers + if request_headers then + local count = #request_headers + for i = 1, count do + assert(HEADERS[request_headers[i]], "invalid request header") + end - return self, false, err -end - -function session.start(opts) - if opts and getmetatable(opts) == session and opts.started then - return opts, opts.present + DEFAULT_REQUEST_HEADERS = request_headers end - local self, present, reason = session.open(opts, true) + local response_headers = configuration.response_headers + if response_headers then + local count = #response_headers + for i = 1, count do + assert(HEADERS[response_headers[i]], "invalid response header") + end - self.started = true + DEFAULT_RESPONSE_HEADERS = response_headers + end - if not present then - local ok, err = save(self) - if not ok then - return nil, err or "unable to save session cookie" - end + DEFAULT_COOKIE_NAME = configuration.cookie_name or DEFAULT_COOKIE_NAME + DEFAULT_COOKIE_PATH = configuration.cookie_path or DEFAULT_COOKIE_PATH + DEFAULT_COOKIE_DOMAIN = configuration.cookie_domain or DEFAULT_COOKIE_DOMAIN + DEFAULT_COOKIE_SAME_SITE = configuration.cookie_same_site or DEFAULT_COOKIE_SAME_SITE + DEFAULT_COOKIE_PRIORITY = configuration.cookie_priority or DEFAULT_COOKIE_PRIORITY + DEFAULT_COOKIE_PREFIX = configuration.cookie_prefix or DEFAULT_COOKIE_PREFIX + DEFAULT_REMEMBER_SAFETY = configuration.remember_safety or DEFAULT_REMEMBER_SAFETY + DEFAULT_REMEMBER_COOKIE_NAME = configuration.remember_cookie_name or DEFAULT_REMEMBER_COOKIE_NAME + DEFAULT_AUDIENCE = configuration.audience or DEFAULT_AUDIENCE + DEFAULT_SUBJECT = configuration.subject or DEFAULT_SUBJECT + DEFAULT_STALE_TTL = configuration.stale_ttl or DEFAULT_STALE_TTL + DEFAULT_IDLING_TIMEOUT = configuration.idling_timeout or DEFAULT_IDLING_TIMEOUT + DEFAULT_ROLLING_TIMEOUT = configuration.rolling_timeout or DEFAULT_ROLLING_TIMEOUT + DEFAULT_ABSOLUTE_TIMEOUT = configuration.absolute_timeout or DEFAULT_ABSOLUTE_TIMEOUT + DEFAULT_REMEMBER_ROLLING_TIMEOUT = configuration.remember_rolling_timeout or DEFAULT_REMEMBER_ROLLING_TIMEOUT + DEFAULT_REMEMBER_ABSOLUTE_TIMEOUT = configuration.remember_absolute_timeout or DEFAULT_REMEMBER_ABSOLUTE_TIMEOUT + DEFAULT_TOUCH_THRESHOLD = configuration.touch_threshold or DEFAULT_TOUCH_THRESHOLD + DEFAULT_COMPRESSION_THRESHOLD = configuration.compression_threshold or DEFAULT_COMPRESSION_THRESHOLD + DEFAULT_STORAGE = configuration.storage or DEFAULT_STORAGE + + local cookie_http_only = configuration.cookie_http_only + if cookie_http_only ~= nil then + DEFAULT_COOKIE_HTTP_ONLY = cookie_http_only + end - return self, present, reason + local cookie_same_party = configuration.cookie_same_party + if cookie_same_party ~= nil then + DEFAULT_COOKIE_SAME_PARTY = cookie_same_party end - if self.strategy.start then - local ok, err = self.strategy.start(self) - if not ok then - return nil, err or "unable to start session" - end + local cookie_partitioned = configuration.cookie_partitioned + if cookie_partitioned ~= nil then + DEFAULT_COOKIE_PARTITIONED = cookie_partitioned end - if self.expires - self.now < self.cookie.renew - or self.expires > self.now + self.cookie.lifetime - then - local ok, err = save(self) - if not ok then - return nil, err or "unable to save session cookie" - end - else - -- we're not saving, so we must touch to update idletime/usebefore - local ok, err = touch(self) - if not ok then - return nil, err or "unable to touch session cookie" - end + local cookie_secure = configuration.cookie_secure + if cookie_secure ~= nil then + DEFAULT_COOKIE_SECURE = cookie_secure end - return self, true -end + local remember = configuration.remember + if remember ~= nil then + DEFAULT_REMEMBER = remember + end -function session.destroy(opts) - if opts and getmetatable(opts) == session and opts.destroyed then - return true + local hash_storage_key = configuration.hash_storage_key + if hash_storage_key ~= nil then + DEFAULT_HASH_STORAGE_KEY = hash_storage_key end - local self, err = session.start(opts) - if not self then - return nil, err + local hash_subject = configuration.hash_subject + if hash_subject ~= nil then + DEFAULT_HASH_SUBJECT = hash_subject + end + + local store_metadate = configuration.store_metadata + if store_metadate ~= nil then + DEFAULT_STORE_METADATA = store_metadate end - if self.strategy.destroy then - self.strategy.destroy(self) - elseif self.strategy.close then - self.strategy.close(self) + local enforce_same_subject = configuration.enforce_same_subject + if enforce_same_subject ~= nil then + DEFAULT_ENFORCE_SAME_SUBJECT = enforce_same_subject end + end - self.data = {} - self.present = nil - self.opened = nil - self.started = nil - self.closed = true - self.destroyed = true + if not DEFAULT_IKM then + DEFAULT_IKM = assert(sha256(assert(rand_bytes(KEY_SIZE)))) + end - return set_cookie(self, "", true) + if type(DEFAULT_STORAGE) == "string" then + DEFAULT_STORAGE = load_storage(DEFAULT_STORAGE, configuration) + end end -function session:regenerate(flush, close) - close = close ~= false - if self.strategy.regenerate then - if flush then - self.data = {} - end - if not self.id then - self.id = session:identifier() +--- +-- Constructors +-- @section constructors + + +--- +-- Create a new session. +-- +-- This creates a new session instance. +-- +-- @function module.new +-- @tparam[opt] table configuration session @{configuration} overrides +-- @treturn table session instance +-- +-- @usage +-- local session = require "resty.session".new() +-- -- OR +-- local session = require "resty.session".new({ +-- audience = "my-application", +-- }) +function session.new(configuration) + local cookie_name = configuration and configuration.cookie_name or DEFAULT_COOKIE_NAME + local cookie_path = configuration and configuration.cookie_path or DEFAULT_COOKIE_PATH + local cookie_domain = configuration and configuration.cookie_domain or DEFAULT_COOKIE_DOMAIN + local cookie_same_site = configuration and configuration.cookie_same_site or DEFAULT_COOKIE_SAME_SITE + local cookie_priority = configuration and configuration.cookie_priority or DEFAULT_COOKIE_PRIORITY + local cookie_prefix = configuration and configuration.cookie_prefix or DEFAULT_COOKIE_PREFIX + local remember_safety = configuration and configuration.remember_safety or DEFAULT_REMEMBER_SAFETY + local remember_cookie_name = configuration and configuration.remember_cookie_name or DEFAULT_REMEMBER_COOKIE_NAME + local audience = configuration and configuration.audience or DEFAULT_AUDIENCE + local subject = configuration and configuration.subject or DEFAULT_SUBJECT + local stale_ttl = configuration and configuration.stale_ttl or DEFAULT_STALE_TTL + local idling_timeout = configuration and configuration.idling_timeout or DEFAULT_IDLING_TIMEOUT + local rolling_timeout = configuration and configuration.rolling_timeout or DEFAULT_ROLLING_TIMEOUT + local absolute_timeout = configuration and configuration.absolute_timeout or DEFAULT_ABSOLUTE_TIMEOUT + local remember_rolling_timeout = configuration and configuration.remember_rolling_timeout or DEFAULT_REMEMBER_ROLLING_TIMEOUT + local remember_absolute_timeout = configuration and configuration.remember_absolute_timeout or DEFAULT_REMEMBER_ABSOLUTE_TIMEOUT + local touch_threshold = configuration and configuration.touch_threshold or DEFAULT_TOUCH_THRESHOLD + local compression_threshold = configuration and configuration.compression_threshold or DEFAULT_COMPRESSION_THRESHOLD + local storage = configuration and configuration.storage or DEFAULT_STORAGE + local ikm = configuration and configuration.ikm + local ikm_fallbacks = configuration and configuration.ikm_fallbacks + local request_headers = configuration and configuration.request_headers + local response_headers = configuration and configuration.response_headers + + local cookie_http_only = configuration and configuration.cookie_http_only + if cookie_http_only == nil then + cookie_http_only = DEFAULT_COOKIE_HTTP_ONLY + end + + local cookie_secure = configuration and configuration.cookie_secure + if cookie_secure == nil then + cookie_secure = DEFAULT_COOKIE_SECURE + end + + local cookie_same_party = configuration and configuration.cookie_same_party + if cookie_same_party == nil then + cookie_same_party = DEFAULT_COOKIE_SAME_PARTY + end + + local cookie_partitioned = configuration and configuration.cookie_partitioned + if cookie_partitioned == nil then + cookie_partitioned = DEFAULT_COOKIE_PARTITIONED + end + + local remember = configuration and configuration.remember + if remember == nil then + remember = DEFAULT_REMEMBER + end + + local hash_storage_key = configuration and configuration.hash_storage_key + if hash_storage_key == nil then + hash_storage_key = DEFAULT_HASH_STORAGE_KEY + end + + local hash_subject = configuration and configuration.hash_subject + if hash_subject == nil then + hash_subject = DEFAULT_HASH_SUBJECT + end + + local store_metadata = configuration and configuration.store_metadata + if store_metadata == nil then + store_metadata = DEFAULT_STORE_METADATA + end + + local enforce_same_subject = configuration and configuration.enforce_same_subject + if enforce_same_subject == nil then + enforce_same_subject = DEFAULT_ENFORCE_SAME_SUBJECT + end + + if cookie_prefix == "__Host-" then + cookie_name = cookie_prefix .. cookie_name + remember_cookie_name = cookie_prefix .. remember_cookie_name + cookie_path = DEFAULT_COOKIE_PATH + cookie_domain = nil + cookie_secure = true + + elseif cookie_prefix == "__Secure-" then + cookie_name = cookie_prefix .. cookie_name + remember_cookie_name = cookie_prefix .. remember_cookie_name + cookie_secure = true + + elseif cookie_same_site == "None" then + cookie_secure = true + end + + if cookie_same_party then + assert(cookie_same_site ~= "Strict", "SameParty session cookies cannot use SameSite=Strict") + cookie_secure = true + end + + FLAGS_BUFFER:reset() + + if cookie_domain and cookie_domain ~= "localhost" and cookie_domain ~= "" then + FLAGS_BUFFER:put("; Domain=", cookie_domain) + end + + FLAGS_BUFFER:put("; Path=", cookie_path, "; SameSite=", cookie_same_site) + + if cookie_priority then + FLAGS_BUFFER:put("; Priority=", cookie_priority) + end + + if cookie_same_party then + FLAGS_BUFFER:put("; SameParty") + end + + if cookie_partitioned then + FLAGS_BUFFER:put("; Partitioned") + end + + if cookie_secure then + FLAGS_BUFFER:put("; Secure") + end + + if cookie_http_only then + FLAGS_BUFFER:put("; HttpOnly") + end + + local cookie_flags = FLAGS_BUFFER:get() + + if ikm then + assert(#ikm == KEY_SIZE, "invalid ikm size") + + else + local secret = configuration and configuration.secret + if secret then + ikm = assert(sha256(secret)) + + else + if not DEFAULT_IKM then + DEFAULT_IKM = assert(sha256(assert(rand_bytes(KEY_SIZE)))) + end + + ikm = DEFAULT_IKM + end + end + + if ikm_fallbacks then + local count = #ikm_fallbacks + for i = 1, count do + assert(#ikm_fallbacks[i] == KEY_SIZE, "invalid ikm size in ikm_fallbacks") + end + + else + local secret_fallbacks = configuration and configuration.secret_fallbacks + if secret_fallbacks then + local count = #secret_fallbacks + if count > 0 then + ikm_fallbacks = table_new(count, 0) + for i = 1, count do + ikm_fallbacks[i] = assert(sha256(secret_fallbacks[i])) end + end + else - regenerate(self, flush) + ikm_fallbacks = DEFAULT_IKM_FALLBACKS end + end - return save(self, close) -end + if request_headers then + local count = #request_headers + for i = 1, count do + assert(HEADERS[request_headers[i]], "invalid request header") + end -function session:save(close) - close = close ~= false + else + request_headers = DEFAULT_REQUEST_HEADERS + end - if not self.id then - self.id = self:identifier() + if response_headers then + local count = #response_headers + for i = 1, count do + assert(HEADERS[response_headers[i]], "invalid response header") end - return save(self, close) + else + response_headers = DEFAULT_RESPONSE_HEADERS + end + + local t = type(storage) + if t == "string" then + storage = load_storage(storage, configuration) + + elseif t ~= "table" then + assert(storage == nil, "invalid session storage") + end + + local self = setmetatable({ + stale_ttl = stale_ttl, + idling_timeout = idling_timeout, + rolling_timeout = rolling_timeout, + absolute_timeout = absolute_timeout, + remember_rolling_timeout = remember_rolling_timeout, + remember_absolute_timeout = remember_absolute_timeout, + touch_threshold = touch_threshold, + compression_threshold = compression_threshold, + hash_storage_key = hash_storage_key and sha256_storage_key or encode_base64url, + hash_subject = hash_subject and sha256_subject or encode_base64url, + store_metadata = store_metadata, + enforce_same_subject = enforce_same_subject, + cookie_name = cookie_name, + cookie_flags = cookie_flags, + remember_cookie_name = remember_cookie_name, + remember_safety = remember_safety, + remember = remember, + flags = FLAGS_NONE, + storage = storage, + ikm = ikm, + ikm_fallbacks = ikm_fallbacks, + request_headers = request_headers, + response_headers = response_headers, + state = STATE_NEW, + meta = DEFAULT_META, + remember_meta = DEFAULT_REMEMBER_META, + info = info, + data_index = 1, + data = { + { + {}, + audience, + subject, + }, + }, + }, metatable) + + if storage then + self.info = info.new(self) + else + self.info = fake_info.new(self) + end + + return self end -function session:close() - self.closed = true - if self.strategy.close then - return self.strategy.close(self) - end +--- +-- Helpers +-- @section helpers + + +--- +-- Open a session. +-- +-- This can be used to open a session, and it will either return an existing +-- session or a new session. +-- +-- @function module.open +-- @tparam[opt] table configuration session @{configuration} overrides +-- @treturn table session instance +-- @treturn string error message +-- @treturn boolean `true`, if session existed, otherwise `false` +-- +-- @usage +-- local session = require "resty.session".open() +-- -- OR +-- local session, err, exists = require "resty.session".open({ +-- audience = "my-application", +-- }) +function session.open(configuration) + local self = session.new(configuration) + local exists, err = self:open() + if not exists then + return self, err, false + end + + return self, err, true +end - return true + +--- +-- Start a session and refresh it as needed. +-- +-- This can be used to start a session, and it will either return an existing +-- session or a new session. In case there is an existing session, the +-- session will be refreshed as well (as needed). +-- +-- @function module.start +-- @tparam[opt] table configuration session @{configuration} overrides +-- @treturn table session instance +-- @treturn string error message +-- @treturn boolean `true`, if session existed, otherwise `false` +-- @treturn boolean `true`, if session was refreshed, otherwise `false` +-- +-- @usage +-- local session = require "resty.session".start() +-- -- OR +-- local session, err, exists, refreshed = require "resty.session".start({ +-- audience = "my-application", +-- }) +function session.start(configuration) + local self, err, exists = session.open(configuration) + if not exists then + return self, err, false, false + end + + local refreshed, err = self:refresh() + if not refreshed then + return self, err, true, false + end + + return self, nil, true, true end -function session:hide() - local cookies = var.http_cookie - if not cookies or cookies == "" then - return - end - - local results = {} - local name = self.name - local name_len = #name - local found - local i = 1 - local j = 0 - local sc_pos = find(cookies, ";", i, true) - while sc_pos do - local isc, cookie = is_session_cookie(sub(cookies, i, sc_pos - 1), name, name_len) - if isc then - found = true - elseif cookie then - j = j + 1 - results[j] = cookie - end - i = sc_pos + 1 - sc_pos = find(cookies, ";", i, true) - end +--- +-- Logout a session. +-- +-- It logouts from a specific audience. +-- +-- A single session cookie may be shared between multiple audiences +-- (or applications), thus there is a need to be able to logout from +-- just a single audience while keeping the session for the other +-- audiences. +-- +-- When there is only a single audience, then this can be considered +-- equal to `session.destroy`. +-- +-- When the last audience is logged out, the cookie will be destroyed +-- as well and invalidated on a client. +-- +-- @function module.logout +-- @tparam[opt] table configuration session @{configuration} overrides +-- @treturn boolean `true` session exists for an audience and was logged out successfully, otherwise `false` +-- @treturn string error message +-- @treturn boolean `true` if session existed, otherwise `false` +-- @treturn boolean `true` if session was logged out, otherwise `false` +-- +-- @usage +-- require "resty.session".logout() +-- -- OR +-- local ok, err, exists, logged_out = require "resty.session".logout({ +-- audience = "my-application", +-- }) +function session.logout(configuration) + local self, err, exists = session.open(configuration) + if not exists then + return nil, err, false, false + end + + local ok, err = self:logout() + if not ok then + return nil, err, true, false + end + + return true, nil, true, true +end - local isc, cookie - if i == 1 then - isc, cookie = is_session_cookie(cookies, name, name_len) - else - isc, cookie = is_session_cookie(sub(cookies, i), name, name_len) - end +--- +-- Destroy a session. +-- +-- It destroys the whole session and clears the cookies. +-- +-- @function module.destroy +-- @tparam[opt] table configuration session @{configuration} overrides +-- @treturn boolean `true` session exists and was destroyed successfully, otherwise `nil` +-- @treturn string error message +-- @treturn boolean `true` if session existed, otherwise `false` +-- @treturn boolean `true` if session was destroyed, otherwise `false` +-- +-- @usage +-- require "resty.session".destroy() +-- -- OR +-- local ok, err, exists, destroyed = require "resty.session".destroy({ +-- cookie_name = "auth", +-- }) +function session.destroy(configuration) + local self, err, exists = session.open(configuration) + if not exists then + return nil, err, false, false + end + + local ok, err = self:destroy() + if not ok then + return nil, err, true, false + end + + return true, nil, true, true +end - if not isc and cookie then - if not found then - return - end - j = j + 1 - results[j] = cookie - end +function session.__set_ngx_log(ngx_log) + log = ngx_log +end - if j == 0 then - clear_header("Cookie") - else - set_header("Cookie", concat(results, "; ", 1, j)) - end + +function session.__set_ngx_var(ngx_var) + var = ngx_var end + +function session.__set_ngx_header(ngx_header) + header = ngx_header +end + + +function session.__set_ngx_req_clear_header(clear_header) + clear_request_header = clear_header +end + + +function session.__set_ngx_req_set_header(set_header) + set_request_header = set_header +end + + return session diff --git a/lib/resty/session/ciphers/aes.lua b/lib/resty/session/ciphers/aes.lua deleted file mode 100644 index 9a088add..00000000 --- a/lib/resty/session/ciphers/aes.lua +++ /dev/null @@ -1,113 +0,0 @@ -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/lib/resty/session/ciphers/none.lua b/lib/resty/session/ciphers/none.lua deleted file mode 100644 index b29bb881..00000000 --- a/lib/resty/session/ciphers/none.lua +++ /dev/null @@ -1,15 +0,0 @@ -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/lib/resty/session/compressors/none.lua b/lib/resty/session/compressors/none.lua deleted file mode 100644 index 3d14a5ce..00000000 --- a/lib/resty/session/compressors/none.lua +++ /dev/null @@ -1,15 +0,0 @@ -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/lib/resty/session/compressors/zlib.lua b/lib/resty/session/compressors/zlib.lua deleted file mode 100644 index 1d23be02..00000000 --- a/lib/resty/session/compressors/zlib.lua +++ /dev/null @@ -1,43 +0,0 @@ -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/lib/resty/session/dshm.lua b/lib/resty/session/dshm.lua new file mode 100644 index 00000000..325564d0 --- /dev/null +++ b/lib/resty/session/dshm.lua @@ -0,0 +1,348 @@ +--- +-- Distributed Shared Memory (DSHM) backend for session library +-- +-- @module resty.session.dshm + + +local buffer = require "string.buffer" +local utils = require "resty.session.utils" +local dshm = require "resty.dshm" + + +local meta_get_latest = utils.meta_get_latest +local meta_get_value = utils.meta_get_value +local get_name = utils.get_name + + +local setmetatable = setmetatable +local error = error +local pairs = pairs +local null = ngx.null +local max = math.max + + +local DEFAULT_HOST = "127.0.0.1" +local DEFAULT_PORT = 4321 + + +local SESSIONS_BUFFER = buffer.new(128) + + +-- not safe for concurrent access +local function update_meta(dshmc, meta_key, key, exp, current_time) + local metadata = dshmc:get(meta_key) + local sessions = metadata and meta_get_latest(metadata, current_time) or {} + + SESSIONS_BUFFER:reset() + + sessions[key] = exp > 0 and exp or nil + + local max_expiry = current_time + for s, e in pairs(sessions) do + SESSIONS_BUFFER:put(meta_get_value(s, e)) + max_expiry = max(max_expiry, e) + end + + local ser = SESSIONS_BUFFER:get() + + if #ser > 0 then + return dshmc:set(meta_key, ser, max_expiry - current_time) + end + + return dshmc:delete(meta_key) +end + + +local function READ_METADATA(self, dshmc, name, audience, subject, current_time) + local meta_key = get_name(self, name, audience, subject) + local res = dshmc:get(meta_key) + if not res then + return nil, "not found" + end + + return meta_get_latest(res, current_time) +end + + +local function SET(self, dshmc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + local inferred_key = get_name(self, name, key) + + if not metadata and not old_key then + return dshmc:set(inferred_key, value, ttl) + end + + local ok, err = dshmc:set(inferred_key, value, ttl) + if err then + return nil, err + end + + local old_name = old_key and get_name(self, name, old_key) + if old_name then + if remember then + dshmc:delete(old_name) + else + dshmc:touch(old_name, stale_ttl) + end + end + + if metadata then + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = get_name(self, name, audiences[i], subjects[i]) + update_meta(dshmc, meta_key, key, current_time + ttl, current_time) + + if old_key then + update_meta(dshmc, meta_key, old_key, 0, current_time) + end + end + end + return ok +end + + +local function GET(self, dshmc, name, key) + local res, err = dshmc:get(get_name(self, name, key)) + if err then + return nil, err + end + return res +end + + +local function DELETE(self, dshmc, name, key, current_time, metadata) + local key_name = get_name(self, name, key) + local ok, err = dshmc:delete(key_name) + + if not metadata then + return ok, err + end + + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = get_name(self, name, audiences[i], subjects[i]) + update_meta(dshmc, meta_key, key, 0, current_time) + end + + return ok, err +end + + +local function exec(self, func, ...) + local dshmc = dshm:new() + local connect_timeout = self.connect_timeout + local send_timeout = self.send_timeout + local read_timeout = self.read_timeout + if connect_timeout or send_timeout or read_timeout then + dshmc.sock:settimeouts(connect_timeout, send_timeout, read_timeout) + end + + local ok, err = dshmc:connect(self.host, self.port, self.options) + if not ok then + return nil, err + end + + if self.ssl and dshmc:get_reused_times() == 0 then + ok, err = dshmc.sock:sslhandshake(false, self.server_name, self.ssl_verify) + if not ok then + dshmc:close() + return nil, err + end + end + + ok, err = func(self, dshmc, ...) + if err then + dshmc:close() + return nil, err + end + + if not dshmc:set_keepalive(self.keepalive_timeout) then + dshmc:close() + end + + if ok == null then + ok = nil + end + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(...) + return exec(self, SET, ...) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(...) + return exec(self, GET, ...) +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(...) + return exec(self, DELETE, ...) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(...) + return exec(self, READ_METADATA, ...) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Distributed shared memory storage backend configuration +-- @field prefix The prefix for the keys stored in DSHM. +-- @field suffix The suffix for the keys stored in DSHM. +-- @field host The host to connect (defaults to `"127.0.0.1"`). +-- @field port The port to connect (defaults to `4321`). +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSL (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @field server_name The server name for the new TLS extension Server Name Indication (SNI). +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a distributed shared memory storage. +-- +-- This creates a new distributed shared memory storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration DSHM storage @{configuration} +-- @treturn table DSHM storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + + local host = configuration and configuration.host or DEFAULT_HOST + local port = configuration and configuration.port or DEFAULT_PORT + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + local server_name = configuration and configuration.server_name + + if pool or pool_size or backlog then + setmetatable({ + prefix = prefix, + suffix = suffix, + host = host, + port = port, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + options = { + pool = pool, + pool_size = pool_size, + backlog = backlog, + } + }, metatable) + end + + return setmetatable({ + prefix = prefix, + suffix = suffix, + host = host, + port = port, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/encoders/base16.lua b/lib/resty/session/encoders/base16.lua deleted file mode 100644 index 552f50eb..00000000 --- a/lib/resty/session/encoders/base16.lua +++ /dev/null @@ -1,29 +0,0 @@ -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/lib/resty/session/encoders/base64.lua b/lib/resty/session/encoders/base64.lua deleted file mode 100644 index ddaf4e82..00000000 --- a/lib/resty/session/encoders/base64.lua +++ /dev/null @@ -1,39 +0,0 @@ -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/lib/resty/session/encoders/hex.lua b/lib/resty/session/encoders/hex.lua deleted file mode 100644 index 1b94a5ac..00000000 --- a/lib/resty/session/encoders/hex.lua +++ /dev/null @@ -1 +0,0 @@ -return require "resty.session.encoders.base16" \ No newline at end of file diff --git a/lib/resty/session/file.lua b/lib/resty/session/file.lua new file mode 100644 index 00000000..414447d4 --- /dev/null +++ b/lib/resty/session/file.lua @@ -0,0 +1,177 @@ +--- +-- File storage backend for session library. +-- +-- @module resty.session.file + + +local file_utils = require "resty.session.file.utils" + + +local run_worker_thread = file_utils.run_worker_thread + + +local setmetatable = setmetatable +local error = error +local byte = string.byte + + +local SLASH_BYTE = byte("/") + + +local THREAD_MODULE = "resty.session.file.thread" + + +local DEFAULT_POOL = "default" +local DEFAULT_PATH do + local path = os.tmpname() + local pos + for i = #path, 1, -1 do + if byte(path, i) == SLASH_BYTE then + pos = i + break + end + end + + DEFAULT_PATH = path:sub(1, pos) +end + + +local function run_thread(self, func, ...) + local ok, res, err = run_worker_thread(self.pool, THREAD_MODULE, func, self.path, self.prefix, self.suffix, ...) + if not ok then + return nil, res + end + + return res, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(...) + return run_thread(self, "set", ...) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(...) + return run_thread(self, "get", ...) +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(...) + return run_thread(self, "delete", ...) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(...) + return run_thread(self, "read_metadata", ...) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- File storage backend configuration +-- @field prefix File prefix for session file. +-- @field suffix File suffix (or extension without `.`) for session file. +-- @field pool Name of the thread pool under which file writing happens (available on Linux only). +-- @field path Path (or directory) under which session files are created. +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a file storage. +-- +-- This creates a new file storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration file storage @{configuration} +-- @treturn table file storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + + local pool = configuration and configuration.pool or DEFAULT_POOL + local path = configuration and configuration.path or DEFAULT_PATH + + if byte(path, -1) ~= SLASH_BYTE then + path = path .. "/" + end + + return setmetatable({ + prefix = prefix ~= "" and prefix or nil, + suffix = suffix ~= "" and suffix or nil, + pool = pool, + path = path, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/file/thread.lua b/lib/resty/session/file/thread.lua new file mode 100644 index 00000000..127a6f99 --- /dev/null +++ b/lib/resty/session/file/thread.lua @@ -0,0 +1,209 @@ +--- +-- File storage backend worker thread module +-- +-- @module resty.session.file.thread + + +local file_utils = require "resty.session.file.utils" +local utils = require "resty.session.utils" + + +local get_modification = file_utils.get_modification +local meta_get_key = file_utils.meta_get_key +local file_create = file_utils.file_create +local file_append = file_utils.file_append +local file_delete = file_utils.file_delete +local file_touch = file_utils.file_touch +local file_read = file_utils.file_read +local get_path = file_utils.get_path +local cleanup = file_utils.cleanup + + +local meta_get_latest = utils.meta_get_latest +local meta_get_value = utils.meta_get_value + + +local max = math.max + + +local function update_meta(path, prefix, suffix, name, meta_key, key, exp) + local meta_value = meta_get_value(key, exp) + if not meta_value then + return + end + + local file_path = get_path(path, prefix, suffix, name, meta_key) + file_append(file_path, meta_value) + local current_expiry = get_modification(file_path) or 0 + + local new_expiry = max(current_expiry, exp) + file_touch(file_path, new_expiry) +end + + +--- +-- Store session data. +-- +-- @function module.set +-- @tparam string path the path where sessions are stored +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name the cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam table remember whether storing persistent session or not +-- @treturn table|nil session metadata +-- @treturn string error message +local function set(path, prefix, suffix, name, key, value, ttl, + current_time, old_key, stale_ttl, metadata, remember) + local file_path = get_path(path, prefix, suffix, name, key) + if not metadata and not old_key then + local ok, err = file_create(file_path, value) + + if ok and current_time and ttl then + file_touch(file_path, current_time + ttl) + end + + cleanup(path, prefix, suffix, name, current_time) + + return ok, err + end + + local old_ttl, old_file_path + if old_key then + old_file_path = get_path(path, prefix, suffix, name, old_key) + if not remember then + local exp = get_modification(old_file_path) + if exp then + old_ttl = exp - current_time + end + end + end + + local ok, err = file_create(file_path, value) + if ok and current_time and ttl then + file_touch(file_path, current_time + ttl) + end + + if old_file_path then + if remember then + file_delete(old_file_path) + + elseif not old_ttl or old_ttl > stale_ttl then + file_touch(old_file_path, current_time + stale_ttl) + end + end + + if metadata then + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = meta_get_key(audiences[i], subjects[i]) + update_meta(path, prefix, suffix, name, meta_key, key, current_time + ttl) + + if old_key then + update_meta(path, prefix, suffix, name, meta_key, old_key, 0) + end + end + end + + cleanup(path, prefix, suffix, name, current_time) + + return ok, err +end + + +--- +-- Retrieve session data. +-- +-- @function module.GET +-- @tparam string path the path where sessions are stored +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +local function get(path, prefix, suffix, name, key, current_time) + local file_path = get_path(path, prefix, suffix, name, key) + + -- 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" + end + + return file_read(file_path) +end + + +--- +-- Delete session data. +-- +-- @function module.delete +-- @tparam string path the path where sessions are stored +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name the cookie name +-- @tparam string key session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +local function delete(path, prefix, suffix, name, key, current_time, metadata) + local file_path = get_path(path, prefix, suffix, name, key) + file_delete(file_path) + + if metadata then + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = meta_get_key(audiences[i], subjects[i]) + update_meta(path, prefix, suffix, name, meta_key, key, 0) + end + end + + cleanup(path, prefix, suffix, name, current_time) + + return true +end + + +--- +-- Read session metadata. +-- +-- @function module.read_metadata +-- @tparam string path the path where sessions are stored +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name the cookie name +-- @tparam string audience session audience +-- @tparam string subject session subject +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +local function read_metadata(path, prefix, suffix, name, audience, subject, current_time) + local meta_key = meta_get_key(audience, subject) + local file_path = get_path(path, prefix, suffix, name, meta_key) + local metadata, err = file_read(file_path) + if not metadata then + return nil, err + end + + return meta_get_latest(metadata, current_time) +end + + +return { + set = set, + get = get, + delete = delete, + read_metadata = read_metadata, +} diff --git a/lib/resty/session/file/utils.lua b/lib/resty/session/file/utils.lua new file mode 100644 index 00000000..af4a12c0 --- /dev/null +++ b/lib/resty/session/file/utils.lua @@ -0,0 +1,290 @@ +--- +-- File storage utilities +-- +-- @module resty.session.file.utils + + +local lfs = require "lfs" + + +local attributes = lfs.attributes +local touch = lfs.touch +local dir = lfs.dir + + + +local file_delete = os.remove +local random = math.random +local pcall = pcall +local open = io.open +local fmt = string.format + + +local CLEANUP_PROBABILITY = 0.0005 -- 1 / 2000 + + +local run_worker_thread do + run_worker_thread = ngx.run_worker_thread -- luacheck: ignore + if not run_worker_thread then + local require = require + run_worker_thread = function(_, module, func, ...) + local m = require(module) + return pcall(m[func], ...) + end + end +end + + +local function file_touch(path, mtime) + return touch(path, nil, mtime) +end + + +--- +-- Store data in file. +-- +-- @function file_create +-- @tparam string path file path +-- @tparam string content file content +-- @treturn true|nil ok +-- @treturn string error message +local function file_create(path, content) + local file, err = open(path, "wb") + if not file then + return nil, err + end + + local ok, err = file:write(content) + + file:close() + + if not ok then + file_delete(path) + return nil, err + end + + return true +end + + +--- +-- Append data in file. +-- +-- @function file_append +-- @tparam string path file path +-- @tparam string data file data +-- @treturn true|nil ok +-- @treturn string error message +local function file_append(path, data) + local file, err = open(path, "a") + if not file then + return nil, err + end + + local ok, err = file:write(data) + + file:close() + + if not ok then + file_delete(path) + return nil, err + end + + return true +end + + +--- +-- Read data from a file. +-- +-- @function file_read +-- @tparam string path file to read +-- @treturn string|nil content +-- @treturn string error message +local function file_read(path) + local file, err = open(path, "rb") + if not file then + return nil, err + end + + local content, err = file:read("*a") + + file:close() + + if not content then + return nil, err + end + + return content +end + + +--- +-- Generate the path for a file to be stored at. +-- +-- @tparam string path the path where sessions are stored +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name the cookie name +-- @tparam string key session key +-- @treturn string path +local function get_path(path, prefix, suffix, name, key) + if prefix and suffix then + return fmt("%s%s_%s_%s.%s", path, prefix, name, key, suffix) + elseif prefix then + return fmt("%s%s_%s_%s", path, prefix, name, key) + elseif suffix then + return fmt("%s%s_%s.%s", path, name, key, suffix) + else + return fmt("%s%s_%s", path, name, key) + end +end + + +--- +-- Get the value modification time of a file. +-- +-- @function utils.get_modification +-- @tparam string path the path to the file +local function get_modification(path) + local attr = attributes(path) + if not attr or attr.mode ~= "file" then + return + end + + return attr.modification or nil +end + + +--- +-- Given an audience and a subject, generate a metadata key. +-- +-- @function utils.meta_get_key +-- @tparam string audience session audience +-- @tparam string subject session subject +-- @treturn string metadata key +local function meta_get_key(audience, subject) + return fmt("%s:%s", audience, subject) +end + + +--- +-- Validate a file name. +-- Run a few checks to try to determine if the file is managed by this library +-- +-- @function utils.validate_file_name +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name cookie name +-- @tparam string filename the name of the file +-- @treturn true|false whether the file is managed by the library or not +local validate_file_name do + local byte = string.byte + local sub = string.sub + local find = string.find + + local UNDERSCORE = byte("_") + local DOT = byte(".") + + validate_file_name = function(prefix, suffix, name, filename) + if filename == "." or filename == ".." then + return false + end + + local plen = 0 + if prefix then + plen = #prefix + if byte(filename, plen + 1) ~= UNDERSCORE or + (plen > 0 and sub(filename, 1, plen) ~= prefix) then + return false + end + end + + local slen = 0 + if suffix then + slen = #suffix + + if byte(filename, -1 - slen) ~= DOT or + (slen > 0 and sub(filename, -slen) ~= suffix) + then + return false + end + end + + local nlen = #name + local name_start = plen == 0 and 1 or plen + 2 + local name_end = name_start + nlen - 1 + + if byte(filename, name_end + 1) ~= UNDERSCORE or + sub(filename, name_start, name_end) ~= name + then + return false + end + + local rest + if slen == 0 then + rest = sub(filename, name_end + 2) + else + rest = sub(filename, name_end + 2, -2 - slen) + end + + local rlen = #rest + if rlen < 3 then + return false + end + + if rlen ~= 43 then + local colon_pos = find(rest, ":", 2, true) + if not colon_pos or colon_pos == 43 then + return false + end + end + + return true + end +end + + +--- +-- Clean up expired session and metadata files. +-- +-- @function utils.cleanup +-- @tparam string path the path where sessions are stored +-- @tparam string prefix the prefix for session files +-- @tparam string suffix the suffix for session files +-- @tparam string name cookie name +-- @tparam number current_time current time +-- @treturn true|false whether clean up completed +local function cleanup(path, prefix, suffix, name, current_time) + if random() > CLEANUP_PROBABILITY then + return false + end + + local deleted = 0 + + for file in dir(path) do + if validate_file_name(prefix, suffix, name, file) then + local exp = get_modification(path .. file) + if exp and exp < current_time then + file_delete(path .. file) + deleted = deleted + 1 + end + end + end + return true +end + + +return { + validate_file_name = validate_file_name, + run_worker_thread = run_worker_thread, + get_modification = get_modification, + meta_get_key = meta_get_key, + file_create = file_create, + file_append = file_append, + file_delete = file_delete, + file_touch = file_touch, + file_read = file_read, + get_path = get_path, + cleanup = cleanup, +} diff --git a/lib/resty/session/hmac/sha1.lua b/lib/resty/session/hmac/sha1.lua deleted file mode 100644 index 1753412b..00000000 --- a/lib/resty/session/hmac/sha1.lua +++ /dev/null @@ -1 +0,0 @@ -return ngx.hmac_sha1 diff --git a/lib/resty/session/identifiers/random.lua b/lib/resty/session/identifiers/random.lua deleted file mode 100644 index a2f97392..00000000 --- a/lib/resty/session/identifiers/random.lua +++ /dev/null @@ -1,13 +0,0 @@ -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/lib/resty/session/memcached.lua b/lib/resty/session/memcached.lua new file mode 100644 index 00000000..ab82a329 --- /dev/null +++ b/lib/resty/session/memcached.lua @@ -0,0 +1,395 @@ +--- +-- Memcached backend for session library +-- +-- @module resty.session.memcached + + +local memcached = require "resty.memcached" +local buffer = require "string.buffer" +local utils = require "resty.session.utils" + + +local meta_get_latest = utils.meta_get_latest +local meta_get_value = utils.meta_get_value +local get_name = utils.get_name +local errmsg = utils.errmsg + + +local setmetatable = setmetatable +local random = math.random +local error = error +local pairs = pairs +local null = ngx.null +local log = ngx.log +local max = math.max + + +local WARN = ngx.WARN + + +local CLEANUP_PROBABILITY = 0.1 -- 1 / 10 +local SESSIONS_BUFFER = buffer.new(128) + + +local function cleanup(memc, meta_key, current_time) + local res, _, cas_unique, err = memc:gets(meta_key) + if not res then + return nil, err + end + + local sessions = meta_get_latest(res, current_time) + + SESSIONS_BUFFER:reset() + + local max_expiry = current_time + for key, exp in pairs(sessions) do + SESSIONS_BUFFER:put(meta_get_value(key, exp)) + max_expiry = max(max_expiry, exp) + end + + local exp = max_expiry - current_time + if exp > 0 then + return memc:cas(meta_key, SESSIONS_BUFFER:get(), cas_unique, exp) + end + + return memc:delete(meta_key) +end + + +local function read_metadata(self, memc, name, audience, subject, current_time) + local meta_key = get_name(self, name, audience, subject) + local res, _, err = memc:get(meta_key) + if not res then + return nil, err + end + + return meta_get_latest(res, current_time) +end + + +-- TODO possible improvement: when available in the lib, use pipelines +local function SET(self, memc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + local inferred_key = get_name(self, name, key) + + if not metadata and not old_key then + return memc:set(inferred_key, value, ttl) + end + + local ok, err = memc:set(inferred_key, value, ttl) + if not ok then + return nil, err + end + + local old_name = old_key and get_name(self, name, old_key) + if old_name then + if remember then + memc:delete(old_name) + else + memc:touch(old_name, stale_ttl) + end + end + + if not metadata then + return true + end + + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = get_name(self, name, audiences[i], subjects[i]) + local meta_value = meta_get_value(key, current_time + ttl) + + local added, err = memc:add(meta_key, meta_value) + if not added then + local appended, err2 = memc:append(meta_key, meta_value) + if not appended then + log(WARN, "[session] ", errmsg(err2 or err, "failed to store metadata")) + end + end + + if old_key then + meta_value = meta_get_value(old_key, 0) + local ok, err = memc:append(meta_key, meta_value) + if not ok then + log(WARN, "[session] ", errmsg(err, "failed to update metadata")) + end + end + + -- no need to clean up every time we write + -- it is just beneficial when a key is used a lot + if random() < CLEANUP_PROBABILITY then + cleanup(memc, meta_key, current_time) + end + end + + return true +end + + +local function GET(self, memc, name, key) + local res, _, err = memc:get(get_name(self, name, key)) + if err then + return nil, err + end + return res +end + + +local function DELETE(self, memc, name, key, current_time, metadata) + local key_name = get_name(self, name, key) + local ok, err = memc:delete(key_name) + if not metadata then + return ok, err + end + + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = get_name(self, name, audiences[i], subjects[i]) + local meta_value = meta_get_value(key, 0) + local ok, err = memc:append(meta_key, meta_value) + if not ok and err ~= "NOT_STORED" then + log(WARN, "[session] ", errmsg(err, "failed to update metadata")) + end + + cleanup(memc, meta_key, current_time) + end + + return ok, err +end + + +local DEFAULT_HOST = "127.0.0.1" +local DEFAULT_PORT = 11211 + + +local function exec(self, func, ...) + local memc = memcached:new() + + local connect_timeout = self.connect_timeout + local send_timeout = self.send_timeout + local read_timeout = self.read_timeout + if connect_timeout or send_timeout or read_timeout then + memc:set_timeouts(connect_timeout, send_timeout, read_timeout) + end + + local ok, err do + local socket = self.socket + if socket then + ok, err = memc:connect(socket, self.options) + else + ok, err = memc:connect(self.host, self.port, self.options) + end + end + if not ok then + return nil, err + end + + if self.ssl and memc:get_reused_times() == 0 then + ok, err = memc:sslhandshake(false, self.server_name, self.ssl_verify) + if not ok then + memc:close() + return nil, err + end + end + + ok, err = func(self, memc, ...) + + if err then + memc:close() + return nil, err + end + + if not memc:set_keepalive(self.keepalive_timeout) then + memc:close() + end + + if ok == null then + ok = nil + end + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(...) + return exec(self, SET, ...) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(...) + return exec(self, GET, ...) +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(...) + return exec(self, DELETE, ...) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(...) + return exec(self, read_metadata, ...) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Distributed shared memory storage backend configuration +-- @field prefix Prefix for the keys stored in memcached. +-- @field suffix Suffix for the keys stored in memcached. +-- @field host The host to connect (defaults to `"127.0.0.1"`). +-- @field port The port to connect (defaults to `11211`). +-- @field socket The socket file to connect to (defaults to `nil`). +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSL (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @field server_name The server name for the new TLS extension Server Name Indication (SNI). +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a memcached storage. +-- +-- This creates a new memcached storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration memcached storage @{configuration} +-- @treturn table memcached storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + + local host = configuration and configuration.host or DEFAULT_HOST + local port = configuration and configuration.port or DEFAULT_PORT + local socket = configuration and configuration.socket + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + local server_name = configuration and configuration.server_name + + if pool or pool_size or backlog then + setmetatable({ + prefix = prefix, + suffix = suffix, + host = host, + port = port, + socket = socket, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + options = { + pool = pool, + pool_size = pool_size, + backlog = backlog, + } + }, metatable) + end + + return setmetatable({ + prefix = prefix, + suffix = suffix, + host = host, + port = port, + socket = socket, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/mysql.lua b/lib/resty/session/mysql.lua new file mode 100644 index 00000000..33a20290 --- /dev/null +++ b/lib/resty/session/mysql.lua @@ -0,0 +1,379 @@ +--- +-- MySQL / MariaDB backend for session library +-- +-- @module resty.session.mysql + + +--- +-- Database +-- @section database + + +--- +-- Sessions table. +-- +-- Database table that stores session data. +-- +-- @usage +-- CREATE TABLE IF NOT EXISTS sessions ( +-- sid CHAR(43) PRIMARY KEY, +-- name VARCHAR(255), +-- data MEDIUMTEXT, +-- exp DATETIME, +-- INDEX (exp) +-- ) CHARACTER SET ascii; +-- @table sessions + + +--- +-- Sessions metadata table. +-- +-- This is only needed if you want to store session metadata. +-- +-- @usage +-- CREATE TABLE IF NOT EXISTS sessions_meta ( +-- aud VARCHAR(255), +-- sub VARCHAR(255), +-- sid CHAR(43), +-- PRIMARY KEY (aud, sub, sid), +-- CONSTRAINT FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE ON UPDATE CASCADE +-- ) CHARACTER SET ascii; +-- @table metadata + + +local buffer = require "string.buffer" +local mysql = require "resty.mysql" + + +local setmetatable = setmetatable +local random = math.random +local ipairs = ipairs +local error = error +local fmt = string.format + + +local DEFAULT_HOST = "127.0.0.1" +local DEFAULT_PORT = 3306 +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_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" +local GET_META = "SELECT sid, exp FROM %s JOIN %s USING (sid) WHERE aud = '%s' AND sub = '%s' AND exp >= FROM_UNIXTIME(%d)" +local GET = "SELECT data FROM %s WHERE sid = '%s' AND exp >= FROM_UNIXTIME(%d)" +local EXPIRE = "UPDATE %s SET exp = FROM_UNIXTIME(%d) WHERE sid = '%s' AND exp > FROM_UNIXTIME(%d)" +local DELETE = "DELETE FROM %s WHERE sid = '%s'" +local CLEANUP = "DELETE FROM %s WHERE exp < FROM_UNIXTIME(%d)" + + +local SQL = buffer.new() +local STM_DELIM = ";\n" +local VAL_DELIM = ", " + + +local CLEANUP_PROBABILITY = 0.001 -- 1 / 1000 + + +local function exec(self, query) + local my = mysql:new() + + local connect_timeout = self.connect_timeout + local send_timeout = self.send_timeout + local read_timeout = self.read_timeout + if connect_timeout or send_timeout or read_timeout then + if my.sock and my.sock.settimeouts then + my.sock:settimeouts(connect_timeout, send_timeout, read_timeout) + else + my:set_timeout(connect_timeout) + end + end + + local ok, err = my:connect(self.options) + if not ok then + return nil, err + end + + ok, err = my:query(query) + + if not my:set_keepalive(self.keepalive_timeout) then + my:close() + end + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + local table = self.table + local exp = ttl + current_time + + if not metadata and not old_key then + return exec(self, fmt(SET, table, key, name, value, exp)) + end + + SQL:reset():putf(SET, table, key, name, value, exp) + + if old_key then + if remember then + SQL:put(STM_DELIM):putf(DELETE, table, old_key) + else + local stale_exp = stale_ttl + current_time + SQL:put(STM_DELIM):putf(EXPIRE, table, stale_exp, old_key, stale_exp) + end + end + + local table_meta = self.table_meta + if metadata then + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + + SQL:put(STM_DELIM):putf(SET_META_PREFIX, table_meta) + + for i = 1, count do + if i > 1 then + SQL:put(VAL_DELIM) + end + SQL:putf(SET_META_VALUES, audiences[i], subjects[i], key) + end + + SQL:putf(SET_META_SUFFIX) + end + + if random() < CLEANUP_PROBABILITY then + SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) + end + + return exec(self, SQL:get()) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(name, key, current_time) -- luacheck: ignore + local res, err = exec(self, fmt(GET, self.table, key, current_time)) + if not res then + return nil, err + end + + local row = res[1] + if not row then + return nil, "session not found" + end + + local data = row.data + if not row.data then + return nil, "session not found" + end + + return data +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(name, key, current_time, metadata) -- luacheck: ignore + SQL:reset():putf(DELETE, self.table, key) + + if random() < CLEANUP_PROBABILITY then + SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) + end + + return exec(self, SQL:get()) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(name, audience, subject, current_time) -- luacheck: ignore + local res = {} + local t = exec(self, fmt(GET_META, self.table_meta, self.table, audience, subject, current_time)) + if not t then + return nil, "not found" + end + + for _, v in ipairs(t) do + local key = v.sid + if key then + res[key] = v.exp + end + end + + return res +end + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Postgres storage backend configuration +-- @field host The host to connect (defaults to `"127.0.0.1"`). +-- @field port The port to connect (defaults to `3306`). +-- @field socket The socket file to connect to (defaults to `nil`). +-- @field username The database username to authenticate (defaults to `nil`). +-- @field password Password for authentication, may be required depending on server configuration. +-- @field charset The character set used on the MySQL connection (defaults to `"ascii"`). +-- @field database The database name to connect. +-- @field table_name Name of database table to which to store session data (defaults to `"sessions"`). +-- @field table_name_meta Name of database meta data table to which to store session meta data (defaults to `"sessions_meta"`). +-- @field max_packet_size The upper limit for the reply packets sent from the MySQL server (defaults to 1 MB). +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSL (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a MySQL / MariaDB storage. +-- +-- This creates a new MySQL / MariaDB storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration mysql/mariadb storage @{configuration} +-- @treturn table mysql/mariadb storage instance +function storage.new(configuration) + local host = configuration and configuration.host or DEFAULT_HOST + local port = configuration and configuration.port or DEFAULT_PORT + local socket = configuration and configuration.socket + + local username = configuration and configuration.username + local password = configuration and configuration.password + local charset = configuration and configuration.charset or DEFAULT_CHARSET + local database = configuration and configuration.database + local max_packet_size = configuration and configuration.max_packet_size + + local table_name = configuration and configuration.table or DEFAULT_TABLE + local table_name_meta = configuration and configuration.table_meta + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + + if socket then + return setmetatable({ + table = table_name, + table_meta = table_name_meta or (table_name .. "_meta"), + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + options = { + path = socket, + user = username, + password = password, + charset = charset, + database = database, + max_packet_size = max_packet_size, + pool = pool, + pool_size = pool_size, + backlog = backlog, + ssl = ssl, + ssl_verify = ssl_verify, + } + }, metatable) + end + + return setmetatable({ + table = table_name, + table_meta = table_name_meta or (table_name .. "_meta"), + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + options = { + host = host, + port = port, + user = username, + password = password, + charset = charset, + database = database, + max_packet_size = max_packet_size, + pool = pool, + pool_size = pool_size, + backlog = backlog, + ssl = ssl, + ssl_verify = ssl_verify, + } + }, metatable) +end + + +return storage diff --git a/lib/resty/session/postgres.lua b/lib/resty/session/postgres.lua new file mode 100644 index 00000000..25e60392 --- /dev/null +++ b/lib/resty/session/postgres.lua @@ -0,0 +1,354 @@ +--- +-- Postgres backend for session library. +-- +-- @module resty.session.postgres + + +--- +-- Database +-- @section database + + +--- +-- Sessions table. +-- +-- Database table that stores session data. +-- +-- @usage +-- CREATE TABLE IF NOT EXISTS sessions ( +-- sid TEXT PRIMARY KEY, +-- name TEXT, +-- data TEXT, +-- exp TIMESTAMP WITH TIME ZONE +-- ); +-- CREATE INDEX ON sessions (exp); +-- @table sessions + + +--- +-- Sessions metadata table. +-- +-- This is only needed if you want to store session metadata. +-- +-- @usage +-- CREATE TABLE IF NOT EXISTS sessions_meta ( +-- aud TEXT, +-- sub TEXT, +-- sid TEXT REFERENCES sessions (sid) ON DELETE CASCADE ON UPDATE CASCADE, +-- PRIMARY KEY (aud, sub, sid) +-- ); +-- @table metadata + + +local buffer = require "string.buffer" +local pgmoon = require "pgmoon" + + +local setmetatable = setmetatable +local random = math.random +local ipairs = ipairs +local error = error +local fmt = string.format + + +local DEFAULT_HOST = "127.0.0.1" +local DEFAULT_PORT = 5432 +local DEFAULT_TABLE = "sessions" + + +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" +local SET_META_PREFIX = "INSERT INTO %s (aud, sub, sid) VALUES " +local SET_META_VALUES = "('%s', '%s', '%s')" +local SET_META_SUFFIX = " ON CONFLICT DO NOTHING" +local GET_META = "SELECT sid, exp FROM %s JOIN %s USING (sid) WHERE aud = '%s' AND sub = '%s' AND exp >= TO_TIMESTAMP(%d)" +local GET = "SELECT data FROM %s WHERE sid = '%s' AND exp >= TO_TIMESTAMP(%d) AT TIME ZONE 'UTC'" +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'" +local DELETE = "DELETE FROM %s WHERE sid = '%s'" +local CLEANUP = "DELETE FROM %s WHERE exp < TO_TIMESTAMP(%d)" + + +local SQL = buffer.new() +local STM_DELIM = ";\n" +local VAL_DELIM = ", " + + +local CLEANUP_PROBABILITY = 0.001 -- 1 / 1000 + + +local function exec(self, query) + local pg = pgmoon.new(self.options) + + local connect_timeout = self.connect_timeout + local send_timeout = self.send_timeout + local read_timeout = self.read_timeout + if connect_timeout or send_timeout or read_timeout then + if pg.sock and pg.sock.settimeouts then + pg.sock:settimeouts(connect_timeout, send_timeout, read_timeout) + else + pg:settimeout(connect_timeout) + end + end + + local ok, err = pg:connect() + if not ok then + return nil, err + end + + ok, err = pg:query(query) + + if not pg:keepalive(self.keepalive_timeout) then + pg:close() + end + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + local table = self.table + local exp = ttl + current_time + + if not metadata and not old_key then + return exec(self, fmt(SET, table, key, name, value, exp)) + end + + SQL:reset():putf(SET, table, key, name, value, exp) + + if old_key then + if remember then + SQL:put(STM_DELIM):putf(DELETE, table, old_key) + else + local stale_exp = stale_ttl + current_time + SQL:put(STM_DELIM):putf(EXPIRE, table, stale_exp, old_key, stale_exp) + end + end + + local table_meta = self.table_meta + if metadata then + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + + SQL:put(STM_DELIM):putf(SET_META_PREFIX, table_meta) + + for i = 1, count do + if i > 1 then + SQL:put(VAL_DELIM) + end + SQL:putf(SET_META_VALUES, audiences[i], subjects[i], key) + end + + SQL:putf(SET_META_SUFFIX) + end + + if random() < CLEANUP_PROBABILITY then + SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) + end + + return exec(self, SQL:get()) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(name, key, current_time) -- luacheck: ignore + local res, err = exec(self, fmt(GET, self.table, key, current_time)) + if not res then + return nil, err + end + + local row = res[1] + if not row then + return nil + end + + local data = row.data + if not row.data then + return nil + end + + return data +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(name, key, current_time, metadata) -- luacheck: ignore + SQL:reset():putf(DELETE, self.table, key) + + if random() < CLEANUP_PROBABILITY then + SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) + end + + return exec(self, SQL:get()) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(name, audience, subject, current_time) -- luacheck: ignore + local res = {} + + local t = exec(self, fmt(GET_META, self.table_meta, self.table, audience, subject, current_time)) + if not t then + return nil, "not found" + end + + for _, v in ipairs(t) do + local key = v.sid + if key then + res[key] = v.exp + end + end + + return res +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Postgres storage backend configuration +-- @field host The host to connect (defaults to `"127.0.0.1"`). +-- @field port The port to connect (defaults to `5432`). +-- @field application Set the name of the connection as displayed in pg_stat_activity (defaults to `"pgmoon"`). +-- @field username The database username to authenticate (defaults to `"postgres"`). +-- @field password Password for authentication, may be required depending on server configuration. +-- @field database The database name to connect. +-- @field table_name Name of database table to which to store session data (can be `database schema` prefixed) (defaults to `"sessions"`). +-- @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"`). +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSL (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @field ssl_required Abort the connection if the server does not support SSL connections (defaults to `nil`). +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a Postgres storage. +-- +-- This creates a new Postgres storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration postgres storage @{configuration} +-- @treturn table postgres storage instance +function storage.new(configuration) + local host = configuration and configuration.host or DEFAULT_HOST + local port = configuration and configuration.port or DEFAULT_PORT + + local application = configuration and configuration.application + local username = configuration and configuration.username + local password = configuration and configuration.password + local database = configuration and configuration.database + + local table_name = configuration and configuration.table or DEFAULT_TABLE + local table_name_meta = configuration and configuration.table_meta + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + local ssl_required = configuration and configuration.ssl_required + + return setmetatable({ + table = table_name, + table_meta = table_name_meta or (table_name .. "_meta"), + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + options = { + host = host, + port = port, + application_name = application, + user = username, + password = password, + database = database, + socket_type = "nginx", + pool = pool, + pool_size = pool_size, + backlog = backlog, + ssl = ssl, + ssl_verify = ssl_verify, + ssl_required = ssl_required, + } + }, metatable) +end + + +return storage diff --git a/lib/resty/session/redis.lua b/lib/resty/session/redis.lua new file mode 100644 index 00000000..3d39b2c1 --- /dev/null +++ b/lib/resty/session/redis.lua @@ -0,0 +1,279 @@ +--- +-- Redis backend for session library +-- +-- @module resty.session.redis + + +local common = require "resty.session.redis.common" +local redis = require "resty.redis" + + +local setmetatable = setmetatable +local error = error +local null = ngx.null + + +local DEFAULT_HOST = "127.0.0.1" +local DEFAULT_PORT = 6379 + + +local SET = common.SET +local GET = common.GET +local UNLINK = common.UNLINK +local READ_METADATA = common.READ_METADATA + + +local function exec(self, func, ...) + local red = redis:new() + + local connect_timeout = self.connect_timeout + local send_timeout = self.send_timeout + local read_timeout = self.read_timeout + if connect_timeout or send_timeout or read_timeout then + red:set_timeouts(connect_timeout, send_timeout, read_timeout) + end + + local ok, err do + local socket = self.socket + if socket then + ok, err = red:connect(socket, self.options) + else + ok, err = red:connect(self.host, self.port, self.options) + end + end + if not ok then + return nil, err + end + + if red:getreusedtimes() == 0 then + local password = self.password + if password then + local username = self.username + if username then + ok, err = red:auth(username, password) + else + ok, err = red:auth(password) + end + + if not ok then + red:close() + return nil, err + end + end + end + + local database = self.database + if database then + ok, err = red:select(database) + if not ok then + return nil, err + end + end + + ok, err = func(self, red, ...) + if err then + red:close() + return nil, err + end + + if not red:set_keepalive(self.keepalive_timeout) then + red:close() + end + + if ok == null then + ok = nil + end + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(...) + return exec(self, SET, ...) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(...) + return exec(self, GET, ...) +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(...) + return exec(self, UNLINK, ...) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(...) + return exec(self, READ_METADATA, ...) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Redis storage backend configuration +-- @field prefix Prefix for the keys stored in Redis. +-- @field suffix Suffix for the keys stored in Redis. +-- @field host The host to connect (defaults to `"127.0.0.1"`). +-- @field port The port to connect (defaults to `6379`). +-- @field socket The socket file to connect to (defaults to `nil`). +-- @field username The database username to authenticate. +-- @field password Password for authentication. +-- @field database The database to connect. +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSL (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @field server_name The server name for the new TLS extension Server Name Indication (SNI). +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a Redis storage. +-- +-- This creates a new Redis storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration redis storage @{configuration} +-- @treturn table redis storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + + local host = configuration and configuration.host or DEFAULT_HOST + local port = configuration and configuration.port or DEFAULT_PORT + local socket = configuration and configuration.socket + + local username = configuration and configuration.username + local password = configuration and configuration.password + local database = configuration and configuration.database + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + local server_name = configuration and configuration.server_name + + if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then + return setmetatable({ + prefix = prefix, + suffix = suffix, + host = host, + port = port, + socket = socket, + username = username, + password = password, + database = database, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + options = { + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + pool = pool, + pool_size = pool_size, + backlog = backlog, + } + }, metatable) + end + + return setmetatable({ + prefix = prefix, + suffix = suffix, + host = host, + port = port, + socket = socket, + username = username, + password = password, + database = database, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/redis/cluster.lua b/lib/resty/session/redis/cluster.lua new file mode 100644 index 00000000..adc0daac --- /dev/null +++ b/lib/resty/session/redis/cluster.lua @@ -0,0 +1,271 @@ +--- +-- Redis Cluster backend for session library +-- +-- @module resty.session.redis.cluster + + +local common = require "resty.session.redis.common" +local redis = require "resty.rediscluster" + + +local setmetatable = setmetatable +local error = error +local null = ngx.null + + +local SET = common.SET +local GET = common.GET +local UNLINK = common.UNLINK +local READ_METADATA = common.READ_METADATA + + +local function exec(self, func, ...) + local red, err = redis:new(self.options) + if err then + return nil, err + end + + local ok, err = func(self, red, ...) + if err then + red:close() + return nil, err + end + + if ok == null then + ok = nil + end + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(...) + return exec(self, SET, ...) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(...) + return exec(self, GET, ...) +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(...) + return exec(self, UNLINK, ...) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(...) + return exec(self, READ_METADATA, ...) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Redis Cluster storage backend configuration +-- @field prefix Prefix for the keys stored in redis. +-- @field suffix Suffix for the keys stored in redis. +-- @field name Redis cluster name. +-- @field nodes Redis cluster nodes. +-- @field lock_zone Shared dictionary name for locks. +-- @field lock_prefix Shared dictionary name prefix for lock. +-- @field max_redirections Maximum retry attempts for redirection. +-- @field max_connection_attempts Maximum retry attempts for connection. +-- @field max_connection_timeout Maximum connection timeout in total among the retries. +-- @field username The database username to authenticate. +-- @field password Password for authentication. +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout controls The default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout controls The default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSL (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @field server_name The server name for the new TLS extension Server Name Indication (SNI). +-- @table configuration + + +--- +-- Cluster Nodes +-- +-- An array of cluster nodes. +-- +-- @table nodes + + +--- +-- Cluster Node +-- @field ip The IP address to connect (defaults to `"127.0.0.1"`). +-- @field port The port to connect (defaults to `6379`). +-- @table node + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a Redis Cluster storage. +-- +-- This creates a new Redis Cluster storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration redis cluster storage @{configuration} +-- @treturn table redis cluster storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + + local name = configuration and configuration.name + local nodes = configuration and configuration.nodes + + local lock_zone = configuration and configuration.lock_zone + local lock_prefix = configuration and configuration.lock_prefix + local max_redirections = configuration and configuration.max_redirections + local max_connection_attempts = configuration and configuration.max_connection_attempts + local max_connection_timeout = configuration and configuration.max_connection_timeout + + local username = configuration and configuration.username + local password = configuration and configuration.password + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + local server_name = configuration and configuration.server_name + + local auth + if password then + if username then + auth = username .. " " .. password + else + auth = password + end + end + + if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then + return setmetatable({ + prefix = prefix, + suffix = suffix, + options = { + name = name, + dict_name = lock_zone, + refresh_lock_key = lock_prefix, + serv_list = nodes, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + keepalive_cons = pool_size, + max_redirection = max_redirections, + max_connection_attempts = max_connection_attempts, + max_connection_timeout = max_connection_timeout, + auth = auth, + connect_opts = { + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + pool = pool, + pool_size = pool_size, + backlog = backlog, + }, + }, + }, metatable) + end + + return setmetatable({ + prefix = prefix, + suffix = suffix, + options = { + name = name, + dict_name = lock_zone, + refresh_lock_key = lock_prefix, + serv_list = nodes, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + keepalive_cons = pool_size, + max_redirection = max_redirections, + max_connection_attempts = max_connection_attempts, + max_connection_timeout = max_connection_timeout, + auth = auth, + }, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/redis/common.lua b/lib/resty/session/redis/common.lua new file mode 100644 index 00000000..993da562 --- /dev/null +++ b/lib/resty/session/redis/common.lua @@ -0,0 +1,172 @@ +--- +-- Common Redis functions shared between Redis, +-- Redis Cluster and Redis Sentinel implementations. +-- +-- @module resty.session.redis.common + + +local utils = require "resty.session.utils" + + +local get_name = utils.get_name +local ipairs = ipairs + + +--- +-- Store session data. +-- +-- @function module.SET +-- @tparam table storage the storage +-- @tparam table red the redis instance +-- @tparam string name the cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam table remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +local function SET(storage, red, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + if not metadata and not old_key then + return red:set(get_name(storage, name, key), value, "EX", ttl) + end + + local old_name + local old_ttl + if old_key then + old_name = get_name(storage, name, old_key) + if not remember then + -- redis < 7.0 + old_ttl = red:ttl(old_name) + end + end + + red:init_pipeline() + red:set(get_name(storage, name, key), value, "EX", ttl) + + -- redis < 7.0 + if old_name then + if remember then + red:unlink(old_name) + elseif not old_ttl or old_ttl > stale_ttl then + red:expire(old_name, stale_ttl) + end + end + + -- redis >= 7.0 + --if old_key then + -- if remember then + -- red:unlink(get_name(storage, name, old_key)) + -- else + -- red:expire(get_name(storage, name, old_key), stale_ttl, "LT") + -- end + --end + + if metadata then + local audiences = metadata.audiences + local subjects = metadata.subjects + local score = current_time - 1 + local new_score = current_time + ttl + local count = #audiences + for i = 1, count do + local meta_key = get_name(storage, name, audiences[i], subjects[i]) + red:zremrangebyscore(meta_key, 0, score) + red:zadd(meta_key, new_score, key) + if old_key then + red:zrem(meta_key, old_key) + end + red:expire(meta_key, ttl) + end + end + + return red:commit_pipeline() +end + + +--- +-- Retrieve session data. +-- +-- @function module.GET +-- @tparam table storage the storage +-- @tparam table red the redis instance +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +local function GET(storage, red, name, key) + return red:get(get_name(storage, name, key)) +end + + +--- +-- Delete session data. +-- +-- @function module.UNLINK +-- @tparam table storage the storage +-- @tparam table red the redis instance +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam number current_time current time +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +local function UNLINK(storage, red, name, key, current_time, metadata) + if not metadata then + return red:unlink(get_name(storage, name, key)) + end + + red:init_pipeline() + red:unlink(get_name(storage, name, key)) + local audiences = metadata.audiences + local subjects = metadata.subjects + local score = current_time - 1 + local count = #audiences + for i = 1, count do + local meta_key = get_name(storage, name, audiences[i], subjects[i]) + red:zremrangebyscore(meta_key, 0, score) + red:zrem(meta_key, key) + end + + return red:commit_pipeline() +end + + +--- +-- Read session metadata. +-- +-- @function module.READ_METADATA +-- @tparam table storage the storage +-- @tparam table red the redis instance +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +local function READ_METADATA(storage, red, name, audience, subject, current_time) + local sessions = {} + local meta_key = get_name(storage, name, audience, subject) + local res, err = red:zrange(meta_key, current_time, "+inf", "BYSCORE", "WITHSCORES") + if not res then + return nil, err + end + + for i, v in ipairs(res) do + if i % 2 ~= 0 then + sessions[v] = res[i + 1] + end + end + + return sessions +end + + +return { + SET = SET, + GET = GET, + UNLINK = UNLINK, + READ_METADATA = READ_METADATA, +} diff --git a/lib/resty/session/redis/sentinel.lua b/lib/resty/session/redis/sentinel.lua new file mode 100644 index 00000000..73db463f --- /dev/null +++ b/lib/resty/session/redis/sentinel.lua @@ -0,0 +1,260 @@ +--- +-- Redis Sentinel backend for session library +-- +-- @module resty.session.redis.sentinel + + +local common = require "resty.session.redis.common" +local redis = require "resty.redis.connector" + + +local setmetatable = setmetatable +local error = error +local null = ngx.null + + +local SET = common.SET +local GET = common.GET +local UNLINK = common.UNLINK +local READ_METADATA = common.READ_METADATA + + +local function exec(self, func, ...) + local red, err = self.connector:connect() + if not red then + return nil, err + end + + local ok, err = func(self, red, ...) + if err then + red:close() + return nil, err + end + + if ok == null then + ok = nil + end + + self.connector:set_keepalive(red) + + return ok, err +end + + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam table remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(...) + return exec(self, SET, ...) +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(...) + return exec(self, GET, ...) +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(...) + return exec(self, UNLINK, ...) +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(...) + return exec(self, READ_METADATA, ...) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Redis Sentinel storage backend configuration +-- @field prefix Prefix for the keys stored in redis. +-- @field suffix Suffix for the keys stored in redis. +-- @field master Name of master. +-- @field role `"master"` or `"slave"`. +-- @field sentinels Redis Sentinels. +-- @field sentinel_username Optional sentinel username. +-- @field sentinel_password Optional sentinel password. +-- @field username The database username to authenticate. +-- @field password Password for authentication. +-- @field database The database to connect. +-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. +-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. +-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. +-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. +-- @field pool A custom name for the connection pool being used. +-- @field pool_size The size of the connection pool. +-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). +-- @field ssl Enable SSK (defaults to `false`). +-- @field ssl_verify Verify server certificate (defaults to `nil`). +-- @field server_name The server name for the new TLS extension Server Name Indication (SNI). +-- @table configuration + + +--- +-- Sentinels +-- +-- An array of sentinels. +-- +-- @table sentinels + + +--- +-- Sentinel +-- @field host The host to connect. +-- @field port The port to connect. +-- @table sentinel + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a Redis Sentinel storage. +-- +-- This creates a new Redis Sentinel storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration redis sentinel storage @{configuration} +-- @treturn table redis sentinel storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + + local master = configuration and configuration.master + local role = configuration and configuration.role + local sentinels = configuration and configuration.sentinels + local sentinel_username = configuration and configuration.sentinel_username + local sentinel_password = configuration and configuration.sentinel_password + + local username = configuration and configuration.username + local password = configuration and configuration.password + local database = configuration and configuration.database + + local connect_timeout = configuration and configuration.connect_timeout + local send_timeout = configuration and configuration.send_timeout + local read_timeout = configuration and configuration.read_timeout + local keepalive_timeout = configuration and configuration.keepalive_timeout + + local pool = configuration and configuration.pool + local pool_size = configuration and configuration.pool_size + local backlog = configuration and configuration.backlog + local ssl = configuration and configuration.ssl + local ssl_verify = configuration and configuration.ssl_verify + local server_name = configuration and configuration.server_name + + local connector + if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then + connector = redis.new({ + master_name = master, + role = role, + sentinels = sentinels, + sentinel_username = sentinel_username, + sentinel_password = sentinel_password, + username = username, + password = password, + db = database, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + keepalive_poolsize = pool_size, + connection_options = { + ssl = ssl, + ssl_verify = ssl_verify, + server_name = server_name, + pool = pool, + pool_size = pool_size, + backlog = backlog, + } + }) + else + connector = redis.new({ + master_name = master, + role = role, + sentinels = sentinels, + sentinel_username = sentinel_username, + sentinel_password = sentinel_password, + username = username, + password = password, + db = database, + connect_timeout = connect_timeout, + send_timeout = send_timeout, + read_timeout = read_timeout, + keepalive_timeout = keepalive_timeout, + keepalive_poolsize = pool_size, + }) + end + + return setmetatable({ + prefix = prefix, + suffix = suffix, + connector = connector, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/serializers/json.lua b/lib/resty/session/serializers/json.lua deleted file mode 100644 index 960c4d8e..00000000 --- a/lib/resty/session/serializers/json.lua +++ /dev/null @@ -1,6 +0,0 @@ -local json = require "cjson.safe" - -return { - serialize = json.encode, - deserialize = json.decode, -} diff --git a/lib/resty/session/shm.lua b/lib/resty/session/shm.lua new file mode 100644 index 00000000..1f569093 --- /dev/null +++ b/lib/resty/session/shm.lua @@ -0,0 +1,315 @@ +--- +-- Shared Memory (SHM) backend for session library +-- +-- @module resty.session.shm + + +local table_new = require "table.new" +local utils = require "resty.session.utils" + + +local meta_get_value = utils.meta_get_value +local meta_get_next = utils.meta_get_next +local get_name = utils.get_name +local errmsg = utils.errmsg + + +local setmetatable = setmetatable +local shared = ngx.shared +local random = math.random +local assert = assert +local error = error +local pairs = pairs +local max = math.max +local log = ngx.log + + +local WARN = ngx.WARN + + +local DEFAULT_ZONE = "sessions" +local CLEANUP_PROBABILITY = 0.1 -- 1 / 10 + + +local function get_and_clean_metadata(dict, meta_key, current_time) + local size = dict:llen(meta_key) + if not size or size == 0 then + return + end + + local max_expiry = current_time + local sessions = table_new(0, size) + + for _ = 1, size do + local meta_value, err = dict:lpop(meta_key) + if not meta_value then + log(WARN, "[session] ", errmsg(err, "failed read meta value")) + break + end + + local key, err, exp = meta_get_next(meta_value, 1) + if err then + return nil, err + end + + if exp and exp > current_time then + sessions[key] = exp + max_expiry = max(max_expiry, exp) + + else + sessions[key] = nil + end + end + + for key, exp in pairs(sessions) do + local meta_value = meta_get_value(key, exp) + local ok, err = dict:rpush(meta_key, meta_value) + if not ok then + log(WARN, "[session] ", errmsg(err, "failed to update metadata")) + end + end + + local exp = max_expiry - current_time + if exp > 0 then + local ok, err = dict:expire(meta_key, max_expiry - current_time) + if not ok and err ~= "not found" then + log(WARN, "[session] ", errmsg(err, "failed to touch metadata")) + end + + else + dict:delete(meta_key) + end + + return sessions +end + + +local function cleanup(dict, meta_key, current_time) + get_and_clean_metadata(dict, meta_key, current_time) +end + + +local function read_metadata(self, meta_key, current_time) + return get_and_clean_metadata(self.dict, meta_key, current_time) +end + +--- +-- Storage +-- @section instance + + +local metatable = {} + + +metatable.__index = metatable + + +function metatable.__newindex() + error("attempt to update a read-only table", 2) +end + + +--- +-- Store session data. +-- +-- @function instance:set +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam string value session value +-- @tparam number ttl session ttl +-- @tparam number current_time current time +-- @tparam[opt] string old_key old session id +-- @tparam string stale_ttl stale ttl +-- @tparam[opt] table metadata table of metadata +-- @tparam boolean remember whether storing persistent session or not +-- @treturn true|nil ok +-- @treturn string error message +function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember) + local dict = self.dict + if not metadata and not old_key then + local ok, err = dict:set(get_name(self, name, key), value, ttl) + if not ok then + return nil, err + end + + return true + end + + local old_name, old_ttl + if old_key then + old_name = get_name(self, name, old_key) + if not remember then + old_ttl = dict:ttl(old_name) + end + end + + local ok, err = dict:set(get_name(self, name, key), value, ttl) + if not ok then + return nil, err + end + + if old_name then + if remember then + dict:delete(old_name) + + elseif (not old_ttl or old_ttl > stale_ttl) then + local ok, err = dict:expire(old_name, stale_ttl) + if not ok then + log(WARN, "[session] ", errmsg(err, "failed to touch old session")) + end + end + end + + if not metadata then + return true + end + + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = get_name(self, name, audiences[i], subjects[i]) + local meta_value = meta_get_value(key, current_time + ttl) + + local ok, err = dict:rpush(meta_key, meta_value) + if not ok then + log(WARN, "[session] ", errmsg(err, "failed to update metadata")) + end + + if old_key then + meta_value = meta_get_value(old_key, 0) + local ok, err = dict:rpush(meta_key, meta_value) + if not ok then + log(WARN, "[session] ", errmsg(err, "failed to update old metadata")) + end + end + + -- no need to clean up every time we write + -- it is just beneficial when a key is used a lot + if random() < CLEANUP_PROBABILITY then + cleanup(dict, meta_key, current_time) + end + end + + return true +end + + +--- +-- Retrieve session data. +-- +-- @function instance:get +-- @tparam string name cookie name +-- @tparam string key session key +-- @treturn string|nil session data +-- @treturn string error message +function metatable:get(name, key) + local value, err = self.dict:get(get_name(self, name, key)) + if not value then + return nil, err + end + + return value +end + + +--- +-- Delete session data. +-- +-- @function instance:delete +-- @tparam string name cookie name +-- @tparam string key session key +-- @tparam[opt] table metadata session meta data +-- @treturn boolean|nil session data +-- @treturn string error message +function metatable:delete(name, key, current_time, metadata) + local dict = self.dict + + dict:delete(get_name(self, name, key)) + + if not metadata then + return true + end + + local audiences = metadata.audiences + local subjects = metadata.subjects + local count = #audiences + for i = 1, count do + local meta_key = get_name(self, name, audiences[i], subjects[i]) + local meta_value = meta_get_value(key, 0) + + local ok, err = dict:rpush(meta_key, meta_value) + if not ok then + if not ok then + log(WARN, "[session] ", errmsg(err, "failed to update metadata")) + end + end + + cleanup(dict, meta_key, current_time) + end + + return true +end + + +--- +-- Read session metadata. +-- +-- @function instance:read_metadata +-- @tparam string name cookie name +-- @tparam string audience session key +-- @tparam string subject session key +-- @tparam number current_time current time +-- @treturn table|nil session metadata +-- @treturn string error message +function metatable:read_metadata(name, audience, subject, current_time) + local meta_key = get_name(self, name, audience, subject) + return read_metadata(self, meta_key, current_time) +end + + +local storage = {} + + +--- +-- Configuration +-- @section configuration + + +--- +-- Shared memory storage backend configuration +-- @field prefix Prefix for the keys stored in SHM. +-- @field suffix Suffix for the keys stored in SHM. +-- @field zone A name of shared memory zone (defaults to `sessions`). +-- @table configuration + + +--- +-- Constructors +-- @section constructors + + +--- +-- Create a SHM storage. +-- +-- This creates a new shared memory storage instance. +-- +-- @function module.new +-- @tparam[opt] table configuration shm storage @{configuration} +-- @treturn table shm storage instance +function storage.new(configuration) + local prefix = configuration and configuration.prefix + local suffix = configuration and configuration.suffix + local zone = configuration and configuration.zone or DEFAULT_ZONE + + local dict = assert(shared[zone], "lua_shared_dict " .. zone .. " is missing") + + return setmetatable({ + prefix = prefix, + suffix = suffix, + dict = dict, + }, metatable) +end + + +return storage diff --git a/lib/resty/session/storage/cookie.lua b/lib/resty/session/storage/cookie.lua deleted file mode 100644 index 95e26d11..00000000 --- a/lib/resty/session/storage/cookie.lua +++ /dev/null @@ -1,7 +0,0 @@ -local storage = {} - -function storage.new() - return storage -end - -return storage diff --git a/lib/resty/session/storage/dshm.lua b/lib/resty/session/storage/dshm.lua deleted file mode 100644 index e6d887f1..00000000 --- a/lib/resty/session/storage/dshm.lua +++ /dev/null @@ -1,163 +0,0 @@ -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/lib/resty/session/storage/memcache.lua b/lib/resty/session/storage/memcache.lua deleted file mode 100644 index da44ba77..00000000 --- a/lib/resty/session/storage/memcache.lua +++ /dev/null @@ -1,303 +0,0 @@ -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/lib/resty/session/storage/memcached.lua b/lib/resty/session/storage/memcached.lua deleted file mode 100644 index 0ecc508c..00000000 --- a/lib/resty/session/storage/memcached.lua +++ /dev/null @@ -1 +0,0 @@ -return require "resty.session.storage.memcache" diff --git a/lib/resty/session/storage/redis.lua b/lib/resty/session/storage/redis.lua deleted file mode 100644 index 3de04722..00000000 --- a/lib/resty/session/storage/redis.lua +++ /dev/null @@ -1,478 +0,0 @@ -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/lib/resty/session/storage/shm.lua b/lib/resty/session/storage/shm.lua deleted file mode 100644 index 6f81435e..00000000 --- a/lib/resty/session/storage/shm.lua +++ /dev/null @@ -1,125 +0,0 @@ -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/lib/resty/session/strategies/default.lua b/lib/resty/session/strategies/default.lua deleted file mode 100644 index a43ef5a6..00000000 --- a/lib/resty/session/strategies/default.lua +++ /dev/null @@ -1,232 +0,0 @@ -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/lib/resty/session/strategies/regenerate.lua b/lib/resty/session/strategies/regenerate.lua deleted file mode 100644 index f2a97dd6..00000000 --- a/lib/resty/session/strategies/regenerate.lua +++ /dev/null @@ -1,43 +0,0 @@ -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 diff --git a/lib/resty/session/utils.lua b/lib/resty/session/utils.lua new file mode 100644 index 00000000..6ab17868 --- /dev/null +++ b/lib/resty/session/utils.lua @@ -0,0 +1,1124 @@ +--- +-- Common utilities for session library and storage backends +-- +-- @module resty.session.utils + + +local require = require + + +local buffer = require "string.buffer" +local bit = require "bit" + + +local select = select +local ceil = math.ceil +local byte = string.byte +local band = bit.band +local bnot = bit.bnot +local bor = bit.bor +local fmt = string.format +local sub = string.sub + + +local bpack, bunpack do + local binpack + local binunpack + + local SIZE_TO_FORMAT = { + [1] = "<C", + [2] = "<S", + [3] = "<I", + [4] = "<I", + [5] = "<L", + [6] = "<L", + [7] = "<L", + [8] = "<L", + } + + local function bpack_real(size, value) + local packed = binpack(SIZE_TO_FORMAT[size], value) + + if size == 3 then + return sub(packed, 1, 3) + elseif size == 5 then + return sub(packed, 1, 5) + elseif size == 6 then + return sub(packed, 1, 6) + elseif size == 7 then + return sub(packed, 1, 7) + end + + return packed + end + + local function bunpack_real(size, value) + if size == 5 then + value = value .. "\0\0\0" + elseif size == 6 then + value = value .. "\0\0" + elseif size == 3 or size == 7 then + value = value .. "\0" + end + + local _, unpacked_value = binunpack(value, SIZE_TO_FORMAT[size]) + return unpacked_value + end + + + --- + -- Binary pack unsigned integer. + -- + -- Returns binary packed version of an integer in little endian unsigned format. + -- + -- Size can be: + -- + -- * `1`, pack input as a little endian unsigned char (`<C`) + -- * `2`, pack input as a little endian unsigned short (`<S`) + -- * `3`, pack input as a little endian unsigned integer (truncated) (`<I`) + -- * `4`, pack input as a little endian unsigned integer (`<I`) + -- * `5`, pack input as a little endian unsigned long (truncated) (`<L`) + -- * `6`, pack input as a little endian unsigned long (truncated) (`<L`) + -- * `7`, pack input as a little endian unsigned long (truncated) (`<L`) + -- * `8`, pack input as a little endian unsigned long (`<L`) + -- + -- @function utils.bpack + -- @tparam number size size of binary packed output + -- @tparam number value value to binary pack + -- @treturn string binary packed value + -- + -- @usage + -- local packed_128 = require "resty.session.utils".bpack(1, 128) + -- local packed_now = require "resty.session.utils".bpack(8, ngx.time()) + bpack = function(size, value) + if not binpack then + binpack = require "lua_pack".pack + end + bpack = bpack_real + return bpack(size, value) + end + + + --- + -- Binary unpack unsigned integer. + -- + -- Returns number from a little endian unsigned binary packed format. + -- + -- Size can be: + -- + -- * `1`, unpack input from little endian unsigned char (`<C`) + -- * `2`, unpack input from little endian unsigned short (`<S`) + -- * `3`, unpack input from little endian unsigned integer (truncated) (`<I`) + -- * `4`, unpack input from little endian unsigned integer (`<I`) + -- * `5`, unpack input from little endian unsigned integer (truncated) (`<L`) + -- * `6`, unpack input from little endian unsigned integer (truncated) (`<L`) + -- * `7`, unpack input from little endian unsigned integer (truncated) (`<L`) + -- * `8`, unpack input from little endian unsigned long (`<L`) + -- + -- @function utils.bunpack + -- @tparam number size size of binary packed output + -- @tparam number value value to binary pack + -- @treturn number binary unpacked value + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local value = 128 + -- local packed_value = utils.bpack(1, value) + -- local unpacked_value = utils.bunpack(1, packed_value) + -- print(value == unpacked_value) -- true + bunpack = function(size, value) + if not binunpack then + binunpack = require "lua_pack".unpack + end + bunpack = bunpack_real + return bunpack(size, value) + end +end + + +local trim do + local SPACE_BYTE = byte(" ") + local TAB_BYTE = byte("\t") + local CR_BYTE = byte("\r") + local LF_BYTE = byte("\n") + local VTAB_BYTE = byte("\v") + local FF_BYTE = byte("\f") + + --- + -- Trim whitespace from the start and from the end of string. + -- + -- Characters that are trimmed: + -- + -- * space `" "` + -- * tab `"\t"` + -- * carriage return `"\r"` + -- * line feed `"\n"` + -- * vertical tab `"\v"` + -- * form feed `"\f"` + -- + -- @function utils.trim + -- @tparam string value string to trim + -- @treturn string a whitespace trimmed string + -- + -- @usage + -- local trimmed = require "resty.session.utils".trim(" hello world ") + trim = function(value) + if value == nil or value == "" then + return "" + end + + local len = #value + + local s = 1 + for i = 1, len do + local b = byte(value, i) + if b == SPACE_BYTE + or b == TAB_BYTE + or b == CR_BYTE + or b == LF_BYTE + or b == VTAB_BYTE + or b == FF_BYTE + then + s = s + 1 + else + break + end + end + + local e = len + for i = len, 1, -1 do + local b = byte(value, i) + if b == SPACE_BYTE + or b == TAB_BYTE + or b == CR_BYTE + or b == LF_BYTE + or b == VTAB_BYTE + or b == FF_BYTE + then + e = e - 1 + else + break + end + end + + if s ~= 1 or e ~= len then + return sub(value, s, e) + end + + return value + end +end + + +local encode_json, decode_json do + local cjson + + --- + -- JSON encode value. + -- + -- @function utils.encode_json + -- @tparam any value value to json encode + -- @treturn string json encoded value + -- + -- @usage + -- local json = require "resty.session.utils".encode_json({ hello = "world" }) + encode_json = function(value) + if not cjson then + cjson = require "cjson.safe".new() + end + encode_json = cjson.encode + return encode_json(value) + end + + --- + -- JSON decode value. + -- + -- @function utils.decode_json + -- @tparam string value string to json decode + -- @treturn any json decoded value + -- + -- @usage + -- local tbl = require "resty.session.utils".decode_json('{ "hello": "world" }') + decode_json = function(value) + if not cjson then + cjson = require "cjson.safe".new() + end + decode_json = cjson.decode + return decode_json(value) + end +end + + +local encode_base64url, decode_base64url, base64_size do + local base64 + --- + -- Base64 URL encode value. + -- + -- @function utils.encode_base64url + -- @tparam string value string to base64 url encode + -- @treturn string base64 url encoded value + -- + -- @usage + -- local encoded = require "resty.session.utils".encode_base64url("test") + encode_base64url = function(value) + if not base64 then + base64 = require "ngx.base64" + end + encode_base64url = base64.encode_base64url + return encode_base64url(value) + end + + --- + -- Base64 URL decode value + -- + -- @function utils.decode_base64url + -- @tparam string value string to base64 url decode + -- @treturn string base64 url decoded value + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local encoded = utils.encode_base64url("test") + -- local decoded = utils.decode_base64url(encoded) + decode_base64url = function(value) + if not base64 then + base64 = require "ngx.base64" + end + decode_base64url = base64.decode_base64url + return decode_base64url(value) + end + + --- + -- Base64 size from original size (without padding). + -- + -- @function utils.base64_size + -- @tparam number size original size + -- @treturn number base64 url encoded size without padding + -- + -- @usage + -- local test = "test" + -- local b64len = require "resty.session.utils".base64_size(#test) + base64_size = function(size) + return ceil(4 * size / 3) + end +end + + +local deflate, inflate do + local DEFLATE_WINDOW_BITS = -15 + local DEFLATE_CHUNK_SIZE = 8192 + local DEFLATE_OPTIONS = { + windowBits = DEFLATE_WINDOW_BITS, + } + + local zlib + local input_buffer = buffer.new() + local output_buffer = buffer.new() + + local function prepare_buffers(input) + input_buffer:set(input) + output_buffer:reset() + end + + local function read_input_buffer(size) + local data = input_buffer:get(size) + return data ~= "" and data or nil + end + + local function write_output_buffer(data) + return output_buffer:put(data) + end + + local function gzip(inflate_or_deflate, input, chunk_size, window_bits_or_options) + prepare_buffers(input) + local ok, err = inflate_or_deflate(read_input_buffer, write_output_buffer, + chunk_size, window_bits_or_options) + if not ok then + return nil, err + end + + return output_buffer:get() + end + + local function deflate_real(data) + return gzip(zlib.deflateGzip, data, DEFLATE_CHUNK_SIZE, DEFLATE_OPTIONS) + end + + local function inflate_real(data) + return gzip(zlib.inflateGzip, data, DEFLATE_CHUNK_SIZE, DEFLATE_WINDOW_BITS) + end + + + --- + -- Compress the data with deflate algorithm. + -- + -- @function utils.deflate + -- @tparam string data data to deflate + -- @treturn string deflated data + -- + -- @usage + -- local test = "test" + -- local deflated = require "resty.session.utils".deflate(("a"):rep(100)) + deflate = function(data) + if not zlib then + zlib = require "ffi-zlib" + end + deflate = deflate_real + return deflate(data) + end + + --- + -- Decompress the data compressed with deflate algorithm. + -- + -- @function utils.inflate + -- @tparam string data data to inflate + -- @treturn string inflated data + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local deflated = utils.deflate(("a"):rep(100)) + -- local inflated = utils.inflate(deflated) + inflate = function(data) + if not zlib then + zlib = require "ffi-zlib" + end + inflate = inflate_real + return inflate(data) + end +end + + +local rand_bytes do + local rand + + --- + -- Generate crypto random bytes. + -- + -- @function utils.rand_bytes + -- @tparam number length how many bytes of random data to generate + -- @treturn string|nil random bytes + -- @treturn string|nil error message + -- + -- @usage + -- local bytes = require "resty.session.utils".rand_bytes(32) + rand_bytes = function(length) + if not rand then + rand = require "resty.openssl.rand" + end + rand_bytes = rand.bytes + return rand_bytes(length) + end +end + + +local sha256 do + local digest + local sha256_digest + + local function sha256_real(value) + local _, err, output + if not sha256_digest then + sha256_digest, err = digest.new("sha256") + if err then + return nil, err + end + end + + output, err = sha256_digest:final(value) + if err then + sha256_digest = nil + return nil, err + end + + _, err = sha256_digest:reset() + if err then + sha256_digest = nil + end + + return output + end + + --- + -- Calculates SHA-256 hash of the value. + -- + -- @function utils.sha256 + -- @tparam string value value from which to calculate hash + -- @treturn string|nil sha-256 hash (32 bytes) + -- @treturn string|nil error message + -- + -- @usage + -- local hash, err = require "resty.session.utils".sha256("hello world") + sha256 = function(value) + if not digest then + digest = require "resty.openssl.digest" + end + sha256 = sha256_real + return sha256(value) + end +end + + +local derive_hkdf_sha256 do + local kdf_derive + + local EXTRACTED_KEYS = {} + + local HKDF_SHA256_EXTRACT_OPTS + local HKDF_SHA256_EXPAND_OPTS + + local function derive_hkdf_sha256_real(ikm, nonce, usage, size) + local err + local key = EXTRACTED_KEYS[ikm] + if not key then + HKDF_SHA256_EXTRACT_OPTS.hkdf_key = ikm + key, err = kdf_derive(HKDF_SHA256_EXTRACT_OPTS) + HKDF_SHA256_EXTRACT_OPTS.hkdf_key = "" + if not key then + return nil, err + end + EXTRACTED_KEYS[ikm] = key + end + + HKDF_SHA256_EXPAND_OPTS.hkdf_key = key + HKDF_SHA256_EXPAND_OPTS.hkdf_info = usage .. ":" .. nonce + HKDF_SHA256_EXPAND_OPTS.outlen = size + key, err = kdf_derive(HKDF_SHA256_EXPAND_OPTS) + if not key then + return nil, err + end + + return key + end + + --- + -- Derive a new key using HKDF with SHA-256. + -- + -- @function utils.derive_hkdf_sha256 + -- @tparam string ikm initial key material + -- @tparam string nonce nonce + -- @tparam string usage e.g. `"encryption"` or `"authentication"` + -- @tparam number size how many bytes to return + -- @treturn string|nil key material + -- @treturn string|nil error message + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local ikm = utils.rand_bytes(32) + -- local nonce = utils.rand_bytes(32) + -- local key, err = utils.derive_hkdf_sha256(ikm, nonce, "encryption", 32) + derive_hkdf_sha256 = function(ikm, nonce, usage, size) + if not kdf_derive then + local kdf = require "resty.openssl.kdf" + HKDF_SHA256_EXTRACT_OPTS = { + type = kdf.HKDF, + outlen = 32, + md = "sha256", + salt = "", + hkdf_key = "", + hkdf_mode = kdf.HKDEF_MODE_EXTRACT_ONLY, + hkdf_info = "", + } + HKDF_SHA256_EXPAND_OPTS = { + type = kdf.HKDF, + outlen = 44, + md = "sha256", + salt = "", + hkdf_key = "", + hkdf_mode = kdf.HKDEF_MODE_EXPAND_ONLY, + hkdf_info = "", + } + kdf_derive = kdf.derive + end + derive_hkdf_sha256 = derive_hkdf_sha256_real + return derive_hkdf_sha256(ikm, nonce, usage, size) + end +end + + +local derive_pbkdf2_hmac_sha256 do + local kdf_derive + + local PBKDF2_SHA256_OPTS + + local function derive_pbkdf2_hmac_sha256_real(pass, salt, usage, size, iterations) + PBKDF2_SHA256_OPTS.pass = pass + PBKDF2_SHA256_OPTS.salt = usage .. ":" .. salt + PBKDF2_SHA256_OPTS.outlen = size + PBKDF2_SHA256_OPTS.pbkdf2_iter = iterations + local key, err = kdf_derive(PBKDF2_SHA256_OPTS) + if not key then + return nil, err + end + + return key + end + + --- + -- Derive a new key using PBKDF2 with SHA-256. + -- + -- @function utils.derive_pbkdf2_hmac_sha256 + -- @tparam string pass password + -- @tparam string salt salt + -- @tparam string usage e.g. `"encryption"` or `"authentication"` + -- @tparam number size how many bytes to return + -- @tparam number iterations how many iterations to run, e.g. `10000` + -- @treturn string|nil key material + -- @treturn string|nil error message + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local pass = "my-super-secret-password" + -- local salt = utils.rand_bytes(32) + -- local key, err = utils.derive_pbkdf2_hmac_sha256(pass, salt, "encryption", 32, 10000) + derive_pbkdf2_hmac_sha256 = function(pass, salt, usage, size, iterations) + if not kdf_derive then + local kdf = require "resty.openssl.kdf" + PBKDF2_SHA256_OPTS = { + type = kdf.PBKDF2, + outlen = 44, + md = "sha256", + pass = "", + salt = "", + pbkdf2_iter = 10000, + } + kdf_derive = kdf.derive + end + derive_pbkdf2_hmac_sha256 = derive_pbkdf2_hmac_sha256_real + return derive_pbkdf2_hmac_sha256(pass, salt, usage, size, iterations) + end +end + + +--- +-- Derive a new AES-256 GCM-mode key and initialization vector. +-- +-- Safety can be: +-- * `nil` or `"None"`: key and iv will be derived using HKDF with SHA-256 +-- * `Low`: key and iv will be derived using PBKDF2 with SHA-256 (1.000 iterations) +-- * `Medium`: key and iv will be derived using PBKDF2 with SHA-256 (10.000 iterations) +-- * `High`: key and iv will be derived using PBKDF2 with SHA-256 (100.000 iterations) +-- * `Very High`: key and iv will be derived using PBKDF2 with SHA-256 (1.000.000 iterations) +-- +-- @function utils.derive_aes_gcm_256_key_and_iv +-- @tparam string ikm initial key material +-- @tparam string nonce nonce +-- @tparam[opt] string safety safety of key generation +-- @treturn string|nil key +-- @treturn string|nil error message +-- @treturn string|nil initialization vector +-- +-- @usage +-- local utils = require "resty.session.utils" +-- local ikm = utils.rand_bytes(32) +-- local nonce = utils.rand_bytes(32) +-- local key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce, "Medium") +local function derive_aes_gcm_256_key_and_iv(ikm, nonce, safety) + local bytes, err + if safety and safety ~= "None" then + if safety == "Very High" then + bytes, err = derive_pbkdf2_hmac_sha256(ikm, nonce, "encryption", 44, 1000000) + elseif safety == "High" then + bytes, err = derive_pbkdf2_hmac_sha256(ikm, nonce, "encryption", 44, 100000) + elseif safety == "Low" then + bytes, err = derive_pbkdf2_hmac_sha256(ikm, nonce, "encryption", 44, 1000) + else + bytes, err = derive_pbkdf2_hmac_sha256(ikm, nonce, "encryption", 44, 10000) + end + + else + bytes, err = derive_hkdf_sha256(ikm, nonce, "encryption", 44) + end + + if not bytes then + return nil, err + end + + local key = sub(bytes, 1, 32) -- 32 bytes + local iv = sub(bytes, 33, 44) -- 12 bytes + + return key, nil, iv +end + + +--- +-- Derive HMAC SHA-256 key for message authentication using HDKF with SHA-256. +-- +-- @function utils.derive_hmac_sha256_key +-- @tparam string ikm initial key material +-- @tparam string nonce nonce +-- @treturn string|nil key +-- @treturn string|nil error message +-- +-- @usage +-- local utils = require "resty.session.utils" +-- local ikm = utils.rand_bytes(32) +-- local nonce = utils.rand_bytes(32) +-- local key, err = utils.derive_hmac_sha256_key(ikm, nonce) +local function derive_hmac_sha256_key(ikm, nonce) + return derive_hkdf_sha256(ikm, nonce, "authentication", 32) +end + + +local encrypt_aes_256_gcm, decrypt_aes_256_gcm do + local AES_256_GCP_CIPHER = "aes-256-gcm" + local AES_256_GCM_TAG_SIZE = 16 + + local cipher_aes_256_gcm + local function encrypt_aes_256_gcm_real(key, iv, plaintext, aad) + local ciphertext, err = cipher_aes_256_gcm:encrypt(key, iv, plaintext, false, aad) + if not ciphertext then + return nil, err + end + + local tag, err = cipher_aes_256_gcm:get_aead_tag(AES_256_GCM_TAG_SIZE) + if not tag then + return nil, err + end + + return ciphertext, nil, tag + end + + local function decrypt_aes_256_gcm_real(key, iv, ciphertext, aad, tag) + return cipher_aes_256_gcm:decrypt(key, iv, ciphertext, false, aad, tag) + end + + --- + -- Encrypt plain text using AES-256 in GCM-mode. + -- + -- @function utils.encrypt_aes_256_gcm + -- @tparam string key encryption key + -- @tparam string iv initialization vector + -- @tparam string plaintext plain text + -- @tparam string aad additional authenticated data + -- @treturn string|nil ciphertext + -- @treturn string|nil error message + -- @treturn string|nil authentication tag + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local ikm = utils.rand_bytes(32) + -- local nonce = utils.rand_bytes(32) + -- local key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce) + -- local enc, err, tag = utils.encrypt_aes_256_gcm(key, iv, "hello", "john@doe.com") + encrypt_aes_256_gcm = function(key, iv, plaintext, aad) + if not cipher_aes_256_gcm then + cipher_aes_256_gcm = require("resty.openssl.cipher").new(AES_256_GCP_CIPHER) + end + encrypt_aes_256_gcm = encrypt_aes_256_gcm_real + return encrypt_aes_256_gcm(key, iv, plaintext, aad) + end + + --- + -- Decrypt ciphertext using AES-256 in GCM-mode. + -- + -- @function utils.decrypt_aes_256_gcm + -- @tparam string key encryption key + -- @tparam string iv initialization vector + -- @tparam string plaintext plain text + -- @tparam string aad additional authenticated data + -- @tparam string tag authentication tag + -- @treturn string|nil ciphertext + -- @treturn string|nil error message + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local ikm = utils.rand_bytes(32) + -- local nonce = utils.rand_bytes(32) + -- local key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce) + -- local enc, err, tag = utils.encrypt_aes_256_gcm(key, iv, "hello", "john@doe.com") + -- local out, err = utils.decrypt_aes_256_gcm(key, iv, ciphertext, "john@doe.com", tag) + decrypt_aes_256_gcm = function(key, iv, ciphertext, aad, tag) + if not cipher_aes_256_gcm then + cipher_aes_256_gcm = require("resty.openssl.cipher").new(AES_256_GCP_CIPHER) + end + decrypt_aes_256_gcm = decrypt_aes_256_gcm_real + return decrypt_aes_256_gcm(key, iv, ciphertext, aad, tag) + end +end + + +local hmac_sha256 do + local hmac + local HMAC_SHA256_DIGEST = "sha256" + + local function hmac_sha256_real(key, value) + local mac, err = hmac.new(key, HMAC_SHA256_DIGEST) + if not mac then + return nil, err + end + + local digest, err = mac:final(value) + if not digest then + return nil, err + end + + return digest + end + + --- + -- Calculate message authentication code with HMAC with SHA-256. + -- + -- @function utils.hmac_sha256 + -- @tparam string key key + -- @tparam string value value + -- @treturn string|nil message authentication code (32 bytes) + -- @treturn string|nil error message + -- + -- @usage + -- local utils = require "resty.session.utils" + -- local ikm = utils.rand_bytes(32) + -- local nonce = utils.rand_bytes(32) + -- local key, err = utils.derive_hmac_sha256_key(ikm, nonce) + -- local mac, err = utils.hmac_sha256(key, "hello") + hmac_sha256 = function(key, value) + if not hmac then + hmac = require "resty.openssl.hmac" + end + hmac_sha256 = hmac_sha256_real + return hmac_sha256(key, value) + end +end + + + +local load_storage do + local DSHM + local FILE + local MEMCACHED + local MYSQL + local POSTGRES + local REDIS + local REDIS_SENTINEL + local REDIS_CLUSTER + local SHM + local CUSTOM = {} + + --- + -- Loads session storage and creates a new instance using session configuration. + -- + -- @function utils.load_storage + -- @tparam string storage name of the storage to load + -- @tparam[opt] table configuration session configuration + -- @treturn table|nil instance of session storage + -- @treturn string|nil error message + -- + -- @usage + -- local postgres = require "resty.session.utils".load_storage("postgres", { + -- postgres = { + -- host = "127.0.0.1", + -- } + -- }) + load_storage = function(storage, configuration) + if storage == "cookie" then + return nil + + elseif storage == "dshm" then + if not DSHM then + DSHM = require("resty.session.dshm") + end + return DSHM.new(configuration and configuration.dshm) + + elseif storage == "file" then + if not FILE then + FILE = require("resty.session.file") + end + return FILE.new(configuration and configuration.file) + + elseif storage == "memcached" or storage == "memcache" then + if not MEMCACHED then + MEMCACHED = require("resty.session.memcached") + end + return MEMCACHED.new(configuration and configuration.memcached or configuration.memcache) + + elseif storage == "mysql" or storage == "mariadb" then + if not MYSQL then + MYSQL = require("resty.session.mysql") + end + return MYSQL.new(configuration and configuration.mysql or configuration.mariadb) + + elseif storage == "postgres" or storage == "postgresql" then + if not POSTGRES then + POSTGRES = require("resty.session.postgres") + end + return POSTGRES.new(configuration and configuration.postgres or configuration.postgresql) + + elseif storage == "redis" then + local cfg = configuration and configuration.redis + if cfg then + if cfg.nodes then + if not REDIS_CLUSTER then + REDIS_CLUSTER = require("resty.session.redis.cluster") + end + return REDIS_CLUSTER.new(cfg) + + elseif cfg.sentinels then + if not REDIS_SENTINEL then + REDIS_SENTINEL = require("resty.session.redis.sentinel") + end + return REDIS_SENTINEL.new(cfg) + end + end + + if not REDIS then + REDIS = require("resty.session.redis") + end + return REDIS.new(cfg) + + elseif storage == "shm" then + if not SHM then + SHM = require("resty.session.shm") + end + + return SHM.new(configuration and configuration.shm) + + else + if not CUSTOM[storage] then + CUSTOM[storage] = require(storage) + end + return CUSTOM[storage].new(configuration and configuration[storage]) + end + end +end + + +--- +-- Helper to format error messages. +-- +-- @function utils.errmsg +-- @tparam[opt] string|nil err a possible error coming from underlying library +-- @tparam string|nil msg error message +-- @tparam any ... arguments for formatting the msg +-- @treturn string formatted error message +-- +-- @usage +-- local utils = require "resty.session.utils" +-- local test = "aaaa" +-- local data, err = utils.deflate(test) +-- if not data then +-- print(utils.errmsg(err, "unable to deflate data '%s'", test) +-- end +local function errmsg(err, msg, ...) + if not msg then + if err then + return err + end + + return "unknown error" + end + + if select("#", ...) > 0 then + msg = fmt(msg, ...) + end + + if err then + return fmt("%s (%s)", msg, err) + end + + return msg +end + + +--- +-- Helper to create a delimited key. +-- +-- @function utils.get_name +-- @tparam table storage a storage implementation +-- @tparam string name name +-- @tparam string key key +-- @tparam string subject subject +-- @treturn string formatted and delimited name +local function get_name(storage, name, key, subject) + local prefix = storage.prefix + local suffix = storage.suffix + if prefix and suffix and subject then + return fmt("%s:%s:%s:%s:%s", prefix, name, key, subject, suffix) + elseif prefix and suffix then + return fmt("%s:%s:%s:%s", prefix, name, key, suffix) + elseif prefix and subject then + return fmt("%s:%s:%s:%s", prefix, name, key, subject) + elseif suffix and subject then + return fmt("%s:%s:%s:%s", name, key, subject, suffix) + elseif prefix then + return fmt("%s:%s:%s", prefix, name, key) + elseif suffix then + return fmt("%s:%s:%s", name, key, suffix) + elseif subject then + return fmt("%s:%s:%s", name, key, subject) + else + return fmt("%s:%s", name, key) + end +end + + +--- +-- Helper to turn on a flag. +-- +-- @function utils.set_flag +-- @tparam number flags flags on which the flag is applied +-- @tparam number flag flag that is applied +-- @treturn number flags with the flag applied +-- +-- @usage +-- local flags = 0x0000 +-- local FLAG_DOG = 0x001 +-- flags = utils.set_flag(flags, FLAG_DOG) +local function set_flag(options, flag) + return bor(options, flag) +end + + +--- +-- Helper to turn off a flag. +-- +-- @function utils.unset_flag +-- @tparam number flags flags on which the flag is removed +-- @tparam number flag flag that is removed +-- @treturn number flags with the flag removed +-- +-- @usage +-- local options = 0x0000 +-- local FLAG_DOG = 0x001 +-- flags = utils.set_flag(options, FLAG_DOG) +-- flags = utils.unset_flag(options, FLAG_DOG) +local function unset_flag(flags, flag) + return band(flags, bnot(flag)) +end + + +--- +-- Helper to check if flag is enabled. +-- +-- @function utils.has_flag +-- @tparam number flags flags on which the flag is checked +-- @tparam number flag flag that is checked +-- @treturn boolean true if flag has is present, otherwise false +-- +-- @usage +-- local flags = 0x0000 +-- local FLAG_DOG = 0x001 +-- local FLAG_BONE = 0x010 +-- flags = utils.set_flag(flags, FLAG_DOG) +-- flags = utils.set_flag(flags, FLAG_BONE) +-- print(utils.has_flag(flags, FLAG_BONE) +local function has_flag(flags, flag) + return band(flags, flag) ~= 0 +end + + +--- +-- Helper to get the value used to store metadata for a certain aud and sub +-- Empty exp means the session id has been invalidated +-- +-- @function utils.meta_get_value +-- @tparam string key storage key +-- @tparam string exp expiration +-- @treturn string the value to store in the metadata collection +local function meta_get_value(key, exp) + if not exp or exp == 0 then + return fmt("%s;", key) + end + return fmt("%s:%s;", key, encode_base64url(bpack(5, exp))) +end + + +local meta_get_next do + local KEY_OFFSET = 43 + local DEL_OFFSET = 1 + local EXP_OFFSET = 7 + + local COLON = byte(":") + + --- + -- Function to extract the next key and exp from a serialized + -- metadata list, starting from index + -- + -- @function utils.meta_get_next + -- @tparam string val list of key:exp; + -- @tparam number index start index + -- @treturn key string session id + -- @treturn err string error + -- @treturn exp number expiration + -- @treturn index number|nil index of the cursor + meta_get_next = function(val, index) + local key = sub(val, index, index + KEY_OFFSET - 1) + index = index + KEY_OFFSET + local del = byte(val, index + DEL_OFFSET - 1) + index = index + DEL_OFFSET + + if del ~= COLON then + return key, nil, nil, index + end + + local exp = sub(val, index, index + EXP_OFFSET - 1) + index = index + EXP_OFFSET + DEL_OFFSET + local exp, err = decode_base64url(exp) + if err then + return nil, err + end + + exp = bunpack(5, exp) + return key, nil, exp, index + end +end + + +--- +-- Function to filter out the latest valid key:exp from a +-- serialized list, used to store session metadata +-- +-- @function utils.meta_get_latest +-- @tparam string sessions list of key:exp; +-- @treturn table valid sessions and their exp +local function meta_get_latest(sessions, current_time) + current_time = current_time + + local latest = {} + local index = 1 + local length = #sessions + while index < length do + local key, err, exp + key, err, exp, index = meta_get_next(sessions, index) + if err then + return nil, err + end + + if exp and exp > current_time then + latest[key] = exp + else + latest[key] = nil + end + end + + return latest +end + + +return { + bpack = bpack, + bunpack = bunpack, + trim = trim, + encode_json = encode_json, + decode_json = decode_json, + encode_base64url = encode_base64url, + decode_base64url = decode_base64url, + base64_size = base64_size, + inflate = inflate, + deflate = deflate, + rand_bytes = rand_bytes, + sha256 = sha256, + derive_hkdf_sha256 = derive_hkdf_sha256, + derive_pbkdf2_hmac_sha256 = derive_pbkdf2_hmac_sha256, + derive_aes_gcm_256_key_and_iv = derive_aes_gcm_256_key_and_iv, + derive_hmac_sha256_key = derive_hmac_sha256_key, + encrypt_aes_256_gcm = encrypt_aes_256_gcm, + decrypt_aes_256_gcm = decrypt_aes_256_gcm, + hmac_sha256 = hmac_sha256, + load_storage = load_storage, + errmsg = errmsg, + get_name = get_name, + set_flag = set_flag, + unset_flag = unset_flag, + has_flag = has_flag, + meta_get_value = meta_get_value, + meta_get_next = meta_get_next, + meta_get_latest = meta_get_latest, +} diff --git a/lua-resty-session-4.0.0-1.rockspec b/lua-resty-session-4.0.0-1.rockspec new file mode 100644 index 00000000..fb9d332a --- /dev/null +++ b/lua-resty-session-4.0.0-1.rockspec @@ -0,0 +1,38 @@ +package = "lua-resty-session" +version = "4.0.0-1" +source = { + url = "git+https://github.com/bungle/lua-resty-session.git", + tag = "v4.0.0", +} +description = { + summary = "Session Library for OpenResty - Flexible and Secure", + detailed = "lua-resty-session is a secure, and flexible session library for OpenResty.", + homepage = "https://github.com/bungle/lua-resty-session", + maintainer = "Aapo Talvensaari <aapo.talvensaari@gmail.com>, Samuele Illuminati <samuele@konghq.com>", + license = "BSD", +} +dependencies = { + "lua >= 5.1", + "lua_pack >= 2.0.0", + "lua-ffi-zlib >= 0.5", + "lua-resty-openssl >= 0.8.0", +} +build = { + type = "builtin", + modules = { + ["resty.session"] = "lib/resty/session.lua", + ["resty.session.dshm"] = "lib/resty/session/dshm.lua", + ["resty.session.file"] = "lib/resty/session/file.lua", + ["resty.session.file.thread"] = "lib/resty/session/file/thread.lua", + ["resty.session.file.utils"] = "lib/resty/session/file/utils.lua", + ["resty.session.memcached"] = "lib/resty/session/memcached.lua", + ["resty.session.mysql"] = "lib/resty/session/mysql.lua", + ["resty.session.postgres"] = "lib/resty/session/postgres.lua", + ["resty.session.redis"] = "lib/resty/session/redis.lua", + ["resty.session.redis.cluster"] = "lib/resty/session/redis/cluster.lua", + ["resty.session.redis.sentinel"] = "lib/resty/session/redis/sentinel.lua", + ["resty.session.redis.common"] = "lib/resty/session/redis/common.lua", + ["resty.session.shm"] = "lib/resty/session/shm.lua", + ["resty.session.utils"] = "lib/resty/session/utils.lua", + }, +} diff --git a/lua-resty-session-dev-1.rockspec b/lua-resty-session-dev-1.rockspec deleted file mode 100644 index dc3eb85c..00000000 --- a/lua-resty-session-dev-1.rockspec +++ /dev/null @@ -1,39 +0,0 @@ -package = "lua-resty-session" -version = "dev-1" -source = { - url = "git://github.com/bungle/lua-resty-session.git" -} -description = { - summary = "Session Library for OpenResty – Flexible and Secure", - detailed = "lua-resty-session is a secure, and flexible session library for OpenResty.", - homepage = "https://github.com/bungle/lua-resty-session", - maintainer = "Aapo Talvensaari <aapo.talvensaari@gmail.com>", - license = "BSD" -} -dependencies = { - "lua >= 5.1" -} -build = { - type = "builtin", - modules = { - ["resty.session"] = "lib/resty/session.lua", - ["resty.session.identifiers.random"] = "lib/resty/session/identifiers/random.lua", - ["resty.session.storage.shm"] = "lib/resty/session/storage/shm.lua", - ["resty.session.storage.dshm"] = "lib/resty/session/storage/dshm.lua", - ["resty.session.storage.redis"] = "lib/resty/session/storage/redis.lua", - ["resty.session.storage.cookie"] = "lib/resty/session/storage/cookie.lua", - ["resty.session.storage.memcache"] = "lib/resty/session/storage/memcache.lua", - ["resty.session.storage.memcached"] = "lib/resty/session/storage/memcached.lua", - ["resty.session.strategies.default"] = "lib/resty/session/strategies/default.lua", - ["resty.session.strategies.regenerate"] = "lib/resty/session/strategies/regenerate.lua", - ["resty.session.hmac.sha1"] = "lib/resty/session/hmac/sha1.lua", - ["resty.session.ciphers.aes"] = "lib/resty/session/ciphers/aes.lua", - ["resty.session.ciphers.none"] = "lib/resty/session/ciphers/none.lua", - ["resty.session.compressors.none"] = "lib/resty/session/compressors/none.lua", - ["resty.session.compressors.zlib"] = "lib/resty/session/compressors/zlib.lua", - ["resty.session.encoders.hex"] = "lib/resty/session/encoders/hex.lua", - ["resty.session.encoders.base16"] = "lib/resty/session/encoders/base16.lua", - ["resty.session.encoders.base64"] = "lib/resty/session/encoders/base64.lua", - ["resty.session.serializers.json"] = "lib/resty/session/serializers/json.lua" - } -} diff --git a/spec/01-utils_spec.lua b/spec/01-utils_spec.lua new file mode 100644 index 00000000..bb96cba5 --- /dev/null +++ b/spec/01-utils_spec.lua @@ -0,0 +1,139 @@ +local utils = require "resty.session.utils" + + +local describe = describe +local tostring = tostring +local random = math.random +local assert = assert +local ipairs = ipairs +local match = string.match +local it = it + + +describe("Testing utils", function() + describe("pack/unpack data", function() + local function range_max(bytes) + if bytes == 8 then + bytes = bytes - 1 + end + return 2 ^ (8 * bytes) - 1 + end + + it("bpack and bunpack produce consistent output", function() + for _, i in ipairs{ 1, 2, 4, 8 } do + local input = random(1, range_max(i)) + local packed = utils.bpack(i, input) + local unpacked = utils.bunpack(i, packed) + + assert.equals(i, #packed) + assert.equals(unpacked, input) + end + end) + end) + + describe("trim", function() + it("characters are trimmed as expected", function() + local input = " \t\t\r\n\n\v\f\f\fyay\ntrim!\f\f\v\n\r\t " + local expected_output = "yay\ntrim!" + local output = utils.trim(input) + + assert.equals(output, expected_output) + end) + end) + + describe("encode/decode json", function() + it("produce consistent output", function() + local input = { + foo = "bar", + test = 123 + } + local encoded = utils.encode_json(input) + local decoded = utils.decode_json(encoded) + + assert.same(decoded, input) + end) + end) + + describe("encode/decode base64url", function() + it("produce consistent output", function() + local input = "<<<!?>>>?!" + local encoded = utils.encode_base64url(input) + + assert.is_nil(match(encoded, "[/=+]")) + local decoded = utils.decode_base64url(encoded) + assert.equals(input, decoded) + end) + end) + + describe("deflate/inflate", function() + it("produce consistent output", function() + local input = utils.rand_bytes(1024) + local deflated = utils.deflate(input) + local inflated = utils.inflate(deflated) + + assert.equals(input, inflated) + end) + end) + + describe("Derive keys, encrypt and decrypt", function() + local ikm = "some key material" + local nonce = "0000000000000000" + local usage = "encryption" + local size = 44 + + it("derives key of expected size with derive_hkdf_sha256", function() + local k_bytes, err = utils.derive_hkdf_sha256(ikm, nonce, usage, size) + assert.is_not_nil(k_bytes) + assert.is_nil(err) + assert.equals(size, #tostring(k_bytes)) + end) + + it("derives key of expected size with derive_pbkdf2_hmac_sha256", function() + local k_bytes, err = utils.derive_pbkdf2_hmac_sha256(ikm, nonce, usage, size) + assert.is_not_nil(k_bytes) + assert.is_nil(err) + assert.equals(size, #tostring(k_bytes)) + end) + + it("derives a valid key and calculates mac with it", function() + local key, err, mac + key, err = utils.derive_hmac_sha256_key(ikm, nonce) + assert.is_not_nil(key) + assert.is_nil(err) + assert.equals(32, #key) + + mac, err = utils.hmac_sha256(key, "some message") + assert.is_not_nil(mac) + assert.is_nil(err) + end) + + it("successfully derives key and iv; encryption and decryption succeeds", function() + local data = { + foo = "bar", + tab = { "val" }, + num = 123, + } + local aad = "some_aad" + local input_data = utils.encode_json(data) + + local key, err, iv, ciphertext, tag, plaintext + for _, slow in ipairs({ true, false }) do + key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce, slow) + assert.is_not_nil(key) + assert.is_not_nil(iv) + assert.is_nil(err) + + ciphertext, err, tag = utils.encrypt_aes_256_gcm(key, iv, input_data, aad) + assert.is_not_nil(ciphertext) + assert.is_not_nil(tag) + assert.is_nil(err) + + plaintext, err = utils.decrypt_aes_256_gcm(key, iv, ciphertext, aad, tag) + assert.is_not_nil(plaintext) + assert.is_nil(err) + + assert.equals(plaintext, input_data) + end + end) + end) +end) diff --git a/spec/02-file-utils_spec.lua b/spec/02-file-utils_spec.lua new file mode 100644 index 00000000..cca9b76c --- /dev/null +++ b/spec/02-file-utils_spec.lua @@ -0,0 +1,108 @@ +local file_utils = require "resty.session.file.utils" + + +local fmt = string.format +local describe = describe +local assert = assert +local it = it + + +describe("Testing file utils", function() + describe("validate file name", function() + local validate_file_name = file_utils.validate_file_name + local name = "name" + local sid1 = "ABCdef123_-iJqwertYUIOpasdfgHJKLzxcvbnmJuis" + local aud_sub1 = "some-aud:some-sub" + local simple_prefix = "pref" + local special_prefix = "@-_-" + local simple_suffix = "suff" + local special_suffix = "-_-@" + local filename_inv1 = "some.conf" + local filename_inv2 = "abc" + local filename_inv3 = name.."_".. "abc" + + it("validation fails with invalid files with prefix and suffix", function() + assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv1)) + assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv2)) + assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv3)) + end) + + + it("validation fails with invalid files with prefix only", function() + assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv1)) + assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv2)) + assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv3)) + end) + + it("validation fails with invalid files with suffix only", function() + assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv1)) + assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv2)) + assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv3)) + end) + + it("validation fails with invalid files with no prefix or suffix", function() + assert.is_false(validate_file_name(nil, nil, name, filename_inv1)) + assert.is_false(validate_file_name(nil, nil, name, filename_inv2)) + assert.is_false(validate_file_name(nil, nil, name, filename_inv3)) + end) + + it("validation passes with prefix and suffix", function() + local filename_sess = fmt("%s_%s_%s.%s", simple_prefix, name, sid1, simple_suffix) + local filename_meta = fmt("%s_%s_%s.%s", simple_prefix, name, aud_sub1, simple_suffix) + + assert.is_true(validate_file_name(simple_prefix, simple_suffix, name, filename_sess)) + assert.is_true(validate_file_name(simple_prefix, simple_suffix, name, filename_meta)) + assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv1)) + assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv2)) + end) + + it("validation passes with special prefix and suffix", function() + local sp_filename_sess = fmt("%s_%s_%s.%s", special_prefix, name, sid1, special_suffix) + local sp_filename_meta = fmt("%s_%s_%s.%s", special_prefix, name, aud_sub1, special_suffix) + + assert.is_true(validate_file_name(special_prefix, special_suffix, name, sp_filename_sess)) + assert.is_true(validate_file_name(special_prefix, special_suffix, name, sp_filename_meta)) + end) + + + it("validation passes with prefix", function() + local filename_sess = fmt("%s_%s_%s", simple_prefix, name, sid1) + local filename_meta = fmt("%s_%s_%s", simple_prefix, name, aud_sub1) + + assert.is_true(validate_file_name(simple_prefix, nil, name, filename_sess)) + assert.is_true(validate_file_name(simple_prefix, nil, name, filename_meta)) + end) + + it("validation passes with special prefix", function() + local sp_filename_sess = fmt("%s_%s_%s", special_prefix, name, sid1) + local sp_filename_meta = fmt("%s_%s_%s", special_prefix, name, aud_sub1) + + assert.is_true(validate_file_name(special_prefix, nil, name, sp_filename_sess)) + assert.is_true(validate_file_name(special_prefix, nil, name, sp_filename_meta)) + end) + + it("#only validation passes with suffix", function() + local filename_sess = fmt("%s_%s.%s", name, sid1, simple_suffix) + local filename_meta = fmt("%s_%s.%s", name, aud_sub1, simple_suffix) + + assert.is_true(validate_file_name(nil, simple_suffix, name, filename_sess)) + assert.is_true(validate_file_name(nil, simple_suffix, name, filename_meta)) + end) + + it("validation passes with special suffix", function() + local sp_filename_sess = fmt("%s_%s.%s", name, sid1, special_suffix) + local sp_filename_meta = fmt("%s_%s.%s", name, aud_sub1, special_suffix) + + assert.is_true(validate_file_name(nil, special_suffix, name, sp_filename_sess)) + assert.is_true(validate_file_name(nil, special_suffix, name, sp_filename_meta)) + end) + + it("validation passes with no prefix or suffix", function() + local filename_sess = fmt("%s_%s", name, sid1) + local filename_meta = fmt("%s_%s", name, aud_sub1) + + assert.is_true(validate_file_name(nil, nil, name, filename_sess)) + assert.is_true(validate_file_name(nil, nil, name, filename_meta)) + end) + end) +end) diff --git a/spec/03-session_spec.lua b/spec/03-session_spec.lua new file mode 100644 index 00000000..7ae740ed --- /dev/null +++ b/spec/03-session_spec.lua @@ -0,0 +1,212 @@ +local session = require "resty.session" + + +local before_each = before_each +local describe = describe +local assert = assert +local pcall = pcall +local it = it + + +local function extract_session_cookie(cookie_name, cookies) + local session_cookie = ngx.re.match(cookies, cookie_name .. "=([\\w-]+);") + return session_cookie and session_cookie[1] or "" +end + + +describe("Session", function() + local configuration = {} + + describe("instance methods behave as expected", function() + local cookie_name = "session_cookie" + local test_key = "test_key" + local data = "test_data" + local test_subject = "test_subject" + local test_audience = "test_audience" + + local function test_session_set_get(s) + assert.is_nil( + s:get(test_key) or + s:get(test_subject) or + s:get(test_audience) + ) + + s:set(test_key, data) + s:set_subject(test_subject) + s:set_audience(test_audience) + + assert.equals(s:get(test_key), data) + assert.equals(s:get_subject(), test_subject) + assert.equals(s:get_audience(), test_audience) + end + + local function test_session_save(s, cookies) + session.__set_ngx_header(cookies) + + local ok, err = s:save() + + assert.equals(s.state, "open") + assert.is_true(ok) + assert.is_nil(err) + assert.is_not_nil(s.meta) + assert.is_not_nil(s.meta.data_size) + assert(s.meta.data_size > 0) + + local session_cookie = extract_session_cookie(cookie_name, cookies["Set-Cookie"]) + return session_cookie + end + + local function test_session_close_open(s, session_cookie) + s:close() + + assert.equals(s.state, "closed") + + local ok, err = pcall(s.get, s, "anything") + assert.is_false(ok) + assert.matches("unable to get session data", err) + + session.__set_ngx_var({ + ["cookie_" .. cookie_name] = session_cookie + }) + + ok, err = s:open() + assert.is_true(ok) + assert.is_nil(err) + assert.equals(s.state, "open") + assert.equals(data, s:get(test_key)) + end + + local function test_session_touch(s) + local ok, err = s:touch() + assert.is_true(ok) + assert.is_nil(err) + assert.equals(s.state, "open") + end + + local function test_session_destroy_open(s) + local cookies = {} + + session.__set_ngx_header(cookies) + + local ok, err = s:destroy() + assert.is_true(ok) + assert.is_nil(err) + assert.equals(s.state, "closed") + + ok, err = pcall(s.get, s, "anything") + assert.is_false(ok) + assert.matches("unable to get session data", err) + + local session_cookie = extract_session_cookie(cookie_name, cookies["Set-Cookie"]) -- empty + + session.__set_ngx_var({ + ["cookie_" .. cookie_name] = session_cookie + }) + + ok, err = s:open() + assert.is_nil(ok) + assert.equals("invalid session header", err) + assert.equals(s.state, "closed") + + ok, err = pcall(s.get, s, "anything") + assert.is_false(ok) + assert.matches("unable to get session data", err) + end + + local function test_session(s) + local session_cookie + local cookies = {} + + test_session_set_get(s) + session_cookie = test_session_save(s, cookies) + test_session_close_open(s, session_cookie) + test_session_touch(s) + test_session_destroy_open(s) + end + + before_each(function() + configuration = { + cookie_name = cookie_name + } + end) + + it("with default values", function() + session.init(configuration) + + local s = session.new() + assert.is_not_nil(s) + test_session(s) + end) + + it("with custom secret", function() + configuration.secret = "t" + session.init(configuration) + + local s = session.new() + assert.is_not_nil(s) + test_session(s) + end) + + it("custom ikm takes precedence on secret", function() + configuration.secret = "t" + configuration.ikm = "00000000000000000000000000000000" + session.init(configuration) + + local s = session.new() + assert.is_not_nil(s) + test_session(s) + + assert.equals(configuration.ikm, s.meta.ikm) + end) + end) + + describe("Fields validation", function() + describe("init validates fields", function() + before_each(function() + configuration = {} + end) + + it("custom ikm must be 32 bytes", function() + configuration.ikm = "12345" + + local ok, err = pcall(session.init,configuration) + assert.is_false(ok) + assert.matches("invalid ikm size", err) + end) + + it("custom ikm_fallbacks must be 32 bytes", function() + configuration.ikm_fallbacks = { + "00000000000000000000000000000000", + "123456", + } + + local ok, err = pcall(session.init,configuration) + assert.is_false(ok) + assert.matches("invalid ikm size in ikm_fallbacks", err) + end) + end) + + describe("new validates fields", function() + before_each(function() + configuration = {} + end) + + it("custom ikm must be 32 bytes", function() + configuration.ikm = "12345" + local ok, err = pcall(session.new, configuration) + assert.is_false(ok) + assert.matches("invalid ikm size", err) + end) + + it("custom ikm_fallbacks must be 32 bytes", function() + configuration.ikm_fallbacks = { + "00000000000000000000000000000000", + "123456", + } + local ok, err = pcall(session.new, configuration) + assert.is_false(ok) + assert.matches("invalid ikm size", err) + end) + end) + end) +end) diff --git a/spec/04-storage-1_spec.lua b/spec/04-storage-1_spec.lua new file mode 100644 index 00000000..261dafac --- /dev/null +++ b/spec/04-storage-1_spec.lua @@ -0,0 +1,208 @@ +--- +-- Ensure to keep the tests consistent with those in 05-storage-1_spec.lua + + +local utils = require "resty.session.utils" + + +local before_each = before_each +local after_each = after_each +local lazy_setup = lazy_setup +local describe = describe +local ipairs = ipairs +local assert = assert +local sleep = ngx.sleep +local time = ngx.time +local it = it + + +local storage_configs = { + file = { + suffix = "session", + }, + shm = { + prefix = "sessions", + connect_timeout = 10000, + send_timeout = 10000, + read_timeout = 10000, + } +} + + +for _, st in ipairs({ "file", "shm" }) do + describe("Storage tests 1", function() + local current_time + local storage + local long_ttl = 60 + local short_ttl = 2 + local key = "test_key_1iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local key1 = "test_key_2iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local key2 = "test_key_3iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local old_key = "old_key_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local name = "test_name" + local value = "test_value" + + lazy_setup(function() + local conf = { + remember = true, + store_metadata = true, + } + conf[st] = storage_configs[st] + storage = utils.load_storage(st, conf) + assert.is_not_nil(storage) + end) + + before_each(function() + current_time = time() + end) + + describe("[#" .. st .. "] storage: SET + GET", function() + local audiences = { "foo", "bar" } + local subjects = { "john", "jane" } + + local metadata = { + audiences = audiences, + subjects = subjects, + } + + after_each(function() + storage:delete(name, key, current_time, metadata) + storage:delete(name, key1, current_time, metadata) + storage:delete(name, key2, current_time, metadata) + end) + + it("SET: simple set does not return errors, GET fetches value correctly", function() + local ok = storage:set(name, key, value, long_ttl, current_time) + assert.is_not_nil(ok) + + local v, err = storage:get(name, key, current_time) + assert.is_not_nil(v) + assert.is_nil(err) + assert.equals(v, value) + end) + + it("SET: with metadata and remember works correctly", function() + local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + local v, err = storage:get(name, key, time()) + assert.is_not_nil(v) + assert.is_nil(err) + assert.equals(v, value) + end) + + it("SET: with metadata (long ttl) correctly appends metadata to collection", function() + local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata, true) + ok = ok and storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true) + ok = ok and storage:set(name, key2, value, long_ttl, current_time, nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + for i = 1, #audiences do + local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) + assert.is_not_nil(meta_values) + assert.truthy(meta_values[key ]) + assert.truthy(meta_values[key1]) + assert.truthy(meta_values[key2]) + end + end) + + it("SET: with metadata (short ttl) correctly expires metadata", function() + local ok = storage:set(name, key, value, short_ttl, current_time, nil, nil, metadata, true) + + sleep(short_ttl + 1) + + ok = ok and storage:set(name, key1, value, long_ttl, time(), nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + for i = 1, #audiences do + local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) + assert.falsy(meta_values[key]) + assert.truthy(meta_values[key1]) + end + end) + + it("SET: with old_key correctly applies stale ttl on old key", function() + local stale_ttl = 1 + + local ok = storage:set(name, old_key, value, long_ttl, current_time) + assert.is_not_nil(ok) + + ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, false) + assert.is_not_nil(ok) + + sleep(3) + + local v = storage:get(name, old_key, time()) + assert.is_nil(v) + end) + + it("SET: remember deletes file in old_key", function() + local stale_ttl = long_ttl + + local ok = storage:set(name, old_key, value, long_ttl, current_time) + assert.is_not_nil(ok) + + ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, true) + assert.is_not_nil(ok) + + local v = storage:get(name, old_key, current_time) + assert.is_nil(v) + end) + + it("SET: ttl works as expected", function() + local ok = storage:set(name, key, value, short_ttl, current_time) + assert.is_not_nil(ok) + + sleep(3) + + local v = storage:get(name, key, time()) + assert.is_nil(v) + end) + end) + + describe("[#" .. st .. "] storage: DELETE", function() + local audiences = { "foo" } + local subjects = { "john" } + + local metadata = { + audiences = audiences, + subjects = subjects, + } + + it("deleted file is really deleted", function() + local ok = storage:set(name, key, value, short_ttl, current_time) + assert.is_not_nil(ok) + + storage:delete(name, key, current_time, nil) + + local v = storage:get(name, key, current_time) + assert.is_nil(v) + end) + + it("with metadata correctly deletes metadata collection", function() + local ok = storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + for i = 1, #audiences do + local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) + assert.truthy(meta_values[key1]) + ok = storage:delete(name, key1, time(), metadata) + assert.is_not_nil(ok) + + sleep(2) + + meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) or {} + assert.falsy(meta_values[key1]) + end + end) + end) + end) +end diff --git a/spec/05-storage-2_spec.lua b/spec/05-storage-2_spec.lua new file mode 100644 index 00000000..d5be033e --- /dev/null +++ b/spec/05-storage-2_spec.lua @@ -0,0 +1,263 @@ +--- +-- For now these tests don't run on CI. +-- Ensure to keep the tests consistent with those in 04-storage-1_spec.lua + + +local utils = require "resty.session.utils" + + +local before_each = before_each +local after_each = after_each +local lazy_setup = lazy_setup +local describe = describe +local ipairs = ipairs +local assert = assert +local sleep = ngx.sleep +local time = ngx.time +local it = it + + +local storage_configs = { + mysql = { + username = "root", + password = "password", + database = "test", + }, + postgres = { + username = "postgres", + password = "password", + database = "test", + }, + redis = { + prefix = "sessions", + }, + redis_sentinel = { + prefix = "sessions", + password = "password", + sentinels = { + { host = "127.0.0.1", port = "26379" } + }, + connect_timeout = 10000, + send_timeout = 10000, + read_timeout = 10000, + }, + redis_cluster = { + password = "password", + nodes = { + { ip = "127.0.0.1", port = "6380" } + }, + name = "somecluster", + lock_zone = "sessions", + connect_timeout = 10000, + send_timeout = 10000, + read_timeout = 10000, + }, + memcached = { + prefix = "sessions", + connect_timeout = 10000, + send_timeout = 10000, + read_timeout = 10000, + }, + dshm = { + prefix = "sessions", + connect_timeout = 10000, + send_timeout = 10000, + read_timeout = 10000, + }, +} + + +local function storage_type(ty) + if ty == "redis_cluster" or ty == "redis_sentinel" then + return "redis" + end + return ty +end + + +for _, st in ipairs({ + "memcached", + "mysql", + "postgres", + "redis", + "redis_cluster", + "redis_sentinel", + "dshm" +}) do + describe("Storage tests 2 #noci", function() + local current_time + local storage + local long_ttl = 60 + local short_ttl = 2 + local key = "test_key_1iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local key1 = "test_key_2iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local key2 = "test_key_3iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local old_key = "old_key_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + local name = "test_name" + local value = "test_value" + + lazy_setup(function() + local conf = { + remember = true, + store_metadata = true, + } + conf[storage_type(st)] = storage_configs[st] + storage = utils.load_storage(storage_type(st), conf) + assert.is_not_nil(storage) + end) + + before_each(function() + current_time = time() + end) + + describe("[#" .. st .. "] storage: SET + GET", function() + local audiences = { "foo", "bar" } + local subjects = { "john", "jane" } + + local metadata = { + audiences = audiences, + subjects = subjects, + } + + after_each(function() + current_time = time() + storage:delete(name, key, current_time, metadata) + storage:delete(name, key1, current_time, metadata) + storage:delete(name, key2, current_time, metadata) + end) + + it("SET: simple set does not return errors, GET fetches value correctly", function() + local ok = storage:set(name, key, value, long_ttl, current_time) + assert.is_not_nil(ok) + + local v, err = storage:get(name, key, current_time) + assert.is_not_nil(v) + assert.is_nil(err) + assert.equals(v, value) + end) + + it("SET: with metadata and remember works correctly", function() + local ok = storage:set(name, key, value, long_ttl, time(), nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + local v, err = storage:get(name, key, time()) + assert.is_not_nil(v) + assert.is_nil(err) + assert.equals(v, value) + end) + + it("SET: with metadata (long ttl) correctly appends metadata to collection", function() + local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata, true) + ok = ok and storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true) + ok = ok and storage:set(name, key2, value, long_ttl, current_time, nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + for i = 1, #audiences do + local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) + assert.is_not_nil(meta_values) + assert.truthy(meta_values[key ]) + assert.truthy(meta_values[key1]) + assert.truthy(meta_values[key2]) + end + end) + + it("SET: with metadata (short ttl) correctly expires metadata", function() + local ok = storage:set(name, key, value, short_ttl, current_time, nil, nil, metadata, true) + + sleep(short_ttl + 1) + + ok = ok and storage:set(name, key1, value, long_ttl, time(), nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + for i = 1, #audiences do + local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) + assert.falsy(meta_values[key]) + assert.truthy(meta_values[key1]) + end + end) + + it("SET: with old_key correctly applies stale ttl on old key", function() + local stale_ttl = 1 + + local ok = storage:set(name, old_key, value, long_ttl, current_time) + assert.is_not_nil(ok) + + ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, false) + assert.is_not_nil(ok) + + sleep(3) + + local v = storage:get(name, old_key, time()) + assert.is_nil(v) + end) + + it("SET: remember deletes file in old_key", function() + local stale_ttl = long_ttl + local ok = storage:set(name, old_key, value, long_ttl, current_time) + assert.is_not_nil(ok) + + ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil, true) + assert.is_not_nil(ok) + + local v = storage:get(name, old_key, current_time) + assert.is_nil(v) + end) + + it("SET: ttl works as expected", function() + local ok = storage:set(name, key, value, short_ttl, current_time) + assert.is_not_nil(ok) + + sleep(3) + + local v = storage:get(name, key, time()) + assert.is_nil(v) + end) + end) + + describe("[#" .. st .. "] storage: DELETE", function() + local audiences = { "foo" } + local subjects = { "john" } + + local metadata = { + audiences = audiences, + subjects = subjects, + } + + it("deleted file is really deleted", function() + local ok = storage:set(name, key, value, short_ttl, current_time) + assert.is_not_nil(ok) + + storage:delete(name, key, current_time) + + local v = storage:get(name, key, current_time) + assert.is_nil(v) + end) + + it("with metadata correctly deletes metadata collection", function() + local ok = storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata, true) + assert.is_not_nil(ok) + + sleep(1) + + for i = 1, #audiences do + local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) + assert.truthy(meta_values[key1]) + + ok = storage:delete(name, key1, time(), metadata) + assert.is_not_nil(ok) + + sleep(2) + + meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) or {} + assert.falsy(meta_values[key1]) + end + end) + end) + end) +end