{"id":20537924,"url":"https://github.com/dmjio/s3-signer","last_synced_at":"2025-04-09T10:07:44.390Z","repository":{"id":56876974,"uuid":"20645452","full_name":"dmjio/s3-signer","owner":"dmjio","description":":cloud: Presigned S3 URLs for Haskell","archived":false,"fork":false,"pushed_at":"2024-12-14T00:36:04.000Z","size":56,"stargazers_count":24,"open_issues_count":8,"forks_count":10,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-02T08:10:05.193Z","etag":null,"topics":["ajax","aws","aws-s3","haskell","hmac","s3-signer"],"latest_commit_sha":null,"homepage":"http://hackage.haskell.org/package/s3-signer","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmjio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-06-09T12:18:15.000Z","updated_at":"2024-12-14T00:36:08.000Z","dependencies_parsed_at":"2025-02-28T20:13:27.089Z","dependency_job_id":"17515232-15b8-4653-b600-960b1e9454d2","html_url":"https://github.com/dmjio/s3-signer","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmjio%2Fs3-signer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmjio%2Fs3-signer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmjio%2Fs3-signer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmjio%2Fs3-signer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmjio","download_url":"https://codeload.github.com/dmjio/s3-signer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248018060,"owners_count":21034048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","aws","aws-s3","haskell","hmac","s3-signer"],"created_at":"2024-11-16T00:43:27.230Z","updated_at":"2025-04-09T10:07:44.367Z","avatar_url":"https://github.com/dmjio.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"s3-signer\n======\n[![Hackage](https://img.shields.io/hackage/v/s3-signer.svg)](https://hackage.haskell.org/package/s3-signer)\n![Hackage Dependencies](https://img.shields.io/hackage-deps/v/s3-signer.svg)\n![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)\n![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)\n[![Build Status](https://travis-ci.org/dmjio/s3-signer.svg?branch=master)](https://travis-ci.org/dmjio/s3-signer)\n\ns3-signer is intended to be an aid in building secure cloud-based services with\nAWS. This library generates cryptographically secure URLs that\nexpire at a user-defined interval. These URLs can be used to offload\nthe process of uploading and downloading large files, freeing your\nwebserver to focus on other things.\n\n### Features\n - Minimal depedencies\n - Web framework agnostic\n - Reduces web server load\n - Simple API\n - Ideal for AJAX direct-to-s3 upload scenarios\n\n### Documentation\n[S3 Query String Request Authentication](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth)\n\n### Implementation\n\n\u003e AWS Specification\n\n```shell\nSignature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID,UTF-8-Encoding-Of( StringToSign ) ) ) );\n```\n\u003e Haskell Implementation\n\n```haskell\nmodule Network.S3.Sign  ( sign ) where\n\nimport           Crypto.Hash.SHA1       (hash)\nimport           Crypto.MAC.HMAC        (hmac)\nimport qualified Data.ByteString.Base64 as B64\nimport           Data.ByteString.UTF8   (ByteString)\nimport           Network.HTTP.Types.URI (urlEncode)\n\n-- | HMAC-SHA1 Encrypted Signature\nsign :: ByteString -\u003e ByteString -\u003e ByteString\nsign secretKey url = urlEncode True . B64.encode $ hmac hash 64 secretKey url\n```\n\n### Use Case\n```haskell\n{-# LANGUAGE OverloadedStrings #-}\n\nmodule Main where\n\nimport           Network.S3\n\nmain :: IO ()\nmain = print =\u003c\u003c generateS3URL credentials request\n  where\n     credentials = S3Keys \"\u003cpublic-key-goes-here\u003e\" \"\u003csecret-key-goes-here\u003e\"\n     request     = S3Request S3GET \"application/zip\" \"bucket-name\" \"file-name.extension\" 3 -- 3 secs until expired\n```\n### Result\n```haskell\nS3URL {\n      signedRequest =\n         \"https://bucket-name.s3.amazonaws.com/file-name.extension?AWSAccessKeyId=\u003cpublic-key-goes-here\u003e\u0026Expires=1402346638\u0026Signature=1XraY%2Bhp117I5CTKNKPc6%2BiihRA%3D\"\n     }\n```\n\n### Snap integration - Downloads\n```haskell\n-- Quick and dirty example\ntype FileID = ByteString\n\nmakeS3URL :: FileID -\u003e IO S3URL\nmakeS3URL fileId = generateS3URL credentials request\n  where\n    credentials = S3Keys \"\u003cpublic-key-goes-here\u003e\" \"\u003csecret-key-goes-here\u003e\"\n    request     = S3Request S3GET \"application/zip\" \"bucket-name\" (fileId \u003c\u003e \".zip\") 3 \n\ndownloadFile :: Handler App (AuthManager App) ()\ndownloadFile = method POST $ currentUserId \u003e\u003e= maybe the404 handleDownload\n  where handleDownload uid = do\n          Just fileId \u003c- getParam \"fileId\"\n          -- Ensure file being requested belongs to user else 403...\n          S3URL url \u003c- liftIO $ makeS3URL fileId\n          redirect' url 302\n```\n### Direct to S3 AJAX Uploads\n   - Configure S3 Bucket CORS Policy settings\n   - [CORS Docs](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html#how-do-i-enable-cors)\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cCORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"\u003e\n    \u003cCORSRule\u003e\n        \u003cAllowedOrigin\u003ehttps://my-url-goes-here.com\u003c/AllowedOrigin\u003e\n        \u003cAllowedMethod\u003ePUT\u003c/AllowedMethod\u003e\n        \u003cAllowedHeader\u003e*\u003c/AllowedHeader\u003e\n    \u003c/CORSRule\u003e\n\u003c/CORSConfiguration\u003e\n```\n   - Retrieve PUT Request URL via AJAX \n\n```haskell\ntype FileID = ByteString\n\nmakeS3URL :: FileID -\u003e IO S3URL\nmakeS3URL fileId = generateS3URL credentials request\n  where\n    credentials = S3Keys \"\u003cpublic-key-goes-here\u003e\" \"\u003csecret-key-goes-here\u003e\"\n    request     = S3Request S3PUT \"application/zip\" \"bucket-name\" (fileId \u003c\u003e \".zip\") 3 \n\ngetUploadURL :: Handler App (AuthManager App) ()\ngetUploadURL = method POST $ currentUserId \u003e\u003e= maybe the404 handleDownload\n  where handleDownload _ = do\n          Just fileId \u003c- getParam \"fileId\"\n          writeJSON =\u003c\u003c Data.Text.Encoding.decodeUtf8 \u003c$\u003e liftIO (makeS3URL fileId)\n```\n   - Embed FileReader blob data to request\n   - Send upload request\n\n```javascript\nvar xhr = new XMLHttpRequest();\nxhr.open('PUT', url /* S3-URL generated from server */);\nxhr.setRequestHeader('Content-Type', 'application/zip'); /* whatever http-content-type makes sense */\nxhr.setRequestHeader('x-amz-acl', 'public-read');\n\n/* upload completion check */\nxhr.onreadystatechange = function(e) {\n    if (this.readyState === 4 \u0026\u0026 this.status === 200) \n          console.log('upload complete');\n};\n\n/* Amazon gives you progress information on AJAX Uploads */\nxhr.upload.addEventListener(\"progress\", function(evt) {\n       if (evt.lengthComputable) {\n          var v = (evt.loaded / evt.total) * 100,\n          val = Math.round(v) + '%',\n          console.log('Completed: ' + val);\n      }\n}, false);\n\n/* error handling */\nxhr.upload.addEventListener(\"error\", function(evt) {\n   console.log(\"There has been an error :(\");\n}, false);\n\n/* Commence upload */\nxhr.send(file); // file here is a blob from the file reader API\n```\n### File Reader Info\n[How to read file data from the browser](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)\n\n### Troubleshoooting\n- Why do I keep getting 403 forbidden when I attempt to upload or  download from a pre-signed URL?\n  * Ask yourself the following:\n    - Are my keys specified correctly?\n    - Did I configure the CORS settings on my bucket properly?\n    - Still trouble? [Make an issue](https://github.com/dmjio/s3-signer/issues)\n- Why are my URLs expiring faster than the specified time?\n  * Ask yourself the following:\n    - Is my server's clock synchronized with AWS? [See wiki for NTP info](https://github.com/dmjio/s3-signer/wiki/If-URLs-expire-too-quickly)\n\n### FAQ\n- Why didn't you use HMAC-SHA256?\n  * It's 30% slower, and for all intents and purposes no more secure\n    than HMAC-SHA1 (no known vulnerabilities exist for it, to my knowledge). Plain\n    SHA1 is a different story. Collisions can be found, but there is\n    no known way to apply those to HMAC-SHA1.\n  * For the curious [SHA-1 is broken](https://www.schneier.com/blog/archives/2005/02/sha1_broken.html)\n  * For the paranoid (Schneier quote from same article above)\n  * [Relevant SO Post](http://stackoverflow.com/questions/3334524/hmac-security-is-the-security-of-the-hmac-based-on-sha-1-affected-by-the-colli)\n\n\u003e This attack builds on previous attacks on SHA-0 and SHA-1, and is\n\u003e a major, major cryptanalytic result. It pretty much puts a bullet\n\u003e into SHA-1 as a hash function for digital signatures (although it\n\u003e **doesn't** **affect** applications such as **HMAC** where collisions aren't important).\n\n\n  \n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmjio%2Fs3-signer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmjio%2Fs3-signer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmjio%2Fs3-signer/lists"}