[{"content":"I\u0026rsquo;ve integrated a few payment gateways over the years including Authorize.net, WePay, Spreedly, and Stripe but recently had the opportunity to integrate Authorize.net into our system. It\u0026rsquo;s actually not the first time I\u0026rsquo;ve integrated Authorize.net as I did a server-side implementation years ago when server-side implementations were still cool. I mention this to show I\u0026rsquo;ve seen a couple of ways this can be implemented. Maybe I\u0026rsquo;m missing something but the new Accept.js model seems to fall short in what seems like the most basic ways.\nFor example, our checkout experience has the customer filling out a bunch of details for the purchase, clicking a purchase button which validates the form, and provided it passes validation shows the payment form. With Spreedly I can get this behaviour by calling an openView() method after the form has been validated but Accept.js doesn\u0026rsquo;t appear to have a way to do this. Their implementation has you create an element with a specific class that they then bind an event to which has to be available when the DOM is loaded. There doesn\u0026rsquo;t appear to be any way to add the class after validation. It seems like an odd omission since I have to believe I\u0026rsquo;m not the first to want that.\nAnother example is when the buyer gets to the payment form modal and sees they have entered their personal address instead of their work address so they click the X on the payment modal. Is there an easy way to respond to that behavior? Again, I couldn\u0026rsquo;t find it. I had to listen for a message with the type of CLOSE_IFRAME which isn\u0026rsquo;t exactly hard except it\u0026rsquo;s not documented so I had to source dive to figure out what event to listen for. It seems strange there isn\u0026rsquo;t an onAuthWindowClose() method that takes a callback to deal with the modal being closed.\nThe final example I\u0026rsquo;ve got regards documentation and support. It wasn\u0026rsquo;t clear to me which \u0026ldquo;product\u0026rdquo; I needed when I started this process and one of our goals was to have as little PCI liability as possible so I started the integration with Accept Hosted thinking this was the best choice for us. It was only after I\u0026rsquo;d gotten to charging the card that I realized they don\u0026rsquo;t offer a way to get a tokenized card so I could validate on the server side. I\u0026rsquo;m unclear how other people are dealing with confirming the order but suspect I could have gotten what I wanted by looking at webhooks. I ended up switching to Accept.js which does offer the tokenized card that gets confirmed server side. Perhaps it\u0026rsquo;s on me for not reading the documentation more thoroughly but it was one of those points of confusion that would have been great to avoid. For some reason the documentation felt like a maze where every click resulted in a new tab. When I reached out for help I posted on their developer forum and it\u0026rsquo;s been 11 days with no response. Only later did I learn there appears to also be a StackOverflow sub-site for Authorize.net which possibly is the new way to get \u0026ldquo;support\u0026rdquo;. I know it\u0026rsquo;s very hard to make things clear to everyone and possibly no solution accommodates everyone but two things I\u0026rsquo;d personally do are:\nFind a way to make the \u0026ldquo;blessed\u0026rdquo; path clearer and ideally with less clicks. Don\u0026rsquo;t combine integration methods. Normally I\u0026rsquo;d prefer content all on one page because it makes searching easier. Having all the integration \u0026ldquo;types\u0026rdquo; (e.g. \u0026ldquo;Integrating Accept.js into Your Payment Form\u0026rdquo; \u0026amp; \u0026ldquo;Integrating the Hosted Payment Information Form\u0026rdquo;) on the same page made it easy to scroll up too far and be looking at a different integration type than you were working on. None of these are big issues or even that hard to work around but it got me thinking about software paper cuts and how that extra level of polish is hard to quantify but it can be felt. I know there are differing views on how to deal with these and even how big a deal they are but I lean towards them being a bigger deal than people realize precisely because it\u0026rsquo;s hard to quantify them. It feels similar in concept to popups asking for your email. There are absolutely tasteful and non-intrusive ways of asking for a patron\u0026rsquo;s email. Unfortunately, they don\u0026rsquo;t seem to be the norm and instead, we get popups as soon as the page is loaded with no interaction and sometimes without enough time to read the article. Some segment of the population just fills these out because it\u0026rsquo;s being asked for. For me and I know other people it just associates a bad vibe with your organization. Maybe it\u0026rsquo;s not enough to get me to not come back tomorrow but given enough of those types of interactions you will lose me more permanently.\nThe other thought I had and at this point I\u0026rsquo;m certainly talking out of turn and am speaking above my pay grade but the pricing is now 2.9% and $0.30 + a $29/month merchant account fee. In some sense, it makes total sense that they would do this. The old model was opaque and harder to figure out because it depended on the volume and type of the cards you were accepting. With that said, my understanding was most of our clients chose Authorize.net because if you had good credit and after going through the \u0026ldquo;pain\u0026rdquo; of being vetted you could get a much better rate than that. This is likely still true if you negotiate with their sales reps but on getting reacquainted with their offering it felt like they lost one of the key selling points or points of differentiation. So if I\u0026rsquo;m reviewing choices I\u0026rsquo;ve got Stripe on one hand which is 2.9% + $0.30 or I\u0026rsquo;ve got Authorize.net which is 2.9% + $0.30 + $29/month which also subjectively has a worse onboarding experience overall.\n","permalink":"https://rhenley.com/post/authorize-net-in-2024/","summary":"\u003cp\u003eI\u0026rsquo;ve integrated a few payment gateways over the years including\n\u003ca href=\"https://www.authorize.net/\"\u003eAuthorize.net\u003c/a\u003e, \u003ca href=\"https://go.wepay.com/\"\u003eWePay\u003c/a\u003e,\n\u003ca href=\"https://www.spreedly.com/\"\u003eSpreedly\u003c/a\u003e, and \u003ca href=\"https://stripe.com/\"\u003eStripe\u003c/a\u003e but\nrecently had the opportunity to integrate Authorize.net into our system. It\u0026rsquo;s\nactually not the first time I\u0026rsquo;ve integrated Authorize.net as I did a server-side\nimplementation years ago when server-side implementations were still cool. I\nmention this to show I\u0026rsquo;ve seen a couple of ways this can be implemented. Maybe\nI\u0026rsquo;m missing something but the new\n\u003ca href=\"https://developer.authorize.net/api/reference/features/acceptjs.html\"\u003eAccept.js\u003c/a\u003e\nmodel seems to fall short in what seems like the most basic ways.\u003c/p\u003e","title":"Authorize Net in 2024"},{"content":"I have yet to find the \u0026ldquo;perfect\u0026rdquo; workflow for images for any platform but stumbled on one that could work at least for posts that tend to contain a lot of screenshots.\nThe thought occurred to me that I could just take the latest screenshot move it to the current directory and rename it to its md5 sum. So I added the following function to my ~/.bashrc.\n# MOVE MOST RECENT SCREENSHOT # Moves the most recent screenshot from the Screenshots directory into the current directory # # An example output would be: # Screenshot 2024-05-25 at 9.40.31 AM.png =\u0026gt; 7be6a2f02ea74453d5e1912f32680795.jpg mmrs() { EXT=png SCREENSHOTS_DIR=`defaults read com.apple.screencapture location` LATEST_SCREENSHOT=`ls -1t ${SCREENSHOTS_DIR} | head -1` MD5_SUM=`md5 -q \u0026#34;${SCREENSHOTS_DIR}/${LATEST_SCREENSHOT}\u0026#34;` mv \u0026#34;${SCREENSHOTS_DIR}/${LATEST_SCREENSHOT}\u0026#34; \u0026#34;./${MD5_SUM}.png\u0026#34; # Convert, resize and reduce if imagemagick is present if command -v magick \u0026amp;\u0026gt; /dev/null; then # NOTE: This keeps the original png magick \u0026#34;${MD5_SUM}.png\u0026#34; -strip \\ -resize 1920x \\ -quality 65 \\ -interlace JPEG \\ -sampling-factor 4:2:0 \\ \u0026#34;${MD5_SUM}.jpg\u0026#34; EXT=jpg fi # Copy markdown to clipboard if `pbcopy` exists if command -v pbcopy \u0026amp;\u0026gt; /dev/null; then echo \u0026#34;![alt](${MD5_SUM}.${EXT})\u0026#34; | pbcopy fi echo \u0026#34;${LATEST_SCREENSHOT} =\u0026gt; ${MD5_SUM}.${EXT}\u0026#34; } Then I can just take a screenshot normally with CMD+Shift+4 or CMD+Shift+5 and run the bash function to move it into the folder for my page resource:\nmmrs If the situation calls for it the image markdown is already in the clipboard thanks to pbcopy.\n","permalink":"https://rhenley.com/post/static-site-generator-image-workflow/","summary":"\u003cp\u003eI have yet to find the \u0026ldquo;perfect\u0026rdquo; workflow for images for any platform but\nstumbled on one that could work at least for posts that tend to contain a lot of\nscreenshots.\u003c/p\u003e\n\u003cp\u003eThe thought occurred to me that I could just take the latest screenshot move it\nto the current directory and rename it to its \u003ccode\u003emd5\u003c/code\u003e sum.  So I added the following\nfunction to my \u003ccode\u003e~/.bashrc\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# MOVE MOST RECENT SCREENSHOT\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Moves the most recent screenshot from the Screenshots directory into the current directory\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# An example output would be:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Screenshot 2024-05-25 at 9.40.31 AM.png =\u0026gt;  7be6a2f02ea74453d5e1912f32680795.jpg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emmrs\u003cspan class=\"o\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003eEXT\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003epng\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003eSCREENSHOTS_DIR\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sb\"\u003e`\u003c/span\u003edefaults \u003cspan class=\"nb\"\u003eread\u003c/span\u003e com.apple.screencapture location\u003cspan class=\"sb\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003eLATEST_SCREENSHOT\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sb\"\u003e`\u003c/span\u003els -1t \u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eSCREENSHOTS_DIR\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e head -1\u003cspan class=\"sb\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003eMD5_SUM\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sb\"\u003e`\u003c/span\u003emd5 -q \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eSCREENSHOTS_DIR\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e/\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eLATEST_SCREENSHOT\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"sb\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\tmv \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eSCREENSHOTS_DIR\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e/\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eLATEST_SCREENSHOT\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;./\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eMD5_SUM\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e# Convert, resize and reduce if imagemagick is present\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003ecommand\u003c/span\u003e -v magick \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u0026gt; /dev/null\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"c1\"\u003e# NOTE: This keeps the original png\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\tmagick \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eMD5_SUM\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e.png\u0026#34;\u003c/span\u003e -strip \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e\t\t\t-resize 1920x \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e\t\t\t-quality \u003cspan class=\"m\"\u003e65\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e\t\t\t-interlace JPEG \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e\t\t\t-sampling-factor 4:2:0 \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e\t\t\t\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eMD5_SUM\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e.jpg\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nv\"\u003eEXT\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003ejpg\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e# Copy markdown to clipboard if `pbcopy` exists\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003ecommand\u003c/span\u003e -v pbcopy \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u0026gt; /dev/null\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;![alt](\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eMD5_SUM\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e.\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eEXT\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e)\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e pbcopy\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eLATEST_SCREENSHOT\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e =\u0026gt;  \u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eMD5_SUM\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e.\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eEXT\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen I can just take a screenshot normally with \u003ccode\u003eCMD\u003c/code\u003e+\u003ccode\u003eShift\u003c/code\u003e+\u003ccode\u003e4\u003c/code\u003e or\n\u003ccode\u003eCMD\u003c/code\u003e+\u003ccode\u003eShift\u003c/code\u003e+\u003ccode\u003e5\u003c/code\u003e and run the bash function to move it into the folder for my\n\u003ca href=\"https://gohugo.io/content-management/page-resources/\"\u003epage resource\u003c/a\u003e:\u003c/p\u003e","title":"Static Site Generator Image Workflow"},{"content":"When setting up Borg if you get the following error\nRemote: bash: line 1: borg: command not found\nConnection closed by remote host. Is borg working on the server?\nThis was because only the .bashrc is executed for non-interactive sessions and inside the .bashrc of at least Ubuntu (24.04) is the following at the very top of the file. This meant that in my case I needed to put the path related environment variables above the following lines so SSH could find the borg command.\n# If not running interactively, don\u0026#39;t do anything case $- in *i*) ;; *) return;; esac ","permalink":"https://rhenley.com/post/borg-command-not-found/","summary":"\u003cp\u003eWhen setting up \u003ca href=\"https://borgbackup.readthedocs.io/en/stable/index.html\"\u003eBorg\u003c/a\u003e\nif you get the following error\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eRemote: bash: line 1: borg: command not found\u003cbr\u003e\nConnection closed by remote host. Is borg working on the server?\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eThis was because only the \u003ccode\u003e.bashrc\u003c/code\u003e is executed for non-interactive sessions and\ninside the \u003ccode\u003e.bashrc\u003c/code\u003e of at least Ubuntu (24.04) is the following at the very top\nof the file.  This meant that in my case I needed to put the path related\nenvironment variables above the following lines so SSH could find the \u003ccode\u003eborg\u003c/code\u003e\ncommand.\u003c/p\u003e","title":"Borg Command Not Found"},{"content":"I\u0026rsquo;m trying to recursively download a S3 folder from an EC2 instance. I have role with a policy that includes: s3:ListBucket among other things attached to the EC2 instance and can download individual files but keep getting the following error when trying to do so recursively:\n\u0026ldquo;An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied\u0026rdquo;\nThe command being used that was erroring:\naws s3 cp s3://\u0026lt;bucket\u0026gt;/\u0026lt;path\u0026gt;/ ~/temp --recursive --debug\nNote: aws s3 ls s3://\u0026lt;bucket\u0026gt; also did not work.\nHowever the following did work:\naws s3 cp s3://\u0026lt;bucket\u0026gt;/\u0026lt;folder\u0026gt;/very-important.txt ~/temp\nThe current policy resource section has:\n\u0026quot;Resource\u0026quot;: \u0026quot;arn:aws:s3:::\u0026lt;bucket\u0026gt;/*\u0026quot;\nThe reason is there are different resource types that this document will spell out:\nhttps://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html\nSo when doing a policy the permission may require for example a \u0026ldquo;bucket\u0026rdquo; resource type or a \u0026ldquo;object\u0026rdquo; resource type. For example: ListObjects requires a \u0026ldquo;bucket\u0026rdquo; resource and GetObject requires a \u0026ldquo;object\u0026rdquo; resource type. So when crafting the policy you have to take that into consideration likely separating out the permissions objects such that each has the appropriate resource type associated with it.\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;s3:GetObject\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-devops/*\u0026#34; ] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;s3:ListObjects\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-devops\u0026#34; ] } ] } ","permalink":"https://rhenley.com/posts/s3-listobjectsv2-listobjects-access-denied-issue/","summary":"\u003cp\u003eI\u0026rsquo;m trying to recursively download a S3 folder from an EC2 instance.  I have\nrole with a policy that includes: \u003ccode\u003es3:ListBucket\u003c/code\u003e among other things attached to\nthe EC2 instance and can download individual files but keep getting the\nfollowing error when trying to do so recursively:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u0026ldquo;An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied\u0026rdquo;\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eThe command being used that was erroring:\u003cbr\u003e\n\u003ccode\u003eaws s3 cp s3://\u0026lt;bucket\u0026gt;/\u0026lt;path\u0026gt;/ ~/temp --recursive --debug\u003c/code\u003e\u003c/p\u003e","title":"S3 ListObjectsV2 ListObjects Access Denied Issue"},{"content":"This is mostly a note to myself because for some reason when testing CORS headers I nearly always manage to forget to include the Origin header.\nFor completeness here\u0026rsquo;s an example using curl:\ncurl -v --request OPTIONS \u0026#39;localhost:8080\u0026#39; --header \u0026#39;Origin: http://example.com\u0026#39; Here\u0026rsquo;s an example of doing an OPTIONS request to test authorization against AWS S3:\ncurl --head --request OPTIONS \\ --header \u0026#39;origin: https://www.example.com\u0026#39; \\ --header \u0026#39;Access-Control-Request-Method: GET\u0026#39; \\ https://s3.amazonaws.com/example/path/to/object/my-image.jpg NOTE: Probably more to the story but CORS seems very expensive! In my completely non-scientific tests CORS added 3x cost to the speed of the request.\n","permalink":"https://rhenley.com/post/how-to-test-cors/","summary":"\u003cp\u003eThis is mostly a note to myself because for some reason when testing\n\u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS\"\u003eCORS\u003c/a\u003e headers I nearly\nalways manage to forget to include the \u003ccode\u003eOrigin\u003c/code\u003e header.\u003c/p\u003e\n\u003cp\u003eFor completeness here\u0026rsquo;s an example using \u003ca href=\"https://curl.se/\"\u003ecurl\u003c/a\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -v --request OPTIONS \u003cspan class=\"s1\"\u003e\u0026#39;localhost:8080\u0026#39;\u003c/span\u003e --header \u003cspan class=\"s1\"\u003e\u0026#39;Origin: http://example.com\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere\u0026rsquo;s an example of doing an \u003ccode\u003eOPTIONS\u003c/code\u003e request to test authorization against AWS S3:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl --head --request OPTIONS \u003cspan class=\"se\"\u003e\\ \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    --header \u003cspan class=\"s1\"\u003e\u0026#39;origin: https://www.example.com\u0026#39;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\ \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    --header \u003cspan class=\"s1\"\u003e\u0026#39;Access-Control-Request-Method: GET\u0026#39;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\ \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    https://s3.amazonaws.com/example/path/to/object/my-image.jpg\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eNOTE\u003c/strong\u003e: Probably more to the story but CORS seems very expensive! In my\ncompletely non-scientific tests CORS added 3x cost to the speed of the request.\u003c/p\u003e","title":"How To Test CORS"}]