<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GitHub on Ash Tewari</title><link>https://www.tewari.info/tag/github/</link><description>Recent content in GitHub on Ash Tewari</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Mon, 06 Apr 2026 04:48:17 +0000</lastBuildDate><atom:link href="https://www.tewari.info/tag/github/feed.xml" rel="self" type="application/rss+xml"/><item><title>Giving OpenClaw Secure Access to Cloud Services Without Sharing Your Password</title><link>https://www.tewari.info/2026/04/06/device-code-auth-flow-with-openclaw/</link><pubDate>Mon, 06 Apr 2026 04:48:17 +0000</pubDate><guid>https://www.tewari.info/2026/04/06/device-code-auth-flow-with-openclaw/</guid><description>&lt;p&gt;Device Code Flow has been built into OAuth2 for years, originally designed for TVs and game consoles. It works just as well for a Docker container. It requires no credentials stored on the agent machine. It gives you narrow, revocable access that the agent cannot exceed.&lt;/p&gt;&#10;&lt;p&gt;I have OpenClaw deployed in a sandboxed docker container on a dedicated host. I communicate with it via a Telegram bot, in a locked-down chat.&lt;/p&gt;&#10;&lt;p&gt;I wired the device-code authentication pattern up with OpenClaw and Microsoft (Personal/Consumer) services, but the authentication approach works with any app and any command-line tool you want to run in a headless environment, including Custom APIs. Device code flow + Entra ID app registrations give you a zero-stored-credentials gateway to any HTTP API you can build, not just Microsoft Graph.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/openclaw-telegram-integration.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;strong&gt;What Is Device Code Flow?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;OAuth2 device code flow (RFC 8628) was designed for &amp;ldquo;input-constrained devices&amp;rdquo; — things without a keyboard or a browser. Think of how you sign in to Netflix on a smart TV: a short code appears on screen, you visit a URL on your phone, you type the code in, and the TV logs in. You never type your password on the TV.&lt;/p&gt;&#10;&lt;p&gt;The official name for that pattern is the device authorization grant. A Docker container is, from the protocol&amp;rsquo;s perspective, exactly the same kind of device. It has no browser. It cannot perform an interactive redirect. But it can make HTTP requests, and that is all it needs.&lt;/p&gt;&#10;&lt;p&gt;The flow has three steps:&lt;/p&gt;&#10;&lt;p&gt;1. The app posts to the identity provider&amp;rsquo;s device code endpoint. It gets back a short user code (like &amp;ldquo;WDJB-MJHT&amp;rdquo;), a verification URL, and a polling device code.&lt;/p&gt;&#10;&lt;p&gt;2. The user visits the URL on any browser, on any device — their phone, laptop, anything — and enters the short code. They sign in normally, with their usual credentials and MFA if it is enabled.&lt;/p&gt;&#10;&lt;p&gt;3. The app polls the token endpoint in the background. Once the user finishes signing in, the poll returns a real access token and a refresh token. The app stores these and uses them for API calls going forward.&lt;/p&gt;&#10;&lt;p&gt;The app never sees the password. The password goes directly from the user&amp;rsquo;s browser to the identity provider. The app only ever handles two things: a short temporary code that it sends to the user, and a token that the identity provider gives back once the user has authenticated.&lt;/p&gt;&#10;&lt;p&gt;OpenClaw running in a Docker container fits this model exactly.&lt;/p&gt;&#10;&lt;h2 id="which-services-support-this"&gt;Which Services Support this?&#10;&lt;/h2&gt;&lt;p&gt;Device code flow is not a Microsoft-only feature. It is a standard OAuth2 extension (RFC 8628) and most major identity providers support it — including Microsoft, Google, GitHub, and AWS. If a service uses Okta or Auth0 for identity, those support it too.&lt;/p&gt;&#10;&lt;h2 id="the-security-architecture"&gt;The Security Architecture&#10;&lt;/h2&gt;&lt;p&gt;The agent never sees your credentials. Your password is typed in your browser, on your device, to your identity provider&amp;rsquo;s servers. It never touches the container. The agent only handles a short temporary code to give you, and a token issued by the provider once you have authenticated.&lt;/p&gt;&#10;&lt;p&gt;The device code is one-time and short-lived. After the user authenticates, the code is permanently invalidated. An intercepted code is useless without the user&amp;rsquo;s credentials and MFA.&lt;/p&gt;&#10;&lt;p&gt;Scopes define the ceiling. You configure the OAuth app or app registration to request only specific permissions. The agent cannot exceed those scopes. If you configure read-only access to email, the token cannot be used to send or delete email.&lt;/p&gt;&#10;&lt;p&gt;This is the authentication pattern that many of the consumer devices you already own use to access your accounts on your behalf. Your TV&amp;rsquo;s YouTube app, your smart home hub&amp;rsquo;s Google integration, the GitHub CLI you use on your workstation — these all use device flow. You have been trusting it for years without knowing what it was called.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/device-code-flow-workflow.drawio.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="how-the-implementation-works"&gt;How the implementation works&#10;&lt;/h2&gt;&lt;p&gt;There are three components, and they run as separate Docker services that talk to each other through the Docker socket.&lt;/p&gt;&#10;&lt;p&gt;The app sidecar is your application container. It runs your CLI tool and your auth logic. It does nothing on its own. Its only job is to hold the authenticated token cache and execute commands on demand when OpenClaw calls into it.&lt;/p&gt;&#10;&lt;p&gt;OpenClaw is the AI agent container. It connects to Telegram, understands natural language, and knows which skills to run for which requests. It does not know anything about OAuth or your specific app. It just runs shell commands you specified and reports results back to you.&lt;/p&gt;&#10;&lt;p&gt;The Telegram auth bridge is a small script that lives inside the sidecar. It registers a device code callback, requests an auth flow, and uses the Telegram Bot API to forward the code to you — then confirms when authentication completes.&lt;/p&gt;&#10;&lt;p&gt;The compose file looks roughly like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;myapp-sidecar&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ghcr.io/youruser/myapp:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;XDG_DATA_HOME=/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${APP_DATA_DIR:-./app-data}:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;openclaw-gateway&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;context&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dockerfile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Dockerfile.openclaw&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./skills:/app/skills&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;group_add&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${DOCKER_GID:-999}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;18789:18789&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-callback-pattern"&gt;The Callback Pattern&#10;&lt;/h2&gt;&lt;p&gt;The auth manager has a method called set_device_code_callback. You pass it a function, and when the device code is ready, the auth manager calls your function with the code and the verification URL rather than trying to open a browser.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AuthManager&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self, client_id: str, authority: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; client_id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority &lt;span style="color:#f92672"&gt;=&lt;/span&gt; authority&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_tokens &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_load_cache()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set_device_code_callback&lt;/span&gt;(self, fn) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; fn&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_device_code_auth&lt;/span&gt;(self) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/oauth2/v2.0/devicecode&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id, &lt;span style="color:#e6db74"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: OAUTH_SCOPES},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; flow &lt;span style="color:#f92672"&gt;=&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;user_code&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; verification_uri &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;verification_uri&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow[&lt;span style="color:#e6db74"&gt;&amp;#34;device_code&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;interval&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expires_in &lt;span style="color:#f92672"&gt;=&lt;/span&gt; flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;expires_in&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_on_device_code({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;user_code&amp;#34;&lt;/span&gt;: user_code,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;verification_uri&amp;#34;&lt;/span&gt;: verification_uri,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: flow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; webbrowser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(verification_uri)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deadline &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; expires_in&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; deadline:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(interval)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_authority&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/oauth2/v2.0/token&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;client_id&amp;#34;&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_client_id,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;grant_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;urn:ietf:params:oauth:grant-type:device_code&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;device_code&amp;#34;&lt;/span&gt;: device_code,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; resp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;status_code &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; &lt;span style="color:#f92672"&gt;and&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;access_token&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;in&lt;/span&gt; body:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_store_tokens(body)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error &lt;span style="color:#f92672"&gt;=&lt;/span&gt; body&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; error &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;authorization_pending&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; error &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;slow_down&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; interval &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; error &lt;span style="color:#f92672"&gt;in&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#34;authorization_declined&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;expired_token&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; AuthError(error)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; AuthError(body&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;error_description&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Auth failed&amp;#34;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The polling loop handles the three error codes the spec defines: authorization_pending (keep waiting), slow_down (back off and increase the interval by 5 seconds), and the terminal errors that stop polling.&lt;/p&gt;&#10;&lt;h2 id="the-telegram-bridge"&gt;The Telegram Bridge&#10;&lt;/h2&gt;&lt;p&gt;With the callback mechanism in place, the Telegram bridge is just a script that registers a callback and uses the Telegram Bot API to deliver the code to you:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;,&lt;/span&gt; os&lt;span style="color:#f92672"&gt;,&lt;/span&gt; sys&lt;span style="color:#f92672"&gt;,&lt;/span&gt; traceback&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; httpx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; your_app.auth &lt;span style="color:#f92672"&gt;import&lt;/span&gt; AuthManager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; your_app.config &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Settings&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TELEGRAM_BOT_TOKEN &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ[&lt;span style="color:#e6db74"&gt;&amp;#34;TELEGRAM_BOT_TOKEN&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TELEGRAM_CHAT_ID &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ[&lt;span style="color:#e6db74"&gt;&amp;#34;TELEGRAM_CHAT_ID&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;send_telegram&lt;/span&gt;(text: str) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://api.telegram.org/bot&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;TELEGRAM_BOT_TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/sendMessage&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; httpx&lt;span style="color:#f92672"&gt;.&lt;/span&gt;AsyncClient() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; client:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(url, json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;chat_id&amp;#34;&lt;/span&gt;: TELEGRAM_CHAT_ID,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: text,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;parse_mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Markdown&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; int:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; settings &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Settings()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AuthManager(settings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;client_id, settings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;authority)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;is_signed_in():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;&amp;#34;Already authenticated.&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_device_code&lt;/span&gt;(info: dict) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;*Auth Required*&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Go to: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;info[&lt;span style="color:#e6db74"&gt;&amp;#39;verification_uri&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Enter code: `&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;info[&lt;span style="color:#e6db74"&gt;&amp;#39;user_code&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Complete sign-in in your browser and I&amp;#39;ll confirm when done.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_event_loop()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;create_task(send_telegram(msg))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RuntimeError&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;import&lt;/span&gt; requests&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://api.telegram.org/bot&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;TELEGRAM_BOT_TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/sendMessage&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;chat_id&amp;#34;&lt;/span&gt;: TELEGRAM_CHAT_ID, &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: msg, &lt;span style="color:#e6db74"&gt;&amp;#34;parse_mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Markdown&amp;#34;&lt;/span&gt;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_device_code_callback(on_device_code)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_info &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; auth&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sign_in()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*Signed in* as &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;user_info[&lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; (&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;user_info[&lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; exc:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; send_telegram(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;*Auth failed:* &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;exc&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;```&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;traceback&lt;span style="color:#f92672"&gt;.&lt;/span&gt;format_exc()[:&lt;span style="color:#ae81ff"&gt;800&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;```&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sys&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exit(asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(main()))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-openclaw-skill"&gt;The OpenClaw Skill&#10;&lt;/h2&gt;&lt;p&gt;On the OpenClaw side, a &amp;ldquo;skill&amp;rdquo; is a markdown file. When OpenClaw sees a trigger phrase in Telegram, it runs the associated shell command. The markdown file looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;myapp-auth&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Authenticate with the cloud service via device code flow, coordinated through Telegram.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## myapp-auth&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Use this skill when the user sends &amp;#34;/auth&amp;#34;, &amp;#34;authenticate&amp;#34;, or &amp;#34;sign in&amp;#34;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Run the following command and wait up to 360 seconds for it to complete.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The script sends the device code to the user via Telegram and confirms when done.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker exec myapp-sidecar-1 python /app/scripts/telegram_auth.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&#10;&lt;/h2&gt;&lt;p&gt;Device code flow is a mature, widely-supported OAuth2 pattern that maps naturally onto OpenClaw running in Docker container. With this pattern OpenClaw never handles your credentials. It gets scoped, revocable tokens from the identity provider, after you have authenticated in your own browser. A Telegram bot is all you need to coordinate the handoff of the temporary code.&lt;/p&gt;&#10;&lt;p&gt;The approach works for many use-cases across Microsoft accounts, Google, GitHub, AWS, Auth0, Okta and others. It supports exactly the kinds of personal automations that make an OpenClaw genuinely useful in daily life.&lt;/p&gt;&#10;&lt;h2 id="references"&gt;References&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.openclaw.ai/" target="_blank" rel="noopener"&#10; &gt;OpenClaw Documentation&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.openclaw.ai/tools/creating-skills" target="_blank" rel="noopener"&#10; &gt;OpenClaw: Creating Skills&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc8628" target="_blank" rel="noopener"&#10; &gt;RFC 8628: OAuth 2.0 Device Authorization Grant&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/entra/identity-platform/scenario-desktop-acquire-token-device-code-flow" target="_blank" rel="noopener"&#10; &gt;Microsoft Identity Platform: Acquire a Token Using Device Code Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/graph/permissions-reference" target="_blank" rel="noopener"&#10; &gt;Microsoft Graph Permissions Reference&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" target="_blank" rel="noopener"&#10; &gt;Auth0: Device Authorization Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://developer.okta.com/docs/guides/device-authorization-grant/main/" target="_blank" rel="noopener"&#10; &gt;Okta: Configure Device Authorization Grant Flow&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://core.telegram.org/bots/api" target="_blank" rel="noopener"&#10; &gt;Telegram Bot API&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.docker.com/reference/compose-file/" target="_blank" rel="noopener"&#10; &gt;Docker Compose File Reference&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>AI Powered Bookshelf</title><link>https://www.tewari.info/2024/04/28/ai-powered-bookshelf/</link><pubDate>Mon, 29 Apr 2024 00:25:55 +0000</pubDate><guid>https://www.tewari.info/2024/04/28/ai-powered-bookshelf/</guid><description>&lt;p&gt;Bookshelf is a Generative AI application built as a rudimentary, but fairly capable, &lt;a class="link" href="https://docs.llamaindex.ai/en/stable/use_cases/q_and_a/" target="_blank" rel="noopener"&#10; &gt;RAG&lt;/a&gt; implementation written in &lt;a class="link" href="https://www.python.org/doc/" target="_blank" rel="noopener"&#10; &gt;python&lt;/a&gt;. It can use an open source LLM model (running locally or in the cloud) or a GPT model via OpenAI&amp;rsquo;s API.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The application is created using &lt;a class="link" href="https://streamlit.io/" target="_blank" rel="noopener"&#10; &gt;streamlit&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;I used &lt;a class="link" href="https://www.llamaindex.ai/" target="_blank" rel="noopener"&#10; &gt;llama-index&lt;/a&gt; for orchestrating the loading of documents into the &lt;a class="link" href="https://en.wikipedia.org/wiki/Vector_database" target="_blank" rel="noopener"&#10; &gt;vector database&lt;/a&gt;. Only TokenTextSplitter is currently used. It does not optimize for PDF, html and other formats.&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.trychroma.com/" target="_blank" rel="noopener"&#10; &gt;ChromaDb&lt;/a&gt; is the vector database to store the embedding vectors and metadata of the document nodes.&lt;/li&gt;&#10;&lt;li&gt;You can use any open source embeddings model from &lt;a class="link" href="https://huggingface.co/models?other=embeddings" target="_blank" rel="noopener"&#10; &gt;HuggingFace&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Bookshelf will automatically use the GPU when creating local embeddings, if the GPU is available on your machine.&lt;/li&gt;&#10;&lt;li&gt;You can use &lt;a class="link" href="https://platform.openai.com/docs/guides/embeddings" target="_blank" rel="noopener"&#10; &gt;OpenAI embeddings&lt;/a&gt; as well. There is no way to use a specific OpenAI embedding model or configure the parameters yet.&lt;/li&gt;&#10;&lt;li&gt;Use OpenAI API or any OpenAI compatible LLM API (using &lt;a class="link" href="https://lmstudio.ai/" target="_blank" rel="noopener"&#10; &gt;LMStudio&lt;/a&gt;, &lt;a class="link" href="https://ollama.com/" target="_blank" rel="noopener"&#10; &gt;Ollama&lt;/a&gt; or &lt;a class="link" href="https://github.com/oobabooga/text-generation-webui" target="_blank" rel="noopener"&#10; &gt;text-generation-webui&lt;/a&gt;) of your choice.&lt;/li&gt;&#10;&lt;li&gt;There is a live demo on streamlit cloud - &lt;a class="link" href="https://bookshelf.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;https://bookshelf.streamlit.app/&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;The demo allows only OpenAI integration. You can run it locally for accessing Open Source embedding models and LLMs.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;Live demo - &lt;a class="link" href="https://bookshelf.streamlit.app/" target="_blank" rel="noopener"&#10; &gt;https://bookshelf.streamlit.app/&lt;/a&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;You will need your OpenAI api key for the demo.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-39.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;If you are running it locally, you will have the option of using an Open Source LLM instance via an API Url. In the screenshot, I am using an open source Embedding Model from HuggingFace (sentence-transformers/all-mpnet-base-v2) and The local LLM server at http://localhost:1234/v1&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-40.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Collections tab shows all collections in the database. It also shows the names of all the files in the selected collection. You can inspect individual chunks for the metadata and text of each chunk. You can delete all contents of the collection &lt;strong&gt;(&lt;/strong&gt; &lt;strong&gt;there is no warning&lt;/strong&gt; &lt;strong&gt;)&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-38.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can modify the collection name to create a new collection. Multiple files can be uploaded at the same time. You can specify if you want to extract metadata from the file contents. &lt;strong&gt;Enabling this option can add significant cost&lt;/strong&gt; because it employs Extractors which use LLM to generate title, summaries, keywords and questions for each document.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-41.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;On the Retrieve tab, you can query chunks which are semantically related to your query.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-43.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;On the Prompt tab, you can prompt your LLM. The context as well as the Prompt Template is editable.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-44.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Here is an example of using the context retrieved from chunks in the Vector database to query the LLM.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-45.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;This inference was performed using &lt;a class="link" href="https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-gguf" target="_blank" rel="noopener"&#10; &gt;Phi3&lt;/a&gt; model running locally on LMStudio.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://www.tewari.info/wp-content/image-46.png"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Code is on Github - &lt;a class="link" href="https://github.com/ashtewari/bookshelf" target="_blank" rel="noopener"&#10; &gt;https://github.com/ashtewari/bookshelf&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Have fun!&lt;/p&gt;&#10;</description></item><item><title>KeyNode with Node.js and Microsoft Azure</title><link>https://www.tewari.info/2016/03/10/keynode-with-node-js-and-microsoft-azure/</link><pubDate>Fri, 11 Mar 2016 00:23:06 +0000</pubDate><guid>https://www.tewari.info/2016/03/10/keynode-with-node-js-and-microsoft-azure/</guid><description>&lt;p&gt;&lt;a class="link" href="https://github.com/ashtewari/keynode" title="KeyNode"&#10; target="_blank" rel="noopener"&#10; &gt;KeyNode&lt;/a&gt; is a application to issue and verify software license keys. Technology stack for KeyNode is &lt;a class="link" href="https://nodejs.org/" title="Node.js"&#10; target="_blank" rel="noopener"&#10; &gt;Node.j&lt;/a&gt; s, &lt;a class="link" href="https://www.mongodb.org/" title="MongoDB"&#10; target="_blank" rel="noopener"&#10; &gt;MongoDB&lt;/a&gt; and &lt;a class="link" href="https://azure.microsoft.com/" title="Microsoft Azure"&#10; target="_blank" rel="noopener"&#10; &gt;Microsoft Azure&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;I had built this functionality with &lt;a class="link" href="https://c9.io" title="C9.io"&#10; target="_blank" rel="noopener"&#10; &gt;C9.io&lt;/a&gt; (a cloud-based IDE with a built-in source code repository and debugger), &lt;a class="link" href="http://www.prnewswire.com/news-releases/mongohq-becomes-compose-270174611.html" target="_blank" rel="noopener"&#10; &gt;mongohq&lt;/a&gt; (MongoDB as a service - now part of &lt;a class="link" href="https://www.compose.io/" title="compose.io"&#10; target="_blank" rel="noopener"&#10; &gt;compose.io&lt;/a&gt;) and &lt;a class="link" href="https://www.ctl.io/appfog/" title="AppFog"&#10; target="_blank" rel="noopener"&#10; &gt;appfog&lt;/a&gt; (Cloud PAAS built on top of &lt;a class="link" href="https://www.cloudfoundry.org/" title="CloudFoundry"&#10; target="_blank" rel="noopener"&#10; &gt;CloudFoundry&lt;/a&gt;). It used SMTP/gmail to email license files. That was the version I created a couple of years ago to issue tamper-proof signed xml license files for &lt;a class="link" href="http://codedemo.net/" target="_blank" rel="noopener"&#10; &gt;CodeDemo&lt;/a&gt; (a code snippet tool for developers, presenters and instructors).&lt;/p&gt;&#10;&lt;p&gt;For KeyNode (open source) I switched to a different toolset : &lt;a class="link" href="https://code.visualstudio.com/" title="Visual Studio Code"&#10; target="_blank" rel="noopener"&#10; &gt;Visual Studio Code&lt;/a&gt; and &lt;a class="link" href="https://azure.microsoft.com/" target="_blank" rel="noopener"&#10; &gt;Windows Azure&lt;/a&gt;, simplified the code to remove signed xml file and open-sourced it on &lt;a class="link" href="https://github.com/ashtewari/keynode" target="_blank" rel="noopener"&#10; &gt;GitHub&lt;/a&gt;. Signed xml allowed offline verification in CodeDemo (a Wpf/Desktop app). Removing signed xml requires verification to happen online. I am working on adding the web endpoint for verification of license keys. This version uses &lt;a class="link" href="https://sendgrid.com/" title="SendGrid"&#10; target="_blank" rel="noopener"&#10; &gt;SendGrid&lt;/a&gt; to email license keys. KeyNode is deployed as a &lt;a class="link" href="https://azure.microsoft.com/en-us/services/app-service/web/" target="_blank" rel="noopener"&#10; &gt;Windows Azure Web App&lt;/a&gt;. The Azure Web App is on Continuous Deployment feed from the source code repository on GitHub.&lt;/p&gt;&#10;&lt;p&gt;I created and tested this Node.js application locally without IIS and deployed it as an Azure Web App without making any changes to the code at all. Node.js applications are hosted in Azure under IIS with &lt;a class="link" href="https://github.com/tjanczuk/iisnode" title="iisnode"&#10; target="_blank" rel="noopener"&#10; &gt;iisnode&lt;/a&gt;. &lt;a class="link" href="https://github.com/tjanczuk/iisnode" target="_blank" rel="noopener"&#10; &gt;Iisnode&lt;/a&gt; is a native IIS module that allows hosting of node.js applications in IIS on Windows. Read more about iisnode &lt;a class="link" href="https://tomasz.janczuk.org/2011/08/hosting-nodejs-applications-in-iis-on.html" target="_blank" rel="noopener"&#10; &gt;here&lt;/a&gt;. Iisnode architecture also makes it significantly &lt;a class="link" href="https://blogs.msdn.microsoft.com/hanuk/2012/05/04/top-benefits-of-running-node-js-on-windows-azure/" target="_blank" rel="noopener"&#10; &gt;easier&lt;/a&gt; to take advantages of scalability afforded by Azure.&lt;/p&gt;&#10;&lt;p&gt;KeyNode is a work in progress. My plan is to use this as the basis for further explorations in the following areas :&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;DevOps, Docker and Microservices (at miniature scale of course!)&lt;/li&gt;&#10;&lt;li&gt;Create a Web UI with &lt;a class="link" href="http://expressjs.com/" target="_blank" rel="noopener"&#10; &gt;Express&lt;/a&gt;(a Node.js web application framework)&lt;/li&gt;&#10;&lt;li&gt;Integrate with &lt;a class="link" href="https://azure.microsoft.com/en-us/services/storage/" target="_blank" rel="noopener"&#10; &gt;Azure Storage/Queues&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;and more&amp;hellip;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I invite you to check out the &lt;a class="link" href="http://keynode.azurewebsites.net/" target="_blank" rel="noopener"&#10; &gt;live site on Azure&lt;/a&gt; and fork it for your own experiments : &lt;a class="link" href="https://github.com/ashtewari/keynode" target="_blank" rel="noopener"&#10; &gt;KeyNode on GitHub&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Resources :&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://azure.microsoft.com/en-us/develop/nodejs/" target="_blank" rel="noopener"&#10; &gt;Node.js Developer Center&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://tomasz.janczuk.org/2011/08/hosting-nodejs-applications-in-iis-on.html" target="_blank" rel="noopener"&#10; &gt;Hosting Node.js Applications in IIS on Windows&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://blogs.msdn.microsoft.com/hanuk/2012/05/04/top-benefits-of-running-node-js-on-windows-azure/" target="_blank" rel="noopener"&#10; &gt;Top Benefits of Running Node.js on Windows Azure&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Photo Credit : Piano Keyboard (&lt;a class="link" href="https://www.kpmalinowski.pl/" target="_blank" rel="noopener"&#10; &gt;www.kpmalinowski.pl&lt;/a&gt;)&lt;/p&gt;&#10;</description></item><item><title>The Site44 Workflow</title><link>https://www.tewari.info/2013/02/06/the-site44-workflow/</link><pubDate>Wed, 06 Feb 2013 17:32:50 +0000</pubDate><guid>https://www.tewari.info/2013/02/06/the-site44-workflow/</guid><description>&lt;p&gt;A light weight development workflow with real-time website deployment.&lt;/p&gt;&#10;&lt;p&gt;I recently built &lt;a class="link" href="http://ash.site44.com/hero.html" target="_blank" rel="noopener"&#10; &gt;a sample website&lt;/a&gt; to illustrate &lt;a class="link" href="http://blog.appliedis.com/2013/02/04/bootstrap-with-less/" target="_blank" rel="noopener"&#10; &gt;how clean, semantic html&lt;/a&gt; markup can be maintained when using &lt;a class="link" href="http://twitter.github.com/bootstrap/index.html" target="_blank" rel="noopener"&#10; &gt;Bootstrap&amp;rsquo;s&lt;/a&gt; &lt;a class="link" href="http://twitter.github.com/bootstrap/scaffolding.html#gridSystem" target="_blank" rel="noopener"&#10; &gt;grid system&lt;/a&gt;. The solution is to use a &lt;a class="link" href="http://lesscss.org" target="_blank" rel="noopener"&#10; &gt;css pre-processor&lt;/a&gt; to incorporate Bootstrap&amp;rsquo;s &lt;a class="link" href="http://lesscss.org" target="_blank" rel="noopener"&#10; &gt;LESS&lt;/a&gt; based &lt;a class="link" href="http://twitter.github.com/bootstrap/extend.html#built-with-less" target="_blank" rel="noopener"&#10; &gt;mixins&lt;/a&gt; into your own .less files and push the Bootstrap instructions down into your stylesheets. There are two ways to &amp;ldquo;compile&amp;rdquo; .less stylesheets - use a &lt;a class="link" href="http://winless.org/" target="_blank" rel="noopener"&#10; &gt;stand-alone LESS compiler&lt;/a&gt; or use &lt;a class="link" href="https://github.com/cloudhead/less.js" target="_blank" rel="noopener"&#10; &gt;less.js&lt;/a&gt;. I found it very convenient to use &lt;a class="link" href="https://raw.github.com/cloudhead/less.js/master/dist/less-1.3.3.min.js" target="_blank" rel="noopener"&#10; &gt;less.js&lt;/a&gt; (note that it is not recommended in production deployment). As I started working on developing the sample code I found it a bit cumbersome to work with an entire web application project in Visual Studio, considering I was working with some really simple sample client-side html, css. As I craved for an alternative, I stumbled on to a development workflow that is incredibly simple and a lot of fun. I call it the Site44 workflow. Site44 turns your dropbox folders into websites. And it is awesome! Here is what you do -&lt;/p&gt;&#10;&lt;p&gt;1. Sign into &lt;a class="link" href="http://www.site44.com" target="_blank" rel="noopener"&#10; &gt;Site44.com&lt;/a&gt; using your &lt;a class="link" href="http://www.dropbox.com" target="_blank" rel="noopener"&#10; &gt;dropbox&lt;/a&gt; credentials.&lt;/p&gt;&#10;&lt;p&gt;2. Create a new site (all you have to do is come up with a name). I named it &amp;ldquo;ash&amp;rdquo;. A sub-folder with this name will show up in your &lt;a class="link" href="http://www.dropbox.com" target="_blank" rel="noopener"&#10; &gt;dropbox&lt;/a&gt; folder.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/site44-dropbox-folder.png" &gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/site44-dropbox-folder.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;3. Drag this folder to your &lt;a class="link" href="http://www.github.com" target="_blank" rel="noopener"&#10; &gt;Github&lt;/a&gt; for Windows screen and drop it there to create a github repo in that folder and &lt;a class="link" href="https://github.com/ashtewari/ash.site44.com" target="_blank" rel="noopener"&#10; &gt;push it to github&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.tewari.info/wp-content/site44-to-github.png" &gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.tewari.info/wp-content/site44-to-github.png"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;4. Smile and write code.&lt;/p&gt;&#10;&lt;p&gt;As you save your code. The changes are deployed in real-time to your website. You commit to your github repo as you please. If you revert to a different version/branch of our code from your git repo, that version will be deployed (almost) instantly to your website. I wish there was a .site44ignore feature in &lt;a class="link" href="http://www.site44.com" target="_blank" rel="noopener"&#10; &gt;Site44&lt;/a&gt;, just like .gitignore. That will allow me to keep my .git folder (and some other files) from getting published to the website. Other than that, this worked out really well for me.&lt;/p&gt;&#10;&lt;p&gt;I wrote about the experience of extending Bootstrap with LESS here : &lt;a class="link" href="http://blog.appliedis.com/2013/02/04/bootstrap-with-less/" target="_blank" rel="noopener"&#10; &gt;Bootstrap with LESS&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Hat tip to &lt;a class="link" href="http://www.justinsaraceno.com/" target="_blank" rel="noopener"&#10; &gt;Justin Saraceno&lt;/a&gt; for introducing me to &lt;a class="link" href="http://www.site44.com" target="_blank" rel="noopener"&#10; &gt;site44&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>Protecting Your Api Keys</title><link>https://www.tewari.info/2012/09/02/protecting-your-api-keys/</link><pubDate>Mon, 03 Sep 2012 03:14:38 +0000</pubDate><guid>https://www.tewari.info/2012/09/02/protecting-your-api-keys/</guid><description>&lt;p&gt;I am working on a Windows 8 app (details to follow in a subsequent post) and the code is published in a public repo on &lt;a class="link" href="http://www.github.com/" target="_blank" rel="noopener"&#10; &gt;github&lt;/a&gt;. My app uses third-party APIs and after I committed the first cut to github, I realized that I had included my api keys in the code. The whole world had access to my keys. I did not want to publish the developer keys for those APIs to the entire world.&lt;/p&gt;&#10;&lt;p&gt;When the app will be released and distributed, those keys will need to be included in the app somehow. Once the keys are out there they can not be 100% protected from a determined mind. So, why bother? Why would I want to hide the api keys in the source code? Here are some &lt;a class="link" href="http://www.infosecurity-magazine.com/blog/2011/4/20/protect-the-api-keys-to-your-cloud-kingdom/322.aspx" target="_blank" rel="noopener"&#10; &gt;good reasons&lt;/a&gt; -&lt;/p&gt;&#10;&lt;p&gt;1. It might be illegal to put the keys out there in plain sight for the whole world to see.&#10;2. Developer keys may be throttled or have other restrictions on how many times they can be used per day or per minute.&#10;3. The keys might allow access to expensive cloud computing resources.&#10;4. The keys might allow access to confidential/sensitive customer data.&lt;/p&gt;&#10;&lt;p&gt;First, I had to take my keys back from git repo. Can you really remove information from a public git repository? Yes, you can, using git &lt;a class="link" href="http://git-scm.com/docs/git-filter-branch/" target="_blank" rel="noopener"&#10; &gt;filter-branch&lt;/a&gt;. Here is how - &lt;a class="link" href="https://help.github.com/articles/remove-sensitive-data" target="_blank" rel="noopener"&#10; &gt;https://help.github.com/articles/remove-sensitive-data&lt;/a&gt;. It worked! I successfully rewrote the history! My past commits don&amp;rsquo;t have those file(s) anymore that had my private api keys.&lt;/p&gt;&#10;&lt;p&gt;Next, I made sure that I don&amp;rsquo;t make this mistake again -&lt;/p&gt;&#10;&lt;p&gt;1. I added a new file ApiKeys.cs to the project.&#10;2. Exposed the api keys as constants from a static class in this new file.&#10;3 Added ApiKeys.cs in .gitignore file, to prevent this file from being committed to the repository.&#10;4. Added instructions in ReadMe.txt for external developers to include their own keys.&lt;/p&gt;&#10;&lt;p&gt;This is not an ideal solution. If you are using a continuous build server, this technique will obviously not work. The code will not compile as-is, a file must be added to the project before it will start compiling. This works for me for now, but I am still looking for a better solution.&lt;/p&gt;&#10;</description></item><item><title>Solution to the fetch puzzle</title><link>https://www.tewari.info/2012/01/03/solution-to-the-fetch-puzzle/</link><pubDate>Wed, 04 Jan 2012 03:00:38 +0000</pubDate><guid>https://www.tewari.info/2012/01/03/solution-to-the-fetch-puzzle/</guid><description>&lt;p&gt;Here is a brute force solution to the fetch problem -&lt;/p&gt;&#10;&lt;p&gt;Basically, at each step there are three possibilities :&lt;/p&gt;&#10;&lt;p&gt;1. You can fill a bucket.&lt;/p&gt;&#10;&lt;p&gt;2. You can transfer water from one bucket to the other one.&lt;/p&gt;&#10;&lt;p&gt;3. You can dump out the water from a bucket.&lt;/p&gt;&#10;&lt;p&gt;In this brute force solution, I try each one of these steps and then try all three again after each one of the previous steps. And on&lt;/p&gt;&#10;&lt;p&gt;and on untill I get the required amount of water in one of the buckets.&lt;/p&gt;&#10;&lt;p&gt;Check it out. Source code is on my github repo -&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://github.com/ashtewari/fetch" target="_blank" rel="noopener"&#10; &gt;https://github.com/ashtewari/fetch&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Here is a brute force solution to the &lt;a class="link" href="https://www.tewari.info/2011/12/26/solve-puzzle-with-code/" title="fetch-puzzle"&#10; &gt;fetch puzzle&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;The puzzle goes like this – You have two buckets. A 3 gallon bucket and a 5 gallon bucket. Buckets are not marked or graduated. You are to fetch 4 gallons of water in a single trip to the river. How will you do it?&lt;/p&gt;&#10;&lt;p&gt;Basically, at each step there are three possibilities :&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;You can fill a bucket.&lt;/li&gt;&#10;&lt;li&gt;You can transfer water from one bucket to the other one.&lt;/li&gt;&#10;&lt;li&gt;You can dump out the water from a bucket.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;In this brute force solution, I try each one of these steps and then try all three again after each one of the previous steps. And on and on until I get the required amount of water in one of the buckets.&lt;/p&gt;&#10;&lt;p&gt;Check it out. Source code is on my github repo - &lt;a class="link" href="https://github.com/ashtewari/fetch" title="github-repo-fetch"&#10; target="_blank" rel="noopener"&#10; &gt;https://github.com/ashtewari/fetch&lt;/a&gt;&lt;/p&gt;&#10;</description></item></channel></rss>