diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 67b2741542..8b17d012c0 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -32,6 +32,7 @@ gMe = { '--port': 3128, '--config': '/dev/null', '--debug': False, + 'bearer.transformed.year': "", 'server': None } @@ -46,6 +47,22 @@ gConfigType = { gConfigNeeded = [ '--allowed.domains', '--bearer.insecure' ] +def bearer_transform(): + """ + Transform the raw bearer token to the network handshaked token, + if and when needed. + """ + global gMe + year = str(time.gmtime().tm_year) + if gMe['bearer.transformed.year'] == year: + return + import hashlib + s256 = hashlib.sha256(year.encode('utf-8')) + s256.update(gMe['--bearer.insecure'].encode('utf-8')) + gMe['--bearer.transformed'] = s256.hexdigest() + gMe['bearer.transformed.year'] = year + + class ProxyHandler(http.server.BaseHTTPRequestHandler): """ Implements the logic for handling requests sent to this server. @@ -75,6 +92,7 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): Simple Bearer authorization ALERT: For multiple reasons, this is a very insecure implementation. """ + bearer_transform() authline = self.headers['Authorization'] if authline == None: return { 'AllOk': False, 'Msg': "No auth line" } @@ -83,7 +101,7 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): return { 'AllOk': False, 'Msg': "Invalid auth line" } if authlineA[0] != 'Bearer': return { 'AllOk': False, 'Msg': "Invalid auth type" } - if authlineA[1] != gMe['--bearer.insecure']: + if authlineA[1] != gMe['--bearer.transformed']: return { 'AllOk': False, 'Msg': "Invalid auth" } return { 'AllOk': True, 'Msg': "Auth Ok" } @@ -93,17 +111,21 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): """ print(f"\n\n\nDBUG:ProxyHandler:GET:{self.address_string()}:{self.path}") print(f"DBUG:PH:Get:Headers:{self.headers}") - acGot = self.auth_check() - if not acGot['AllOk']: - self.send_error(400, f"WARN:{acGot['Msg']}") - return pr = urllib.parse.urlparse(self.path) print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: case '/urlraw': - handle_urlraw(self, pr) + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + else: + handle_urlraw(self, pr) case '/urltext': - handle_urltext(self, pr) + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + else: + handle_urltext(self, pr) case '/aum': handle_aum(self, pr) case _: diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 973ef77a76..25089be4bc 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -89,7 +89,12 @@ remember to content like head, scripts, styles, headers, footers, ... Be careful when accessing web through this and use it only with known safe sites. - * it allows one to specify a white list of allowed.domains, look into local.tools/simpleproxy.json + * look into local.tools/simpleproxy.json for specifying + + * the white list of allowed.domains + * the shared bearer token between server and client ui + + ### using the front end @@ -228,6 +233,10 @@ It is attached to the document object. Some of these can also be updated using t * proxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py + * proxyAuthInsecure - shared token between simpleproxy.py server and client ui, for accessing service provided by it. + + Shared token is currently hashed with the current year and inturn handshaked over the network. In future if required one could also include a dynamic token provided by simpleproxy server during /aum handshake and running counter or so into hashed token. ALERT: However do remember that currently the handshake occurs over http and not https, so others can snoop the network and get token. Per client ui running counter and random dynamic token can help mitigate things to some extent, if required in future. + * searchUrl - specify the search engine's search url template along with the tag SEARCHWORDS in place where the search words should be substituted at runtime. * toolCallResponseTimeoutMS - specifies the time (in msecs) for which the logic should wait for a tool call to respond @@ -419,7 +428,8 @@ The bundled simple proxy * tools/server/public_simplechat/local.tools/simpleproxy.py * it provides for a basic white list of allowed domains to access, to be specified by the end user. - This should help limit web access to a safe set of sites determined by the end user. + This should help limit web access to a safe set of sites determined by the end user. There is also + a provision for shared bearer token to be specified by the end user. * it tries to mimic the client/browser making the request to it by propogating header entries like user-agent, accept and accept-language from the got request to the generated request during proxying diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 0808c6d0b3..909fbdae7e 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -27,6 +27,13 @@ function get_gme() { } +function bearer_transform() { + let data = `${new Date().getUTCFullYear()}${get_gme().tools.proxyAuthInsecure}` + return crypto.subtle.digest('sha-256', new TextEncoder().encode(data)).then(ab=>{ + return Array.from(new Uint8Array(ab)).map(b=>b.toString(16).padStart(2,'0')).join('') + }) +} + /** * Helper http get logic wrt the bundled SimpleProxy server, * which helps execute a given proxy dependent tool call. @@ -43,10 +50,11 @@ function get_gme() { * @param {string} qkey * @param {string} qvalue */ -function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { +async function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { if (gToolsWorker.onmessage != null) { let newUrl = `${get_gme().tools.proxyUrl}/${path}?${qkey}=${qvalue}` - fetch(newUrl, { headers: { 'Authorization': `Bearer ${get_gme().tools.proxyAuthInsecure}` }}).then(resp => { + let btoken = await bearer_transform() + fetch(newUrl, { headers: { 'Authorization': `Bearer ${btoken}` }}).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); } @@ -70,9 +78,7 @@ function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { * @param {Object>} tcs */ async function proxyserver_tc_setup(tag, tcPath, tcName, tcsData, tcs) { - await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`, { - headers: { 'Authorization': `Bearer ${get_gme().tools.proxyAuthInsecure}` } - }).then(resp=>{ + await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log(`WARN:ToolWeb:${tag}:Dont forget to run the bundled local.tools/simpleproxy.py to enable me`) return