This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Display MJPEG stream in iframe with Reverse Proxy for auth
#1
Hi, 

Im using the followig code to create a reverse proxy to authenticate with a Camera NVR and attempt to display the MJPEG stream in an iframe. This is to display the nvr camera feed in the visu without the user needing to fill the auth each time.

Currently i can display a snapshot ( the /path 6 url ) but i would like display the MJPEG stream ( the /path5 url ).
The MJPEG stream works in the iframe without the proxy if i set the src as the nvr url directly and manually fill the auth form when it pops up.
The Auth is successfully prefilled using the proxy when displaying the snapshot but i have only been successful in display a snapshot and not the MJPEG stream url.

Also open to any alterations as im unsure if the current method will drain memory with the proxy handling the MJPEG stream.

current code below:

Code:
if not server then
    local http = require("socket.http")
    local ltn12 = require("ltn12")
    local url = require("socket.url")
    local md5 = require("encdec").md5 -- Using the md5 library for hash computation

    local port = 7673

    local urlMappings = {
        ["/path1"] = "http://nodejs.org", -- Replace with the target URL for path1 -- test proxy redirect
        ["/path5"] = "https://<NVR IP>/cgi-bin/mjpg/video.cgi?channel=3&subtype=1", -- Replace with the target URL for path5
       ["/path6"] = "https://<NVR IP>/cgi-bin/snapshot.cgi?channel=1", -- Replace with the target URL for path6
        -- Add more mappings as needed
    }

    -- Set up the login credentials for digest authentication
    local username = "user"
    local password = "password"

-- Function to generate the Digest authentication header with the correct nonce
local function generateDigestHeader(uri, method, realm, nonce, qop, opaque)
    local ha1 = md5(username .. ":" .. realm .. ":" .. password)
    local ha2 = md5(method .. ":" .. uri)
    local nc = "00000001"
    local cnonce = "0a4f113b"
    local response = md5(ha1 .. ":" .. nonce .. ":" .. nc .. ":" .. cnonce .. ":" .. qop .. ":" .. ha2)
    return string.format('Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s", nc=%s, cnonce="%s", qop="%s", opaque="%s"',
        username, realm, nonce, uri, response, nc, cnonce, qop, opaque)
end

    -- Function to handle incoming queries and perform digest authentication
    function handleQuery(query)
    local method, path, _ = query:match("(%u+)%s+([^%s]+)")
    path = path:gsub("?.*", "")

    local targetURL = urlMappings[path]

    if targetURL then
        -- Perform digest authentication before sending the response
        local response_body = {}
        local response_headers = {}
        local res, status_code, response_headers, status_string = http.request {
            url = targetURL,
            headers = {
                ["Accept"] = "*/*", -- Accept all content types
            },
            method = "HEAD",
            create = function()
                local conn = socket.tcp()
                conn:settimeout(5) -- Set a timeout for the initial connection
                return conn
            end,
            sink = ltn12.sink.table(response_body),
            headers = response_headers, -- Store the response headers in the table
        }

        if res and response_headers["www-authenticate"] then
            local realm = response_headers["www-authenticate"]:match('realm="(.-)"')
            local nonce = response_headers["www-authenticate"]:match('nonce="(.-)"')
            local qop = response_headers["www-authenticate"]:match('qop="(.-)"')
            local opaque = response_headers["www-authenticate"]:match('opaque="(.-)"')

            -- Perform the actual HTTPS request with digest authentication
            local auth_response_body = {}
            local auth_res, auth_status_code, auth_response_headers, auth_status_string = http.request {
                url = targetURL,
                headers = {
                    ["Authorization"] = generateDigestHeader(targetURL, "GET", realm, nonce, qop, opaque),
                    ["Accept"] = "*/*", -- Accept all content types
                },
                method = "GET",
                sink = ltn12.sink.table(auth_response_body),
                create = function()
                    local conn = socket.tcp()
                    conn:settimeout(10) -- Set a timeout for the actual request
                    return conn
                end,
            }
        log(auth_response_headers["content-type"])

            if auth_res and auth_status_code == 200 then
                -- Check if the response is an image (e.g., for snapshot requests)
                local content_type = auth_response_headers["content-type"] or ""
                local image_types = { ["image/jpeg"] = true, ["image/png"] = true, ["image/gif"] = true }

                if image_types[content_type] then
                    -- If it's an image, set the appropriate Content-Type header
                    local response = "HTTP/1.1 200 OK\r\nContent-Type: " .. content_type .. "\r\n\r\n" .. table.concat(auth_response_body)
                    log(query, "Success - Image")
                    return response
                else
                    -- For other types of responses, pass them through as they are
                    local response = "HTTP/1.1 200 OK\r\n\r\n" .. table.concat(auth_response_body)
                    log(query, "Success - Other")
                    return response
                end
            else
                -- If the request failed, log and return a 500 error response
                log(query, "Error: Digest authentication request failed with status code " .. auth_status_code)
                return "HTTP/1.1 500 Internal Server Error\r\n\r\n"
            end
        else
            -- If the initial connection failed or no digest headers were received, log and return a 500 error response
            log(query, "Error: Failed to establish a connection with the server or no digest headers received.")
            return "HTTP/1.1 500 Internal Server Error\r\n\r\n"
        end
    else
        return "HTTP/1.1 404 Not Found\r\n\r\n"
    end
end

    -- Create a server socket to listen for incoming queries
    local server = assert(socket.bind("*", port))
    log("Proxy server is listening on localhost:" .. port .. "\n")

    while true do
        local client = server:accept()
        local query = client:receive()

        local response = handleQuery(query)
        client:send(response)
        client:close()
    end
end
Reply


Messages In This Thread
Display MJPEG stream in iframe with Reverse Proxy for auth - by Diggerz - 26.07.2023, 01:25

Forum Jump: