Skip to content

Commit

Permalink
Implemented SameSite cookie flag, added small bug fix related to #27,…
Browse files Browse the repository at this point in the history
… and some cleanups, marking this as a Release 2.4.
  • Loading branch information
bungle committed Apr 17, 2016
1 parent 6a0609a commit 56918ea
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 104 deletions.
12 changes: 12 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
Changes with lua-resty-session 2.4 17 Apr 2016

*) Feature: Cookie will now have SameSite attribute set as "Lax" by
default. You can turn it off or set to "Strict" by configuration.

*) Change: Calling save will not also set session.id if the save
was called without calling start first.

See Also: https://github.com/bungle/lua-resty-session/issues/27

Thanks @hcaihao

Changes with lua-resty-session 2.3 16 Oct 2015

*) Bugfix: Fixes issue #19 where regenerating session would
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2014, Aapo Talvensaari
Copyright (c) 2014 – 2016, Aapo Talvensaari
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,14 @@ This can be configured with Nginx `set $session_cookie_path /forums/;`.
Nginx variable `host`. This can be configured with Nginx `set $session_cookie_domain openresty.org;`.
For `localhost` this is omitted.

#### string session.cookie.samesite

`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`, and `off`. Actually, setting this parameter anything else than
`Lax` or `Strict` will turn this off (but in general, you shouldn't do it). If you want better protection
against Cross Site Requet Forgery (CSRF), set this to `Strict`. Default value of `Lax` gives you quite a
good protection against CSRF, but `Strict` goes even further.

#### boolean session.cookie.secure

`session.cookie.secure` holds the value of the cookie `Secure` flag. meaning that when set the client will
Expand Down Expand Up @@ -830,6 +838,7 @@ 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 |;
Expand All @@ -850,7 +859,7 @@ set $session_cipher_rounds 1;
`lua-resty-session` uses two clause BSD license.

```
Copyright (c) 2015, Aapo Talvensaari
Copyright (c) 2014 – 2016 Aapo Talvensaari
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
Expand Down
155 changes: 89 additions & 66 deletions lib/resty/session.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
local ngx_var = ngx.var
local ngx_header = ngx.header
local require = require
local var = ngx.var
local header = ngx.header
local concat = table.concat
local hmac = ngx.hmac_sha1
local time = ngx.time
local http_time = ngx.http_time
local find = string.find
local type = type
local pcall = pcall
local tonumber = tonumber
local setmetatable = setmetatable
local getmetatable = getmetatable
local ffi = require "ffi"
local ffi_cdef = ffi.cdef
local ffi_new = ffi.new
Expand Down Expand Up @@ -33,50 +38,63 @@ end

local function setcookie(session, value, expires)
local c = session.cookie
local cookie = { session.name, "=", value or "" }
local domain = c.domain
local i = 3
local n = session.name .. "="
local k = { n, value or "" }
local d = c.domain
if expires then
cookie[#cookie + 1] = "; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0"
k[i] = "; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0"
i=i+1
elseif c.persistent then
cookie[#cookie + 1] = "; Expires="
cookie[#cookie + 1] = http_time(session.expires)
cookie[#cookie + 1] = "; Max-Age="
cookie[#cookie + 1] = c.lifetime
k[i] = "; Expires="
k[i+1] = http_time(session.expires)
k[i+2] = "; Max-Age="
k[i+3] = c.lifetime
i=i+4
end
if domain and domain ~= "localhost" and domain ~= "" then
cookie[#cookie + 1] = "; Domain="
cookie[#cookie + 1] = domain
if d and d ~= "localhost" and d ~= "" then
k[i] = "; Domain="
k[i+1] = d
i=i+2
end
k[i] = "; Path="
k[i+1] = c.path or "/"
i=i+2
if c.samesite == "Lax" or c.samesite == "Strict" then
k[i] = "; SameSite="
k[i+1] = c.samesite
i=i+2
end
cookie[#cookie + 1] = "; Path="
cookie[#cookie + 1] = c.path or "/"
if c.secure then
cookie[#cookie + 1] = "; Secure"
k[i] = "; Secure"
i=i+1
end
if c.httponly then
cookie[#cookie + 1] = "; HttpOnly"
k[i] = "; HttpOnly"
i=i+1
end
local needle = concat(cookie, nil, 1, 2)
cookie = concat(cookie)
local cookies = ngx_header["Set-Cookie"]
local t = type(cookies)
k = concat(k)
local s = header["Set-Cookie"]
local t = type(s)
if t == "table" then
local found = false
for i, c in ipairs(cookies) do
if c:find(needle, 1, true) == 1 then
cookies[i] = cookie
found = true
local f = false
local z = #s
for i=1, z do
if find(s[i], n, 1, true) == 1 then
s[i] = k
f = true
break
end
end
if not found then
cookies[#cookies + 1] = cookie
if not f then
s[z+1] = k
end
elseif t == "string" and cookies:find(needle, 1, true) ~= 1 then
cookies = { cookies, cookie }
elseif t == "string" and find(s, n, 1, true) ~= 1 then
s = { s, k }
else
cookies = cookie
s = k
end
ngx_header["Set-Cookie"] = cookies
header["Set-Cookie"] = s
return true
end

Expand Down Expand Up @@ -104,35 +122,36 @@ local function regenerate(session, flush)
end
end

local persistent = enabled(ngx_var.session_cookie_persistent or false)
local persistent = enabled(var.session_cookie_persistent or false)
local defaults = {
name = ngx_var.session_name or "session",
storage = ngx_var.session_storage or "cookie",
serializer = ngx_var.session_serializer or "json",
encoder = ngx_var.session_encoder or "base64",
cipher = ngx_var.session_cipher or "aes",
name = var.session_name or "session",
storage = var.session_storage or "cookie",
serializer = var.session_serializer or "json",
encoder = var.session_encoder or "base64",
cipher = var.session_cipher or "aes",
cookie = {
persistent = persistent,
renew = tonumber(ngx_var.session_cookie_renew) or 600,
lifetime = tonumber(ngx_var.session_cookie_lifetime) or 3600,
path = ngx_var.session_cookie_path or "/",
domain = ngx_var.session_cookie_domain,
secure = enabled(ngx_var.session_cookie_secure),
httponly = enabled(ngx_var.session_cookie_httponly or true),
delimiter = ngx_var.session_cookie_delimiter or "|"
renew = tonumber(var.session_cookie_renew) or 600,
lifetime = tonumber(var.session_cookie_lifetime) or 3600,
path = var.session_cookie_path or "/",
domain = var.session_cookie_domain,
secure = enabled(var.session_cookie_secure),
httponly = enabled(var.session_cookie_httponly or true),
samesite = var.session_cookie_samesite or "Lax",
delimiter = var.session_cookie_delimiter or "|"
}, check = {
ssi = enabled(ngx_var.session_check_ssi or persistent == false),
ua = enabled(ngx_var.session_check_ua or true),
scheme = enabled(ngx_var.session_check_scheme or true),
addr = enabled(ngx_var.session_check_addr or false)
ssi = enabled(var.session_check_ssi or persistent == 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)
}, identifier = {
length = tonumber(ngx_var.session_identifier_length) or 16
length = tonumber(var.session_identifier_length) or 16
}
}
defaults.secret = ngx_var.session_secret or random(32)
defaults.secret = var.session_secret or random(32)

local session = {
_VERSION = "2.2"
_VERSION = "2.3-dev"
}

session.__index = session
Expand Down Expand Up @@ -177,6 +196,7 @@ function session.new(opts)
domain = a.domain or b.domain,
secure = a.secure or b.secure,
httponly = a.httponly or b.httponly,
samesite = a.samesite or b.samesite,
delimiter = a.delimiter or b.delimiter
}, check = {
ssi = c.ssi or d.ssi,
Expand All @@ -201,43 +221,43 @@ function session.open(opts)
else
self = session.new(opts)
end
local scheme = ngx_header["X-Forwarded-Proto"]
local scheme = header["X-Forwarded-Proto"]
if self.cookie.secure == nil then
if scheme then
self.cookie.secure = scheme == "https"
else
self.cookie.secure = ngx_var.https == "on"
self.cookie.secure = var.https == "on"
end
end
scheme = self.check.scheme and (scheme or ngx_var.scheme or "") or ""
scheme = self.check.scheme and (scheme or var.scheme or "") or ""
local addr = ""
if self.check.addr then
addr = ngx_header["CF-Connecting-IP"] or
ngx_header["Fastly-Client-IP"] or
ngx_header["Incap-Client-IP"] or
ngx_header["X-Real-IP"]
addr = header["CF-Connecting-IP"] or
header["Fastly-Client-IP"] or
header["Incap-Client-IP"] or
header["X-Real-IP"]
if not addr then
addr = ngx_header["X-Forwarded-For"]
addr = header["X-Forwarded-For"]
if addr then
-- We shouldn't really get the left-most address, because of spoofing,
-- but this is better handled with a module, like nginx realip module,
-- anyway (see also: http://goo.gl/Z6u2oR).
local s = (addr:find(',', 1, true))
local s = find(addr, ',', 1, true)
if s then
addr = addr:sub(1, s - 1)
end
else
addr = ngx_var.remote_addr
addr = var.remote_addr
end
end
end
self.key = concat{
self.check.ssi and (ngx_var.ssl_session_id or "") or "",
self.check.ua and (ngx_var.http_user_agent or "") or "",
self.check.ssi and (var.ssl_session_id or "") or "",
self.check.ua and (var.http_user_agent or "") or "",
addr,
scheme
}
local cookie = ngx_var["cookie_" .. self.name]
local cookie = var["cookie_" .. self.name]
if cookie then
local i, e, d, h = self.storage:open(cookie, self.cookie.lifetime)
if i and e and e > time() and d and h then
Expand Down Expand Up @@ -283,8 +303,11 @@ function session:regenerate(flush)
return save(self)
end

function session:save()
return save(self, true)
function session:save(close)
if not self.id then
self.id = random(self.identifier.length)
end
return save(self, close)
end

function session:destroy()
Expand Down
18 changes: 9 additions & 9 deletions lib/resty/session/ciphers/aes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local tonumber = tonumber
local aes = require "resty.aes"
local cip = aes.cipher
local hashes = aes.hash
local ngx_var = ngx.var
local var = ngx.var

local CIPHER_MODES = {
ecb = "ecb",
Expand All @@ -22,10 +22,10 @@ local CIPHER_SIZES = {
}

local defaults = {
size = CIPHER_SIZES[ngx_var.session_aes_size] or 256,
mode = CIPHER_MODES[ngx_var.session_aes_mode] or "cbc",
hash = hashes[ngx_var.session_aes_hash] or "sha512",
rounds = tonumber(ngx_var.session_aes_rounds) or 1
size = CIPHER_SIZES[var.session_aes_size] or 256,
mode = CIPHER_MODES[var.session_aes_mode] or "cbc",
hash = hashes[var.session_aes_hash] or "sha512",
rounds = tonumber(var.session_aes_rounds) or 1
}

local cipher = {}
Expand All @@ -35,10 +35,10 @@ cipher.__index = cipher
function cipher.new(config)
local a = config.aes or defaults
return setmetatable({
size = CIPHER_MODES[a.size or defaults.size] or 256,
mode = CIPHER_MODES[a.mode or defaults.mode] or "cbc",
hash = hashes[a.hash or defaults.hash] or hashes.sha512,
rounds = tonumber(a.rounds or defaults.rounds) or 1
size = CIPHER_MODES[a.size or defaults.size] or 256,
mode = CIPHER_MODES[a.mode or defaults.mode] or "cbc",
hash = hashes[a.hash or defaults.hash] or hashes.sha512,
rounds = tonumber(a.rounds or defaults.rounds) or 1
}, cipher)
end

Expand Down
5 changes: 3 additions & 2 deletions lib/resty/session/encoders/base64.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local base64enc = ngx.encode_base64
local base64dec = ngx.decode_base64
local ngx = ngx
local base64enc = ngx.encode_base64
local base64dec = ngx.decode_base64

local ENCODE_CHARS = {
["+"] = "-",
Expand Down
19 changes: 10 additions & 9 deletions lib/resty/session/storage/memcache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ local concat = table.concat
local floor = math.floor
local sleep = ngx.sleep
local now = ngx.now
local var = ngx.var

local function enabled(val)
if val == nil then return nil end
return val == true or (val == "1" or val == "true" or val == "on")
end

local defaults = {
prefix = ngx.var.session_memcache_prefix or "sessions",
socket = ngx.var.session_memcache_socket,
host = ngx.var.session_memcache_host or "127.0.0.1",
port = tonumber(ngx.var.session_memcache_port) or 11211,
uselocking = enabled(ngx.var.session_memcache_uselocking or true),
spinlockwait = tonumber(ngx.var.session_memcache_spinlockwait) or 10000,
maxlockwait = tonumber(ngx.var.session_memcache_maxlockwait) or 30,
prefix = var.session_memcache_prefix or "sessions",
socket = var.session_memcache_socket,
host = var.session_memcache_host or "127.0.0.1",
port = tonumber(var.session_memcache_port) or 11211,
uselocking = enabled(var.session_memcache_uselocking or true),
spinlockwait = tonumber(var.session_memcache_spinlockwait) or 10000,
maxlockwait = tonumber(var.session_memcache_maxlockwait) or 30,
pool = {
timeout = tonumber(ngx.var.session_memcache_pool_timeout),
size = tonumber(ngx.var.session_memcache_pool_size)
timeout = tonumber(var.session_memcache_pool_timeout),
size = tonumber(var.session_memcache_pool_size)
}
}

Expand Down
Loading

0 comments on commit 56918ea

Please sign in to comment.