aboutsummaryrefslogtreecommitdiffstats
path: root/server/resty/session/211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
local type   = type
local concat = table.concat

local strategy = {}

function strategy.load(session, cookie, key, keep_lock)
  local storage    = session.storage
  local id         = cookie.id
  local id_encoded = session.encoder.encode(id)

  local data, err, tag
  if storage.open then
    data, err = storage:open(id_encoded, keep_lock)
    if not data then
      return nil, err or "cookie data was not found"
    end

  else
    data = cookie.data
  end

  local expires   = cookie.expires
  local usebefore = cookie.usebefore
  local hash      = cookie.hash

  if not key then
    key = concat{ id, expires, usebefore }
  end

  local hkey = session.hmac(session.secret, key)

  data, err, tag = session.cipher:decrypt(data, hkey, id, session.key, hash)
  if not data then
    if storage.close then
      storage:close(id_encoded)
    end

    return nil, err or "unable to decrypt data"
  end

  if tag then
    if tag ~= hash then
      if storage.close then
        storage:close(id_encoded)
      end

      return nil, "cookie has invalid tag"
    end

  else
    local input = concat{ key, data, session.key }
    if session.hmac(hkey, input) ~= hash then
      if storage.close then
        storage:close(id_encoded)
      end

      return nil, "cookie has invalid signature"
    end
  end

  data, err = session.compressor:decompress(data)
  if not data then
    if storage.close then
      storage:close(id_encoded)
    end

    return nil, err or "unable to decompress data"
  end

  data, err = session.serializer.deserialize(data)
  if not data then
    if storage.close then
      storage:close(id_encoded)
    end

    return nil, err or "unable to deserialize data"
  end

  session.id        = id
  session.expires   = expires
  session.usebefore = usebefore
  session.data      = type(data) == "table" and data or {}
  session.present   = true

  return true
end

function strategy.open(session, cookie, keep_lock)
  return strategy.load(session, cookie, nil, keep_lock)
end

function strategy.start(session)
  local storage = session.storage
  if not storage.start then
    return true
  end

  local id_encoded = session.encoder.encode(session.id)

  local ok, err = storage:start(id_encoded)
  if not ok then
    return nil, err or "unable to start session"
  end

  return true
end

function strategy.modify(session, action, close, key)
  local id         = session.id
  local id_encoded = session.encoder.encode(id)
  local storage    = session.storage
  local expires    = session.expires
  local usebefore  = session.usebefore
  local ttl        = expires - session.now

  if ttl <= 0 then
    if storage.close then
      storage:close(id_encoded)
    end

    return nil, "session is already expired"
  end

  if not key then
    key = concat{ id, expires, usebefore }
  end

  local data, err = session.serializer.serialize(session.data)
  if not data then
    if close and storage.close then
      storage:close(id_encoded)
    end

    return nil, err or "unable to serialize data"
  end

  data, err = session.compressor:compress(data)
  if not data then
    if close and storage.close then
      storage:close(id_encoded)
    end

    return nil, err or "unable to compress data"
  end

  local hkey = session.hmac(session.secret, key)

  local encrypted_data, tag
  encrypted_data, err, tag = session.cipher:encrypt(data, hkey, id, session.key)
  if not encrypted_data then
    if close and storage.close then
      storage:close(id_encoded)
    end

    return nil, err
  end

  local hash
  if tag then
    hash = tag
  else
    -- it would be better to calculate signature from encrypted_data,
    -- but this is kept for backward compatibility
    hash = session.hmac(hkey, concat{ key, data, session.key })
  end

  if action == "save" and storage.save then
    local ok
    ok, err = storage:save(id_encoded, ttl, encrypted_data, close)
    if not ok then
      return nil, err
    end
  elseif close and storage.close then
    local ok
    ok, err = storage:close(id_encoded)
    if not ok then
      return nil, err
    end
  end

  if usebefore then
    expires = expires .. ":" .. usebefore
  end

  hash = session.encoder.encode(hash)

  local cookie
  if storage.save then
    cookie = concat({ id_encoded, expires, hash }, "|")
  else
    local encoded_data = session.encoder.encode(encrypted_data)
    cookie = concat({ id_encoded, expires, encoded_data, hash }, "|")
  end

  return cookie
end

function strategy.touch(session, close)
  return strategy.modify(session, "touch", close)
end

function strategy.save(session, close)
  return strategy.modify(session, "save", close)
end

function strategy.destroy(session)
  local id = session.id
  if id then
    local storage = session.storage
    if storage.destroy then
      return storage:destroy(session.encoder.encode(id))
    elseif storage.close then
      return storage:close(session.encoder.encode(id))
    end
  end

  return true
end

function strategy.close(session)
  local id = session.id
  if id then
    local storage = session.storage
    if storage.close then
      return storage:close(session.encoder.encode(id))
    end
  end

  return true
end

return strategy