-- VID-11 Token Generator
--
-- Generate a sha256_hmac based token to allow user to authenticate
--
-- Auth Checks are:
-- 
--   - is referer in permitted_referrers?
--   - if httpref is not null, is the domain the same as in referrer?
--   - Is ip not in blacklisted_ips?
--
-- If the above passes, or the ip is in excluded_ips, then we should mint the token
-- otherwise, call sendFailed()

local table = table
local require = require
local string = string
local os = os
local sha256 = require "lib.sha256"
local json = require "lib.json"




-- Config section

-- How long should a token be valid for (seconds)
local tokenlife = 3600

-- What IPs should be excluded from checks?
local excluded_ips = {"127.0.0.1"}

-- What referrers do we allow?
local permitted_referrers = {"*.bentasker.co.uk"}

-- Blacklisted clients
local blacklisted_ips = {'127.0.2.1'}



-- Functions

function table.contains(table, element)
  -- From https://stackoverflow.com/questions/2282444/how-to-check-if-a-table-contains-an-element-in-lua
  for _, value in pairs(table) do
    if value == element then
      return true
    end
  end
  return false
end


function strSplit(delim,str)
    -- From https://snippets.bentasker.co.uk/page-1705231204-Split-string-on-Delimiter-LUA.html
    local t = {}

    for substr in string.gmatch(str, "[^".. delim.. "]*") do
        if substr ~= nil and string.len(substr) > 0 then
            table.insert(t,substr)
        end
    end

    return t
end

local hex_to_char = function(x)
  -- From https://stackoverflow.com/questions/20282054/how-to-urldecode-a-request-uri-string-in-lua
  return string.char(tonumber(x, 16))
end

local unescape = function(url)
  -- From https://stackoverflow.com/questions/20282054/how-to-urldecode-a-request-uri-string-in-lua
  return url:gsub("%%(%x%x)", hex_to_char)
end




local function sendSuccessful(tok,expires,debug)
    -- response format should be
    -- {"status": true, "token": "OqBvCvqg49QJgGKDHFbdNccB", "exp": 0} 
    local r = {status = true, token = tok, exp = expires, debug = debug}
    return json.encode(r)
end


local function mint_token(path,expires,ip,secret)
    local mint = {path,expires,ip}
    local mintstr = table.concat(mint,':')
    -- print mintstr
    return sha256.hmac_sha256(secret,mintstr)
end


local function getPath(str,sep)
    -- From https://stackoverflow.com/questions/9102126/lua-return-directory-path-from-path
    sep=sep or'/'
    return str:match("(.*"..sep..")")
end


local function sendFailed(reason)
    local r = {status = false, token = 'failed', exp = '-1', msg = reason}
    
    
    if ngx ~= nil
    then
        ngx.status = 403
        ngx.say(json.encode(r))
        ngx.exit(403)
    else
        print("Denying")
        print(json.encode(r)) 
    end
        
end



local function check_referrer(referrer,httpref,permitted)

    
    local refsplit = strSplit("/",referrer)
    local refdomain = refsplit[2]
    
    -- First, if a HTTP referrer header has been included in the request (not mandatory)
    -- check the domain matches the one submitted in the query string
    if httpref ~= nil
    then
        local hsplit = strSplit("/",httpref)
        if hsplit[2] ~= refdomain
        then
            return false
        end
    end
    
    local first
    local splitd
    -- Next, check whether the domain exists in permitted_referrers
    for key,allowed in pairs(permitted_referrers)
    do
       
       -- Check whether the whitelist uses a wild card
       first = string.sub(allowed, 1, 1)
       if first == "*"
       then
          -- remove the first label from the referring domain, and then do a comparison
          splitd = strSplit(".",refdomain)
          splitd[1] = '*'
          if table.concat(splitd,".") == allowed
          then
              -- print("Matched wildcard")
              return true
          end
       end
       
       -- Otherwise, we just need to check for a direct match 
       if refdomain == allowed
       then
           return true
       end
       
    end
    
    return false
end

-- Function main
local debug = nil
local enable_debug = nil

-- Set various config items
if ngx ~= nil
then
    -- We're running in OpenResty, run for real
    referer = unescape(ngx.var.arg_ref)
    vidpath = unescape(ngx.var.arg_vidpath)
    ip = ngx.var.remote_addr
    secret = ngx.var.secret
    httpref = ngx.var.http_referer
    
    if ngx.var.arg_debug ~= nil
    then
        enable_debug = 1
    end
    
else
    print("CLI detected, using preset values")
    -- Testing from CLI, use static values
    referer = 'https://snippets.bentasker.co.uk/foobar'
    vidpath = '2019/08/16.m3u8'
    ip = '127.0.1.1'
    secret = '1234abcd'
    httpref = 'https://snippets.bentasker.co.uk/foobar'
    
end



if table.contains(blacklisted_ips,ip)
then
    sendFailed("Users IP is blacklisted")
    return
end

if check_referrer(referer,httpref,permitted_referrers) ~= true
then
    sendFailed("Referred from unauthorised source")
    return
end



-- Calculate when the token should expire
local expires = os.time() + tokenlife

local tok = mint_token(getPath(vidpath),expires,ip,secret)


if enable_debug ~= nil
then
    debug = {vidpath = vidpath, IP = ip, Expires = expires}
end

if ngx ~= nil
then
    ngx.print(sendSuccessful(tok,expires,debug))
else
    print(sendSuccessful(tok,expires,debug))
end
