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.

Digest Auth - JSON
#1
Hello!
Is there anyone that can provide me with a script to recieve a JSON table from an API using the Digest Auth?
Reply
#2
See this thread: https://forum.logicmachine.net/showthrea...64#pid9364
Reply
#3
I tried the script from the thread but i got this as a result:

* arg: 1
* string:
* arg: 2
* string: missing credentials in url
* arg: 3
* nil

The credentials are in the url like: https://myemail@gmail.com:password@url
Is it because the username is an email address or is there something else what's wrong? I try to get the data of some JSON string.
When i try the complete url in the browser it does work.

(15.07.2023, 14:44)Joep Wrote: I tried the script from the thread but i got this as a result:

* arg: 1
  * string:
* arg: 2
  * string: missing credentials in url
* arg: 3
  * nil

The credentials are in the url like: https://myemail@gmail.com:password@url
Is it because the username is an email address or is there something else what's wrong? I try to get the data of some JSON string.
When i try the complete url in the browser it does work.

Yes the email address for the user seems to be the problem. When logging log(url.user) i just got the first part of the email address back and not the complete email address. That makes sense as the part after the @ is seen as the url of course. But how to solve this as i can't change the user.
Reply
#4
Google is your best friend..

I replaced the @ with %40 so the credentials part is solved. But now i get this error: missing realm/nonce from response
I have no idea what it means.. How can i solve this? I'm trying to get the status of my sauna controllers API https://api.huum.eu/action/home/status
Reply
#5
(15.07.2023, 15:58)Joep Wrote: Google is your best friend..

I replaced the @ with %40 so the credentials part is solved. But now i get this error: missing realm/nonce from response
I have no idea what it means.. How can i solve this? I'm trying to get the status of my sauna controllers API https://api.huum.eu/action/home/status

This is the raw header: 
WWW-Authenticate Basic realm="HUUM api"

Is it a problem due to the capital characters or the whitespace in the header name?
Hope someone can help me out. Below the code so far.

Code:
local skthttp = require('socket.http')
local skturl = require('socket.url')
local ltn12 = require('ltn12')
local md5sum = require('encdec').md5

local hash = function(...)
  return md5sum(table.concat({...}, ':'))
end

local parse_header = function(header)
  local result = {}
  for key, value in (header .. ','):gmatch('(%w+)=(.-),') do
    if value:sub(1, 1) == '"' then -- strip quotes
      result[ key:lower() ] = value:sub(2, -2)
    else
      result[ key:lower() ] = value
    end
  end
  return result
end

local make_digest_header = function(headers)
  local digest = {}
  for _, header in ipairs(headers) do
    if not header.unquote then
      header[ 2 ] = '"' .. header[ 2 ] .. '"'
    end

    digest[ #digest + 1 ] = header[ 1 ] .. '=' .. header[ 2 ]
  end
  return 'Digest ' .. table.concat(digest, ', ')
end

local _request = function(req)
  if not req.url then
    return nil, 'missing url'
  end

  local url = skturl.parse(req.url)
  local user, password = url.user, url.password
  local sink = req.sink

  if not user or not password then
    return nil, 'missing credentials in url'
  end

  url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
  req.url = skturl.build(url)
  local source
  if req.source then
    local chunks = {}
    local capture = function(chunk)
      if chunk then
        chunks[ #chunks + 1 ] = chunk
      end
      return chunk
    end
    local chunk_id = 0
    source = function()
      chunk_id = chunk_id + 1
      return chunks[ chunk_id ]
    end
    req.source = ltn12.source.chain(req.source, capture)
  end
  req.sink = nil
  local body, code, hdrs = skthttp.request(req)
  if code == 401 and hdrs['www-authenticate'] then
    local ht = parse_header(hdrs['www-authenticate'])
    if not ht.realm or not ht.nonce then
      return nil, 'missing realm/nonce from response'
    end
    local qop = ht.qop
    if qop and qop ~= 'auth' then
      return nil, 'unsupported qop ' .. tostring(qop)
    end
    if ht.algorithm and ht.algorithm:lower() ~= 'md5' then
      return nil, 'unsupported algo ' .. tostring(ht.algorithm)
    end
    local nc = '00000001'
    local cnonce = string.format('%08x', os.time())
    local uri = skturl.build({ path = url.path, query = url.query })
    local method = req.method or 'GET'
    local response = hash(
      hash(user, ht.realm, password),
      ht.nonce,
      nc,
      cnonce,
      'auth',
      hash(method, uri)
    )
    req.headers = req.headers or {}
    local auth = {
      { 'username', user },
      { 'realm', ht.realm },
      { 'nonce', ht.nonce },
      { 'uri', uri },
      { 'cnonce', cnonce },
      { 'nc', nc, unquote = true },
      { 'qop', 'auth' },
      { 'algorithm', 'MD5' },
      { 'response', response },
    }
    if ht.opaque then
      table.insert(auth, { 'opaque', ht.opaque })
    end
    req.headers.authorization = make_digest_header(auth)
    if not req.headers.cookie and hdrs['set-cookie'] then
      -- not really correct but enough for httpbin
      local cookie = (hdrs['set-cookie'] .. ';'):match('(.-=.-)[;,]')
      if cookie then
        req.headers.cookie = '$Version: 0; ' .. cookie .. ';'
      end
    end
    if req.source then
      req.source = source
    end
    req.sink = sink
    body, code, hdrs = skthttp.request(req)
  end

  return body, code, hdrs
end

local request = function(url)
  local t = type(url)
  if t == 'table' then
    return _request(table.clone(url))
  elseif t == 'string' then
    local req = {}
    local _, code, headers = _request({ url = url, sink = ltn12.sink.table(req) })
    return table.concat(req), code, headers
  end
end

url = 'https://myemail%40gmail.com:Password@api.huum.eu/action/home/status'
data, err, hdrs = request(url)

require('json')
data = json.pdecode(data)

log(data, err)

With the result:

* arg: 1
  * nil
* arg: 2
  * string: missing realm/nonce from response
Reply
#6
You need to use basic auth instead of digest. Simply use socket.http.request
Reply
#7
(17.07.2023, 15:20)admin Wrote: You need to use basic auth instead of digest. Simply use socket.http.request

I got a 403 error back. If i try the complete url in the webbrowser it works correct. I replaced the @ with %40 in the username of the url.

Sauna Huum status 19.07.2023 14:49:57
* string: {"state":{"code":403,"message":"Error 403. Access denied."}}
Reply
#8
Try passing username/password like in this example: https://forum.logicmachine.net/showthrea...8#pid26618
Try both username variants - with @ and %40.
Reply
#9
(19.07.2023, 13:04)admin Wrote: Try passing username/password like in this example: https://forum.logicmachine.net/showthrea...8#pid26618
Try both username variants - with @ and %40.

Yes that works like a charm Smile

Code:
http = require('socket.http')
mime = require('mime')
json = require('json')

result = http.request({
  url = 'http://api.huum.eu/action/home/status',
  headers = {
    Authorization = 'Basic ' .. mime.b64('username:password')
  }
})

data = json.pdecode(result)

log(data)

And now my next question is how to send a JSON POST command?

The url is then: https://api.huum.eu/action/home/start
And the JSON command: {'targetTemperature' : 80}

(19.07.2023, 13:55)Joep Wrote:
(19.07.2023, 13:04)admin Wrote: Try passing username/password like in this example: https://forum.logicmachine.net/showthrea...8#pid26618
Try both username variants - with @ and %40.

Yes that works like a charm Smile

Code:
http = require('socket.http')
mime = require('mime')
json = require('json')

result = http.request({
  url = 'http://api.huum.eu/action/home/status',
  headers = {
    Authorization = 'Basic ' .. mime.b64('username:password')
  }
})

data = json.pdecode(result)

log(data)

And now my next question is how to send a JSON POST command?

The url is then: https://api.huum.eu/action/home/start
And the JSON command: {'targetTemperature' : 80}

I already found the solution. Smile

Code:
function post(url, body)
  local ltn12 = require('ltn12')
  local http = require('socket.http')
  local sink = {}
  local res, err = http.request({
    url = url,
    method = 'POST',
    headers = {
      ['Authorization'] = 'Basic '..(mime.b64('username:password')),
      ['Content-Length'] = #body,
      ['Content-Type'] = 'application/json',
    },
    sink = ltn12.sink.table(sink),
    source = ltn12.source.string(body),
  })
  if res then
    return table.concat(sink)
  else
    return nil, err
  end
end
require('json')
url = 'https://api.huum.eu/action/home/start'
body = '{"targetTemperature" : 70}'
result = post(url, body)
data = json.pdecode(result)
log(data)
Reply


Forum Jump: