{"id":31601490,"url":"https://github.com/eyevinn/node-cat","last_synced_at":"2025-10-06T07:10:25.049Z","repository":{"id":282439894,"uuid":"947823185","full_name":"Eyevinn/node-cat","owner":"Eyevinn","description":" Node library for generating and validating Common Access Tokens (CTA-5007)","archived":false,"fork":false,"pushed_at":"2025-05-23T07:45:02.000Z","size":513,"stargazers_count":5,"open_issues_count":1,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-02T05:58:21.648Z","etag":null,"topics":["cat","commonaccesstoken","contentprotection","cwt","security"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Eyevinn.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"CONTRIBUTING.md","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,"zenodo":null}},"created_at":"2025-03-13T09:57:49.000Z","updated_at":"2025-05-24T21:48:01.000Z","dependencies_parsed_at":"2025-04-09T15:27:06.296Z","dependency_job_id":"fe5bcfea-03ff-40cc-9626-bcf470575d40","html_url":"https://github.com/Eyevinn/node-cat","commit_stats":null,"previous_names":["eyevinn/node-cat"],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/Eyevinn/node-cat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fnode-cat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fnode-cat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fnode-cat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fnode-cat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Eyevinn","download_url":"https://codeload.github.com/Eyevinn/node-cat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fnode-cat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278572003,"owners_count":26008686,"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","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["cat","commonaccesstoken","contentprotection","cwt","security"],"created_at":"2025-10-06T07:10:21.077Z","updated_at":"2025-10-06T07:10:25.044Z","avatar_url":"https://github.com/Eyevinn.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  Node library for Common Access Token\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n  Node library for Common Access Token (CTA-5007)\n  \u003cbr /\u003e\n  \u003cbr /\u003e\n  :book: \u003cb\u003e\u003ca href=\"https://eyevinn.github.io/node-cat/\"\u003eSDK Documentation\u003c/a\u003e\u003c/b\u003e :eyes:\n  \u003cbr /\u003e  \n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\u003cbr /\u003e\n\n[![npm](https://img.shields.io/npm/v/@eyevinn/cat?style=flat-square)](https://www.npmjs.com/package/@eyevinn/cat)\n[![github release](https://img.shields.io/github/v/release/Eyevinn/node-cat?style=flat-square)](https://github.com/Eyevinn/node-cat/releases)\n[![license](https://img.shields.io/github/license/eyevinn/node-cat.svg?style=flat-square)](LICENSE)\n\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg?style=flat-square)](https://github.com/eyevinn/node-cat/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)\n[![made with hearth by Eyevinn](https://img.shields.io/badge/made%20with%20%E2%99%A5%20by-Eyevinn-59cbe8.svg?style=flat-square)](https://github.com/eyevinn)\n[![Slack](http://slack.streamingtech.se/badge.svg)](http://slack.streamingtech.se)\n\n\u003c/div\u003e\n\nThis is a Node library for generating and validating Common Access Tokens (CTA-5007).\n\nOnline token parser and generator available at [cta-token.net](https://cta-token.net)\n\n![online parser and generator](demo-screenshot.png)\n\nFeatures:\n\n- Generate and Validate Common Access Tokens. Supported claims in table below.\n- HTTP and CloudFront Lambda handlers supporting\n  - Validation and parsing of tokens\n  - Handle automatic renewal of tokens\n  - Token usage count using store plugins (see further down for available plugins)\n  - Log how tokens are being used with logging plugins (see further down for available plugins)\n\nFollowing projects (known to the maintainer) are using this library:\n\n- [CAT Validator service](https://github.com/Eyevinn/cat-validate) by Eyevinn\n\nWant your name on this list? Submit a PR or create an issue.\n\n## Claims Validation Support\n\n### Core Claims\n\n| Claim                                                     | Pass-through | Validate |\n| --------------------------------------------------------- | ------------ | -------- |\n| Issuer (`iss`)                                            | Yes          | Yes      |\n| Audience (`aud`)                                          | Yes          | Yes      |\n| Expiration (`exp`)                                        | Yes          | Yes      |\n| Not Before (`nbf`)                                        | Yes          | Yes      |\n| CWT ID (`cti`)                                            | Yes          | Yes      |\n| Subject (`sub`)                                           | Yes          | n/a      |\n| Issued at (`iat`)                                         | Yes          | n/a      |\n| Common Access Token Replay (`catreplay`)                  | Yes          | Yes      |\n| Common Access Token Probability of Rejection (`catpor`)   | Yes          | No       |\n| Common Access Token Version (`catv`)                      | Yes          | Yes      |\n| Common Access Token Network IP (`catnip`)                 | Yes          | Yes      |\n| Common Access Token URI (`catu`)                          | Yes          | Yes      |\n| Common Access Token Methods (`catm`)                      | Yes          | Yes      |\n| Common Access Token ALPN (`catalpn`)                      | No           | No       |\n| Common Access Token Header (`cath`)                       | Yes          | No       |\n| Common Access Token Geographic ISO3166 (`catgeoiso3166`)  | Yes          | No       |\n| Common Access Token Geographic Coordinate (`catgeocoord`) | Yes          | No       |\n| Geohash (`geohash`)                                       | Yes          | No       |\n| Common Access Token Altitude (`catgeoalt`)                | Yes          | No       |\n| Common Access Token TLS Public Key (`cattpk`)             | Yes          | No       |\n| Common Access Token If (`catif`) claim                    | Yes          | Yes      |\n| Common Access Token Renewal (`catr`) claim                | Yes          | No       |\n\n## Requirements\n\n- Node version 22+\n\n## Installation / Usage\n\n```bash\n% npm install --save @eyevinn/cat\n```\n\n### Validate CTA Common Access Token in HTTP incoming message\n\n```javascript\nimport { HttpValidator, RedisCTIStore } from '@eyevinn/cat';\n\nconst httpValidator = new HttpValidator({\n  keys: [\n    {\n      kid: 'Symmetric256',\n      key: Buffer.from(\n        '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388',\n        'hex'\n      )\n    }\n  ],\n  autoRenewEnabled: true // Token renewal enabled. Optional (default: true)\n  tokenMandatory: true // Optional (default: true)\n  issuer: 'eyevinn',\n  audience: ['one', 'two'], // Optional\n  store: new RedisCTIStore(new URL(process.env.REDIS_URL || 'redis://localhost:6379')), // Where to store token usage count. Optional (default: none)\n  logger: new ConsoleLogger() // Adapter to store all tokens that been successfully validated. Optional (default: none)\n});\n\nconst server = http.createServer((req, res) =\u003e {\n  const result = await httpValidator.validateHttpRequest(\n    req, res\n  );\n  console.log(result.claims); // Claims\n  console.log(res.getHeaders('cta-common-access-token')); // Renewed token\n  console.log(result.count); // Number of times the token has been used\n  res.writeHead(result.status, { 'Content-Type': 'text/plain' });\n  res.end(result.message || 'ok');\n});\nserver.listen(8080, '127.0.0.1', () =\u003e {\n  console.log('Server listening');\n});\n```\n\n```bash\n% curl -v -H 'CTA-Common-Access-Token: 2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2U6MEGmfXP_YGGmfXQAsFGmfXQAtYINTT_KlOyhaV6NaSxFXkqJWfBagSkPkem10dysoA-C0w' http://localhost:8080/\n\u003e GET / HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/8.7.1\n\u003e Accept: */*\n\u003e CTA-Common-Access-Token: 2D3RhEOhAQWhBFBha2FtYWlfa2V5X2hzMjU2U6MEGmfXP_YGGmfXQAsFGmfXQAtYINTT_KlOyhaV6NaSxFXkqJWfBagSkPkem10dysoA-C0w\n\u003e\n* Request completely sent off\n\u003c HTTP/1.1 401 Unauthorized\n\u003c Content-Type: text/plain\n\u003c Date: Sun, 16 Mar 2025 23:11:03 GMT\n\u003c Connection: keep-alive\n\u003c Keep-Alive: timeout=5\n\u003c Transfer-Encoding: chunked\n\u003c\n* Connection #0 to host localhost left intact\nToken has expired\n```\n\n### Validate CTA Common Access Token in a CloudFront request\n\n```javascript\nimport {\n  Context,\n  CloudFrontResponseEvent,\n  CloudFrontResponseCallback\n} from 'aws-lambda';\nimport { HttpValidator } from '@eyevinn/cat';\n\nexport const handler = async (\n  event: CloudFrontResponseEvent,\n  context: Context,\n  callback: CloudFrontResponseCallback\n) =\u003e {\n  const httpValidator = new HttpValidator({\n    keys: [\n      {\n        kid: 'Symmetric256',\n        key: Buffer.from(\n          '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388',\n          'hex'\n        )\n      }\n    ],\n    issuer: 'eyevinn'\n  });\n  const request = event.Records[0].cf.request;\n  const response = event.Records[0].cf.response;\n  const result = await httpValidator.validateCloudFrontRequest(request);\n  // If renewed new token is found here given catr type is \"header\"\n  console.log(result.cfResponse.headers['cta-common-access-token']);\n  console.log(result.count); // Number of times the token has been used (undefined if no store is enabled)\n  response = result.cfResponse;\n  if (result.claims) {\n    console.log(result.claims);\n  }\n  callback(null, response);\n};\n```\n\n### Verify token\n\n```javascript\nimport { CAT } from '@eyevinn/cat';\n\nconst validator = new CAT({\n  keys: {\n    Symmetric256: Buffer.from(\n      '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388',\n      'hex'\n    )\n  }\n});\nconst base64encoded =\n  '0YRDoQEEoQRMU3ltbWV0cmljMjU2eKZkOTAxMDNhNzAxNzU2MzZmNjE3MDNhMmYyZjYxNzMyZTY1Nzg2MTZkNzA2YzY1MmU2MzZmNmQwMjY1NmE2ZjZlNjE3MzAzNzgxODYzNmY2MTcwM2EyZjJmNmM2OTY3Njg3NDJlNjU3ODYxNmQ3MDZjNjUyZTYzNmY2ZDA0MWE1NjEyYWViMDA1MWE1NjEwZDlmMDA2MWE1NjEwZDlmMDA3NDIwYjcxSKuCk/+kFmlY';\ntry {\n  const result = await validator.validate(base64encoded, 'mac', {\n    issuer: 'coap://as.example.com'\n  });\n  if (result.error) {\n    console.log(result.error.message);\n  }\n  console.log(result.cat?.claims);\n} catch (err) {\n  // Not valid\n  console.log(err);\n}\n```\n\n### Generate token\n\n```javascript\nimport { CAT } from '@eyevinn/cat';\n\nconst generator = new CAT({\n  keys: {\n    Symmetric256: Buffer.from(\n      '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388',\n      'hex'\n    )\n  }\n});\nconst base64encoded = await generator.generateFromJson(\n  {\n    iss: 'coap://as.example.com',\n    sub: 'jonas',\n    aud: 'coap://light.example.com',\n    exp: 1444064944,\n    nbf: 1443944944,\n    iat: 1443944944,\n    catr: {\n      type: 'header',\n      'header-name': 'cta-common-access-token',\n      expadd: 120,\n      deadline: 60\n    }\n  },\n  {\n    type: 'mac',\n    alg: 'HS256',\n    kid: 'Symmetric256',\n    generateCwtId: true // automatically generate a random CWT Id (cti) claim (default: false)\n  }\n);\n```\n\nThis example generates a token with a `catif` claim.\n\n```javascript\nimport { CAT } from '@eyevinn/cat';\n\nconst generator = new CAT({\n  keys: {\n    Symmetric256: Buffer.from(\n      '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388',\n      'hex'\n    )\n  }\n});\n\nconst json = {\n  iss: 'eyevinn',\n  exp: Math.floor(Date.now() / 1000) - 60,\n  cti: 'foobar',\n  catif: {\n    exp: [\n      307,\n      {\n        Location: [\n          'https://auth.example.net/?CAT=',\n          {\n            iss: null,\n            iat: null,\n            catu: {\n              host: { 'exact-match': 'auth.example.net' }\n            }\n          }\n        ]\n      },\n      'Symmetric256'\n    ]\n  }\n};\nconst base64encoded = await generator.generateFromJson(json, {\n  type: 'mac',\n  alg: 'HS256',\n  kid: 'Symmetric256',\n  generateCwtId: true\n});\n```\n\nProviding above token to the `HttpValidator` the validator will return with a status code `307` and the header `Location` with the value of `https://auth.example.net/?CAT=\u003cnewtoken\u003e` where `\u003cnewtoken\u003e` is created by the JSON specified in the `catif` claim.\n\n## Token store plugins\n\nTo enable token usage count the HTTP validators requires a way to store the token id:s that has been used. The following types of stores are supported today.\n\n### Memory Store\n\n```javascript\nimport { MemoryCTIStore } from '@eyevinn/cat';\nconst store = new MemoryCTIStore();\n```\n\n### Redis Store\n\n```javascript\nimport { RedisCTIStore } from '@eyevinn/cat';\nconst store = new RedisCTIStore('redis://localhost:6379');\n```\n\n### Custom Store\n\nTo implement your own store you implement the interface `ICTIStore`, for example:\n\n```javascript\nimport { ICTIStore, CommonAccessToken } from '@eyvinn/cat';\n\nclass MyStore implements ICTIStore {\n  async storeToken(token: CommonAccessToken): Promise\u003cnumber\u003e {\n    const cti = token.cti;\n    // Store CTI in your key/value store and return new count\n  }\n\n  async getTokenCount(token: CommonAccessToken): Promise\u003cnumber\u003e {\n    const cti = token.cti;\n    // Return current token count for a CTI\n  }\n}\n```\n\n## Token logging plugin\n\nLog how tokens are being used to be able to analyze detect usage anamolies with a token logging plugin. The following logging plugins are provided today.\n\n### Console Logger\n\nThis just logs the token to stdout in a JSON format.\n\n```javascript\nimport { ConsoleLogger } from '@eyevinn/cat';\nconst logging = new ConsoleLogger();\n// Will output for example\n// {\"cti\":\"b43c9cef64ca7dc83af8a33f39fc7168\",\"timestamp\":1742386792234,\"iat\":1742386782,\"exp\":1742386902,\"sub\":\"jonas\"}\n// {\"cti\":\"b43c9cef64ca7dc83af8a33f39fc7168\",\"timestamp\":1742386799501,\"iat\":1742386782,\"exp\":1742386902,\"sub\":\"jonas\"}\n```\n\n### Custom Logger\n\nTo implement your own logger you implement the interface `ITokenLogger`, for example:\n\n```javascript\nimport { ITokenLogger, CommonAccessToken } from '@eyvinn/cat';\n\n// Pseudo code below\nclass MyLogger implements ITokenLogger {\n  async logToken(token: CommonAccessToken): Promise\u003cvoid\u003e {\n    if (!this.db.connected) {\n      await this.db.connect();\n    }\n    this.db.insert(token);\n  }\n}\n```\n\n## Token Reuse Detection\n\nEnforcing and detecting invalid token reuse when `catreplay` claim has a value of 2 is an implementation-defined process.\n\nYou provide a custom callback function to the HTTP validator. This callback is provided with the Common Access Token, store and logger. Pseudo code as an example.\n\n```javascript\nclass MyLogger implements ITokenLogger {\n  async logToken(token: CommonAccessToken): Promise\u003cvoid\u003e {\n    if (!this.db.connected) {\n      await this.db.connect();\n    }\n    this.db.insert(token);\n  }\n  async getLogsForToken(token: CommonAccessToken): Promise\u003cLogList[]\u003e {\n    return await this.db.find({ cti: token.cti });\n  }\n}\n\nconst reuseDetection = async (\n  cat: CommonAccessToken,\n  store?: ICTIStore,\n  logger?: ITokenLogger\n) =\u003e {\n  if (logger) {\n    const logs = await logger.getLogsForToken(cat);\n    return tokenLogsInPeriod(logs, 5) \u003e 10; // two many uses within a 5 second period\n  }\n}\n\nconst httpValidator = new HttpValidator({\n  keys: [\n    {\n      kid: 'Symmetric256',\n      key: Buffer.from(\n        '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388',\n        'hex'\n      )\n    }\n  ],\n  issuer: 'eyevinn',\n  logger: new MyLogger(),\n  reuseDetection\n});\n...\n```\n\n## Development\n\n\u003c!--Add clear instructions on how to start development of the project here --\u003e\n\n## Contributing\n\nSee [CONTRIBUTING](CONTRIBUTING.md)\n\n## License\n\nThis project is licensed under the MIT License, see [LICENSE](LICENSE).\n\n# Support\n\nJoin our [community on Slack](http://slack.streamingtech.se) where you can post any questions regarding any of our open source projects. Eyevinn's consulting business can also offer you:\n\n- Further development of this component\n- Customization and integration of this component into your platform\n- Support and maintenance agreement\n\nContact [sales@eyevinn.se](mailto:sales@eyevinn.se) if you are interested.\n\n# About Eyevinn Technology\n\n[Eyevinn Technology](https://www.eyevinntechnology.se) is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor. As our way to innovate and push the industry forward we develop proof-of-concepts and tools. The things we learn and the code we write we share with the industry in [blogs](https://dev.to/video) and by open sourcing the code we have written.\n\nWant to know more about Eyevinn and how it is to work here. Contact us at work@eyevinn.se!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyevinn%2Fnode-cat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feyevinn%2Fnode-cat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyevinn%2Fnode-cat/lists"}