{"id":13407921,"url":"https://github.com/logibit/logibit.hawk","last_synced_at":"2025-03-14T12:31:52.112Z","repository":{"id":25151491,"uuid":"28574015","full_name":"logibit/Logibit.Hawk","owner":"logibit","description":"A F# Hawk implementation with a strongly typed API that guides your usage and minimises security programming errors.","archived":false,"fork":false,"pushed_at":"2018-05-15T15:35:11.000Z","size":425,"stargazers_count":36,"open_issues_count":2,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-05-22T09:07:46.943Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/logibit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-28T23:25:56.000Z","updated_at":"2023-08-04T14:11:24.000Z","dependencies_parsed_at":"2022-08-23T22:10:08.872Z","dependency_job_id":null,"html_url":"https://github.com/logibit/Logibit.Hawk","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logibit%2FLogibit.Hawk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logibit%2FLogibit.Hawk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logibit%2FLogibit.Hawk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logibit%2FLogibit.Hawk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/logibit","download_url":"https://codeload.github.com/logibit/Logibit.Hawk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243578292,"owners_count":20313795,"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":[],"created_at":"2024-07-30T20:00:49.489Z","updated_at":"2025-03-14T12:31:51.424Z","avatar_url":"https://github.com/logibit.png","language":"F#","funding_links":[],"categories":["Authentication and Authorization"],"sub_categories":[],"readme":"# Logibit Hawk\n\nA F# implementation of the Hawk authentication protocol. Few dependencies. No\ncruft. No thrown exceptions.\n\nIf this library throws an exception, report an issue - instead it uses return\nvalues that are structured instead.\n\n``` bash\npaket add nuget Hawk\npaket add nuget Hawk.Suave\n```\n\nDependencies: { Aether, FSharp.Core, NodaTime }, nugets [Hawk][ng-h] and\n[Hawk.Suave][ng-hs].\n\nFor all API methods implemented, the full test suite for those methods has also\nbeen translated.\n\nSponsored by [qvitoo – A.I. bookkeeping](https://qvitoo.com?utm_source=github).\n\n## Usage (Suave Example)\n\n``` fsharp\nopen Logibit.Hawk\nopen Logibit.Hawk.Types\nopen Logibit.Hawk.Server\n\nopen Suave\nopen Suave.Http // houses submodule 'Hawk'\nopen Suave.Http.Successful\nopen Suave.Http.RequestErrors\nopen Suave.Types\n\n// your own user type\ntype User =\n  { homepage  : Uri\n    realName : string }\n\n// this is the structure that is the 'context' for Logibit.Hawk\nlet settings =\n  // this is what the lib is looking for to verify the request\n  let sampleCreds =\n    { id        = \"haf\"\n      key       = \"werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn\"\n      algorithm = SHA256 }\n\n  // the generic type param allows you to implement a generic user repository\n  // for your own user type (above)\n  { Settings.empty\u003cUser\u003e() with\n     // sign: UserId -\u003e Choice\u003cCredentials * 'a, CredsError\u003e\n     credsRepo = fun id -\u003e\n       (sampleCreds,\n        { homepage = Uri(\"https://qvitoo.com\"); realName = \"Henrik\" }\n       )\n       // no error:\n       |\u003e Choice1Of2 }\n\n// You can compose this into the rest of the app, as it's a web part. In this\n// case you're doing a Authorization header authentication\nlet sampleApp settings : WebPart =\n  Hawk.authenticate\n    settings\n    Hawk.bindHeaderReq\n    // in here you can put your authenticated web parts\n    (fun (attr, creds, user) -\u003e OK (sprintf \"authenticated user '%s'\" user.realName))\n    // on failure to authenticate the request\n    (fun err -\u003e UNAUTHORIZED (err.ToString()))\n\n// Similarly for bewits, where you want to authenticate a portion of the query\n// string:\nlet sampleApp2 settings : WebPart =\n  Hawk.authenticateBewit\n    settings Hawk.bindQueryRequest\n    // in here you can put your authenticated web parts\n    (fun (attr, creds, user) -\u003e OK (sprintf \"authenticated user '%s'\" user.realName))\n    // on failure to authenticate the request\n    (fun err -\u003e UNAUTHORIZED (err.ToString()))\n```\n\nCurrently the code is only fully documented - but not outside the code, so have\na browse to\n[the source code](https://github.com/logibit/Logibit.Hawk/blob/master/src/Logibit.Hawk/Server.fs#L1)\nthat you are interested in to see how the API composes.\n\n## Usage from client:\n\nUse the .js file from `src/vendor/hawk.js/lib`, then you can wrap your ajax\ncalls like this:\n\n**request.js:** (using CommonJS module layout, which you can use to require it and\nget a function in return).\n\n``` javascript\nvar Auth   = require('./auth.js'),\n    Hawk   = require('./lib/hawk.js'),\n    Logger = require('./logger.js'),\n    jQuery = require('jquery');\n\nvar qt = function(str) {\n  return \"'\" + str + \"'\";\n}\n\nvar jqSetHawkHeader = function(opts, creds, jqXHR, settings) {\n  if (typeof opts.contentType == 'undefined') {\n    throw new Error('missing contentType from options');\n  }\n\n  var opts = jQuery.extend({ credentials: creds, payload: settings.data }, opts),\n      // header(uri, method, options): should have options values for\n      // - contentType\n      // - credentials\n      // - payload\n      header = Hawk.client.header(settings.url, settings.type, opts); // type = HTTP-method\n\n  if (typeof header.err !== 'undefined') {\n    Logger.error('(1/2) Hawk error:', qt(header.err), 'for', method, qt(settings.url));\n    Logger.error('(2/2) Using credentials', opts.credentials);\n    return;\n  }\n\n  Logger.debug('(1/3)', settings.type, settings.url);\n  Logger.debug('(2/3) opts:', opts);\n  Logger.debug('(3/3) header:', header.field);\n\n  jqXHR.setRequestHeader('Authorization', header.field);\n};\n\nmodule.exports = function (method, resource, data, opts) {\n  var origin    = window.location.origin,\n      creds     = Auth.getCredentials(),\n      url       = origin + resource,\n      opts      = jQuery.extend({\n        contentType: 'application/x-www-form-urlencoded; charset=UTF-8',\n        dataType: 'html'\n      }, (typeof opts !== 'undefined' ? opts : {})),\n      jqOpts    = jQuery.extend({\n        type:       method,\n        data:       data,\n        url:        url,\n        beforeSend: function(xhr, s) { jqSetHawkHeader(opts, creds, xhr, s) }\n      }, opts);\n\n  return jQuery.ajax(jqOpts);\n};\n```\n\n## Changelog\n\nPlease have a look at [Releases](https://github.com/logibit/Logibit.Hawk/releases).\n\n## API\n\nThis is the public API of the library. It mimics the API of Hawk.js - the\n[reference implementation](https://github.com/hueniverse/hawk/blob/master/lib/client.js).\n\n### `Logibit.Hawk.Bewit`\n\nThese functions are available to creating and verifying Bewits.\n\n - [x] generate - generate a new bewit from credentials, a uri and an optional\n       ext field.\n - [x] generate' - generate a new bewit from credentials, a string uri and an\n       optional ext field.\n - [x] authenticate - verify a given bewit\n\n#### `authenticate` details\n\nTBD - docs, in the meanwhilse, see Server authenticate (superset is validated).\n\n### `Logibit.Hawk.Client`\n\nThese functions are available, checked functions are implemented\n\n - [x] header - generate a request header for server to authenticate\n - [x] bewit - delegates to Bewit.generate\n - [x] authenticate - test that server response is authentic, see\n   [Response Payload Validation](https://github.com/hueniverse/hawk#response-payload-validation).\n - [ ] message - generate an authorisation string for a message\n\n### `Logibit.Hawk.Server`\n\n - [x] authenticate - authenticate a request\n - [x] authenticatePayload - authenticate the payload of a request - assumes\n   you first have called `authenticate` to get credentials.\n   [Payload Validation](https://github.com/hueniverse/hawk#payload-validation)\n - [ ] authenticatePayloadHash\n - [ ] header - generate a server-header for the client to authenticate\n - [x] authenticateBewit - authenticate a client-supplied bewit, see [Bewit\n   Usage Example](https://github.com/hueniverse/hawk#bewit-usage-example).\n - [ ] authenticateMessage - authenticate a client-supplied message\n\n#### `authenticate` details\n\nHow strictly does the server validate its input? Compared to reference\nimplementation. This part is important since it will make or break the usability\nof your api/app. Just throwing SecurityException for any of these is not\ngranular enough.\n\n - [x] server cannot parse header -\u003e `FaultyAuthorizationHeader`\n - [x] server cannot find Hawk scheme in header -\u003e `FaultyAuthorizationHeader`\n - [x] id, ts, nonce and mac (required attrs) are supplied -\u003e `MissingAttribute`\n - [x] credential function errors -\u003e `CredsError`\n - [x] mac doesn't match payload -\u003e `BadMac`\n - [x] missing payload hash if payload -\u003e `MissingAttribute`\n - [x] payload hash not matching -\u003e `BadPayloadHash of hash_given * hash_calculated`\n - [x] nonce reused -\u003e `NonceError AlreadySeen`, with in-memory cache\n - [x] stale timestamp -\u003e `StaleTimestamp`\n\n##### Hints when not under attack (in dev)\n\nIf you see `CredsError`, it's most likely a problem that you can't find the user\nwith your repository function.\n\nIf you see `BadMac`, it means probably means you haven't fed the right parameters\nto `authenticate`. Log the input parameters, verify that host and port match\n(are you [behind a][revproxy-read] [reverse proxy][revproxy]?) and check that the\nlength of the content is the same on the client as on the server.\n\nThe `BadMac` error comes from hashing a normalised string of these parameters:\n\n - hawk header version\n - type of normalisation ('header' in this case)\n - timestamp\n - nonce\n - method\n - resource (aka PathAndQuery for the constructed Uri)\n - host\n - port\n - hash value\n - ext if there is one\n - app if there is one\n - dlg if there is app and if there is one\n\nIf you see PadPayloadHash, it means that the MAC check passed, so you're\nprobably looking at an empty byte array, or your Content-Type isn't being passed\nto the server properly, or the server implementation doesn't feed the correct\nContent-Type header (e.g. it doesn't trim the stuff after the first MimeType\ndeclaration, before the semi-colon `;`).\n\n### `Logibit.Hawk.Crypto`\n\nThe crypto module contains functions for validating the pieces of the request.\n\n - [x] genNormStr - generate a normalised string for a request/auth data\n - [x] calcPayloadHash - calculates the payload hash from a given byte[]\n - [x] calcPayloadHash - calculates the payload hash from a given string\n - [x] calcHmac - calculates the HMAC for a given string\n\n### `Logibit.Hawk.Types`\n\nThis module contains the shared types that you should use for interacting with\nthe above modules.\n\n - HttpMethod - discriminated union type of HTTP methods\n - Algo - The supported hash algorithms\n - Credentials - The credentials object used in both client and server\n - HawkAttributes - Recognised attributes in the Hawk header\n - FullAuth - A structure that represents the fully calculated hawk request data\n   structure\n\nThis module also contains a module-per-type with lenses for that type. The\nlenses follow the same format as [Aether](https://github.com/xyncro/aether)\nrecommends.\n\n### `Logibit.Hawk.Logging`\n\nTypes:\n\n - `LogLevel` - the level of the LogLine.\n - `LogLine` - this is the data structure of the logging module, this is where\n   you feed your data.\n - `Logger interface` - the main interface that we can log to/into.\n - `Logger module` - a module that contains functions equiv. to the instance\n   methods of the logger interface.\n - `NoopLogger : Logger`  - the default logger, you have to replace it yourself\n\nIt's good to know that you have to construct your LogLine yourself. That\nLogLines with Verbose or Debug levels should be sent to the `debug` or `verbose`\nfunctions/methods of the module/interface Logger, which in turn takes functions,\nwhich are evaluated if it's the case that the logging infrastructure is indeed\nlogging at that level.\n\nThis means that logging at that level, and computing the log lines, needs only\nbe done if we can really do something with them.\n\n### Other APIs\n\nThere are some modules that are currently internal as to avoid conflicting with\nexisting code. If these are made 'more coherent' or else moved to external\nlibraries, they can be placed on their own and be made public. The modules like this are `Random`, `Prelude`, `Parse`.\n\n[ng-h]: https://www.nuget.org/packages/Hawk/\n[ng-hs]: https://www.nuget.org/packages/Hawk.Suave/\n[revproxy]: https://github.com/logibit/Logibit.Hawk/blob/master/src/Logibit.Hawk/Types.fs#L323-L333\n[revproxy-read]: https://github.com/logibit/Logibit.Hawk/blob/master/src/Logibit.Hawk.Suave/Hawk.fs#L86-L95\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flogibit%2Flogibit.hawk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flogibit%2Flogibit.hawk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flogibit%2Flogibit.hawk/lists"}