[
  {
    "path": ".git-blame-ignore-revs",
    "content": "# Scala Steward: Reformat with scalafmt 3.7.2\ne8c410d04442fe9ac7aa50df34a398972a602cdc\n\n# Scala Steward: Reformat with scalafmt 3.7.17\n14d9145808edddb1d80975faf427882b2e081e03\n\n# Scala Steward: Reformat with scalafmt 3.8.3\n7013c976689533b3513842d28925048c75cb592e\n\n# Scala Steward: Reformat with scalafmt 3.9.7\n427d1611771af19de6fd3a56f78c60b0ea18e910\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Docs\non:\n  push:\n    tags: [\"*\"]\nconcurrency:\n  group: docs\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 11\n          cache: sbt\n      - uses: sbt/setup-sbt@v1\n      - name: \"Get latest tag\"\n        id: previoustag\n        uses: \"WyriHaximus/github-action-get-previous-tag@v1\"\n      - name: Build\n        run: sbt 'set docs/version := \"${{ steps.previoustag.outputs.tag }}\".drop(1)' docs/makeSite\n      - name: setup git config\n        run: |\n          git config user.name \"GitHub Actions Bot\"\n          git config user.email \"<>\"\n      - name: Deploy\n        run: |\n          git checkout --orphan gh-pages\n          git add -f docs/target/site\n          git commit -m \"Rebuild GitHub pages\"\n          git filter-branch -f --prune-empty --subdirectory-filter docs/target/site\n          git push -f origin gh-pages\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  push:\n    branches: [master, main]\n    tags: [\"*\"]\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 8\n          cache: sbt\n      - uses: sbt/setup-sbt@v1\n      - run: sbt ci-release\n        env:\n          PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}\n          PGP_SECRET: ${{ secrets.PGP_SECRET }}\n          SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}\n          SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: CI\n\non:\n  pull_request:\n    branches: [master, main]\n  push:\n    branches: [master, main]\n\nenv:\n  SCALA212: 2.12.20\n  SCALA213: 2.13.14\n  SCALA3: 3.3.0\n\njobs:\n  linting:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: \"actions/checkout@v3\"\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 11\n          cache: sbt\n      - uses: sbt/setup-sbt@v1\n      - name: Checking code formatting\n        run: sbt formatCheck\n  docs-check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: \"actions/checkout@v3\"\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 11\n          cache: sbt\n      - uses: sbt/setup-sbt@v1\n      - name: Check the documentation\n        run: sbt docs/makeSite\n  mima:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: \"actions/checkout@v3\"\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 11\n          cache: sbt\n      - uses: sbt/setup-sbt@v1\n      - name: Report binary issues\n        run: \"sbt ${{ matrix.project }}/mimaReportBinaryIssues\"\n    strategy:\n      matrix:\n        project:\n          - coreJVM\n          - coreJS\n          - coreNative\n          - playJson\n          - playFramework\n          - circeJVM\n          - circeJS\n          - circeNative\n          - upickleJVM\n          - upickleJS\n          - upickleNative\n          - json4sNative\n          - json4sJackson\n          - argonaut\n          - zioJson\n  tests:\n    runs-on: ubuntu-latest\n    name: Tests ${{ matrix.project }} (${{ matrix.scala }})\n    steps:\n      - uses: \"actions/checkout@v3\"\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 11\n          cache: sbt\n      - uses: sbt/setup-sbt@v1\n      - name: Test\n        run: \"sbt ++${{ matrix.scala }} ${{ matrix.project }}/test\"\n    strategy:\n      matrix:\n        exclude:\n          - project: playFramework\n            scala: $SCALA212\n        project:\n          - coreJVM\n          - coreJS\n          - coreNative\n          - playJson\n          - playFramework\n          - circeJVM\n          - circeJS\n          - circeNative\n          - upickleJVM\n          - upickleJS\n          - upickleNative\n          - json4sNative\n          - json4sJackson\n          - argonaut\n          - zioJson\n        scala:\n          - $SCALA212\n          - $SCALA213\n          - $SCALA3\n"
  },
  {
    "path": ".gitignore",
    "content": ".history\ntarget\nproject/project\nproject/target\n.bloop/\n.idea\n.bsp\n.metals/\n.vscode/\nmetals.sbt"
  },
  {
    "path": ".scala-steward.conf",
    "content": "updates.ignore = [\n  { groupId = \"com.google.inject\", artifactId = \"guice\" }\n]\n"
  },
  {
    "path": ".scalafmt.conf",
    "content": "version=3.10.7\nrunner.dialect=scala213\n\nmaxColumn = 100\n\nrewrite.rules = [Imports, AvoidInfix, SortModifiers, PreferCurlyFors]\nrewrite.imports.sort = ascii\nrewrite.imports.groups = [\n  [\"java\\\\..*\", \"javax\\\\..*\", \"scala\\\\..*\"]\n]\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nNote: this file is no longer updated, check the [releases tab](https://github.com/jwt-scala/jwt-scala/releases)\nfor details about each version.\n\n## 6.0.0 (26/02/2021)\n\nImportant: the groupId changed from `fr.pauldijou` to `com.github.jwt-scala`,\nso you need to update your dependencies:\n\n```\nlibraryDependencies += \"com.github.jwt-scala\" %% \"<artifact>\" % \"6.0.0\"\n```\n\n- Upgrade Play to 2.8.7\n- Upgrade Play Json to 2.9.2\n- Upgrade uPickle to 1.2.3\n- Upgrade Argonaut to 6.3.3\n- Upgrade Bouncycastle to 1.68\n- Drop support for Scala 2.11\n\n## 5.0.0 (31/10/2020)\n\n- Make `JwtException` a proper exception (thanks @tpolecat)\n- Update SBT and Scala version (thanks @erwan)\n- Improve string splitting performance (thanks @jfosback)\n- **Breaking** (a little): `JwtSession` should always have an expiration now if you have set a `play.http.session.maxAge`. Before, a few ways to create the session would forget to add it.\n- **Breaking** (also a little): calling `refreshJwtSession` on a Play Result will now truly refresh and set a session if there was one to begin with. Before, it would always set a session with an expiration even if there was nothing.\n- **Breaking** (maybe, maybe not, unsure): renamed KeyFactory algorithm from \"ECDSA\" to \"EC\" to better comply with [Java Security Standard Algorithm Names](https://docs.oracle.com/en/java/javase/14/docs/specs/security/standard-names.html#keyfactory-algorithms), this might impact curve names, check [ParameterSpec Names](https://docs.oracle.com/en/java/javase/14/docs/specs/security/standard-names.html#parameterspec-names) if you are impacted.\n\n## 4.3.0 (29/02/2020)\n\n- Add support for asymmetric algorithms for Play framerwork (thanks @Bangalor)\n- Upgrade Circe to 0.13.0 (thanks @howyp)\n- Upgrade Play and play-json to 2.8.0\n- Upgrade upickle to 0.9.5\n\n## 4.2.0 (03/11/2019)\n\n- No longer fail on unknown algorithm when `signature` is `false` on options (thanks @Baccata)\n- Upgrade upickle to 0.8.0 (thanks @vic)\n\n## 4.1.0 (22/09/2019)\n\n- Upgrade to Circe 0.12.1 (thanks @erwan)\n\n## 4.0.0 (26/08/2019)\n\nThis is not really a breaking change release but I did some small adjustements that might break in very specific cases so not taking chances.\n\n- Support Scala 2.13 for Play framework.\n- Revert Circe to 0.11.1. After consideration, it was probably a mistake to use a Release Candidate version, I should stick to official stable releases.\n- Fix an issue in `Jwt` pure Scala implementation around regexp. Again, try not to use this one, mostly for tests and demos.\n- Fix examples.\n\n## 3.1.0 (30/06/2019)\n\n- If claim.audience is only one item, it will be stringified as a simple string compared to an array if several values. (thanks @msinton)\n\n## 3.0.1 (16/06/2019)\n\n- Fix support for Java 8. (thanks @brakthehack)\n- Improve support for Scala 2.13. (thanks @erwan)\n\n## 3.0.0 (09/06/2019)\n\n- Allow override of the system clock, remove jmockit from tests. (thanks @Ophirr33)\n- `JwtHeader` and `JwtClaim` are no longer `case class` so that you can extend them. (thanks @fahman)\n- Fix Play demo app. (thanks @ma3574)\n- Remove dependency on Bouncycastle. (thanks @brakthehack)\n- Deprecate (but also improved) the pure Scala implementation of `JwtCore`. It's very limited, non-performant and should not be used. I will keep it around for tests and if some people need it.\n\n## 2.1.0 (24/02/2019)\n\n- Upgrade to play-json 2.7.1 (thanks @etspaceman)\n- Add Scala 2.13.0-M5 to cross compilation for projects supporting it (thanks @2m and @ennru)\n- Move JwtHeader and JwtClaim to basic classes (thanks @fahman)\n\n## 2.0.0 (13/02/2019)\n\n- Upgrade to Play 2.7.0 (thanks @prakhunov)\n- Upgrade to play-json 2.7.0 (thanks @etspaceman)\n- Drop support for Java 6 and 7\n\n## 1.1.0 (09/01/2019)\n\n- Upgrade to uPickle 0.7.1 (thanks @edombowsky)\n- Add support for Argonaut (thanks @isbodand)\n\n## 1.0.0 (25/11/2018)\n\n- Bump bouncyCastle version to fix [CVE-2018-1000613](cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000613) (thanks @djamelz)\n- Also 1.0.0 for no reason except no feature was needed over the last months.\n\n## 0.19.0 (20/10/2018)\n\n**Breaking change**\n\nThis is actually a simple one but still... fixed a typo at `asymmetric` missing one `m`, just need to rename a few types to fix your code (thanks @DrPhil).\n\n- Add support to `spray-json` (thanks @Slakah)\n- Bump some versions (thanks @vhiairrassary)\n\n## 0.18.0 (09/10/2018)\n\n- Add support to `aud` being a simple string on uPickle (thanks @deterdw)\n- Make all `parseHeader` and `parseClaim` methods public.\n\n## 0.17.0 (29/07/2018)\n\n- After consideration, release #84 , which mostly allow users to write custom parsers by extending jwt-scala ones. Doc page can be found here.\n\n## 0.16.0 (05/03/2018)\n\n- Adding Key ID property to JwtHeader as `kid` in JSON payload\n\n## 0.15.0 (24/02/2018)\n\n- Upgrade to uPickle 0.5.1\n- Upgrade to Circe 0.9.1 (thanks @jan0sch)\n\n## 0.14.1 (30/10/2017)\n\n- Fix exception when `play.http.session.maxAge` is `null` in Play 2.6.x (thanks @austinpernell)\n\n## 0.14.0 (07/07/2017)\n\n- Add `play.http.session.jwtResponseName` to customize response header in Play (thanks @Isammoc)\n- Fix code snippet style in docs\n\n## 0.13.0 (08/06/2017)\n\n- Upgrade to Circe 0.8.0 (thanks @dvic)\n- Play 2.6 support (thanks @perotom)\n- Bouncy Castle 1.57 (thanks @rwhitworth)\n\n## 0.12.1 (29/03/2017)\n\n- Support spaces in JSON for pure Scala JWT\n\n## 0.12.0 (20/02/2017)\n\n- **Breaking changes** I liked having all implicits directly inside the package object but it started to create problems. When generating the documentation, which depends on all projects, we had runtime errors while all tests were green, but they are ran on project at a time. Also, it means all implicits where always present on the scope which might not be the best option. So the idea is to move them from the package object to the `JwtXXX` object. For example, for Play Json:\n\n```scala\n// Before\n// JwtJson.scala.\npackage pdi.jwt\n\nobject JwtJson extends JwtJsonCommon[JsObject] {\n  // stuff...\n}\n\n// package.scala\npackage pdi\n\npackage object jwt extends JwtJsonImplicits {}\n\n// --------------------------------------------------------\n// After\n// JwtJson.scala.\npackage pdi.jwt\n\nobject JwtJson extends JwtJsonCommon[JsObject] with JwtJsonImplicits {\n  // stuff...\n}\n```\n\n## 0.11.0 (19/02/2017)\n\n- Drop Scala 2.10\n- Play support is back\n\n## 0.10.0 (02/02/2017)\n\n- Support Scala 2.12.0\n- Drop Play Framework support until it supports Scala 2.12\n- Add uPickle support (thanks @alonsodomin)\n- Update Play Json to 2.6.0-M1 for Scala 2.12 support\n- Update Circe to 0.7.0\n\n## 0.9.2 (10/11/2016)\n\n- Support Circe 0.6.0 (thanks @TimothyKlim )\n\n## 0.9.1 (10/11/2016)\n\n- Support Json4s 3.5.0 (thanks @sanllanta)\n\n## 0.9.0 (08/10/2016)\n\n- Transformation of Signature to ASN.1 DER for ECDSA Algorithms (thanks @bestehle)\n- Remove algorithm aliases to align with [JWA spec](https://tools.ietf.org/html/rfc7518#section-3.1)\n\n## 0.8.1 (04/09/2016)\n\n- Update to Circe 0.5.0\n\n## 0.8.0 (05/07/2016)\n\n- Update to Circe 0.4.1\n- `audience` is now `Set[String]` rather than just `String` inside `Claim` according to JWT spec. API using `String` still available.\n- Use `org.bouncycastle.util.Arrays.constantTimeAreEqual` to check signature rather than home made function.\n- Remove Play Legacy since Play 2.5+ only supports Java 1.8+\n\n## 0.7.1 (20/04/2016)\n\nAdd `leeway` support in `JwtOptions`\n\n## 0.7.0 (17/03/2016)\n\nSupport for Circe 0.3.0\n\n## 0.6.0 (09/03/2016)\n\nSupport for Play Framework 2.5.0\n\n## 0.5.1 (05/03/2016)\n\nFix bug not-escaping quotation mark `\"` when stringifying JSON.\n\n## 0.5.0 (31/12/2015)\n\n### Circe support\n\nThanks to @dwhitney , `JWT Scala` now has support for [Circe](https://github.com/travisbrown/circe). Check out [samples](http://pauldijou.fr/jwt-scala/samples/jwt-circe/) and [Scaladoc](http://pauldijou.fr/jwt-scala/api/latest/jwt-circe/).\n\n### Disable validation\n\nWhen decoding, `JWT Scala` also performs validation. If you need to decode an invalid token, you can now use a `JwtOptions` as the last argument of any decoding function to disable validation checks like expiration, notBefore and signature. Read the **Options** section of the [core sample](http://pauldijou.fr/jwt-scala/samples/jwt-core/) to know more.\n\n### Fix null session in Play 2.4\n\nSince 2.4, Play assign `null` as default value for some configuration keys which throw a `ConfigException.Null` in TypeSafe config lib. This should be fixed with the new configuration system at some point in the future. In the mean time, all calls reading the configuration will be wrapped in a try/catch to prevent that.\n\n## 0.4.1 (30/09/2015)\n\nFix tricky bug inside all JSON libs not supporting correctly the `none` algorithm.\n\n## 0.4.0 (24/07/2015)\n\nThanks a lot to @drbild for helping review the code around security vulnerabilities.\n\n### Now on Maven\n\nAll the sub-projects are now released directly on Maven Central. Since Sonatype didn't accept `pdi` as the groupId, I had to change it to `com.pauldijou`. Sorry about that, you will need to quickly update your `build.sbt` (or whatever file contains your dependencies).\n\n### Breaking changes\n\n**Good news** Those changes don't impact the `jwt-play` lib, only low level APIs.\n\nAll decoding and validating methods with a `key: String` are now removed for security reasons. Please use their counterpart which now needs a 3rd argument corresponding to the list of algorithms that the token can be signed with. This list cannot mix HMAC and asymetric algorithms (like RSA or ECDSA). This is to prevent a server using RSA with a String key to receive a forged token signed with a HMAC algorithm and the RSA public key to be accepted using the same RSA public key as the HMAC secret key by default. You can learn more by reading [this article](https://www.timmclean.net/2015/03/31/jwt-algorithm-confusion.html).\n\n```scala\n// Before\nval claim = Jwt.decode(token, key)\n\n// After (knowing that you only expect a HMAC 256)\nval claim = Jwt.decode(token, key, Seq(JwtAlgorithm.HS256))\n// After (supporting all HMAC algorithms)\nval claim = Jwt.decode(token, key, JwtAlgorithm.allHmac)\n```\n\nIf you are using `SecretKey` or `PublicKey`, the list of algorithms is optional and will be automatically computed (using `JwtAlgorithm.allHmac` and `JwtAlgorithm.allAsymetric` respesctively) but feel free to provide you own list if you want to restrict the possible algorithms. More security never killed any web application.\n\nWhy not deprecate them? I considered doing that but I decided to enforce the security fix. I'm pretty sure that most people only use one HMAC algorithm with a String key and it will force them to edit their code but it should be a minor edit since you usually only decode tokens once or twice inside a code base. The fact that the project is still very new and at a `0.x` version played in the decision.\n\n### Fixes\n\nFix a security vulnerability around timing attacks.\n\n### Features\n\nAdd implicit class to convert `JwtHeader` and `JwtClaim` to `JsValue` or `JValue`. See [examples for Play JSON](http://pauldijou.fr/jwt-scala/samples/jwt-play-json/) or [examples for Json4s](http://pauldijou.fr/jwt-scala/samples/jwt-json4s/).\n\n```scala\n// Play JSON\nJwtHeader(JwtAlgorithm.HS256).toJsValue\nJwtClaim().by(\"me\").to(\"you\").about(\"something\").issuedNow.startsNow.expiresIn(15).toJsValue\n\n// Json4s\nJwtHeader(JwtAlgorithm.HS256).toJValue\nJwtClaim().by(\"me\").to(\"you\").about(\"something\").issuedNow.startsNow.expiresIn(15).toJValue\n```\n\n## 0.2.1 (24/07/2015)\n\nSame as `0.4.0` but targeting Play 2.3\n\n## 0.3.0 (08/06/2015)\n\n### Breaking changes\n\n- move exceptions to their own package\n- move algorithms to their own package\n\n### Features\n\n- support Play 2.4.0\n\n## 0.2.0 (02/06/2015)\n\n### Breaking changes\n\n- removed all `Option` from API. Now, it's either nothing or a valid key. It shouldn't have a big impact since the majority of users were using valid keys already.\n- when decoding a token to a `Tuple3`, the last part representing the signature is now a `String` rather than an `Option[String]`.\n\n### New features\n\n- full support for `SecretKey` for HMAC algorithms\n- full support for `PrivateKey` and `PublicKey` for RSA and ECDSA algorithms\n- Nearly all API now have 4 possible signatures (note: `JwtAsymetricAlgorithm` is either a RSA or a ECDSA algorithm)\n  - `method(...)`\n  - `method(..., key: String, algorithm: JwtAlgorithm)`\n  - `method(..., key: SecretKey, algorithm: JwtHmacAlgorithm)`\n  - `method(..., key: PrivateKey/PublicKey, algorithm: JwtAsymetricAlgorithm)`\n\nUse `PrivateKey` when encoding and `PublicKey` when decoding or verifying.\n\n### Bug fixes\n\n- Some ECDSA algorithms were extending the wrong super-type\n- `{\"algo\":\"none\"}` header was incorrectly supported\n\n## 0.1.0 (18/05/2015)\n\nNo code change from 0.0.6, just more doc and tests.\n\n## 0.0.6 (14/05/2015)\n\nAdd support for Json4s (both Native and Jackson implementations)\n\n## 0.0.5 (13/05/2015)\n\nWe should be API ready. Just need more tests and scaladoc before production ready.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Contributor Guide\n\nFor any bug, new feature, or documentation improvement,\nthe best way to start a conversation is by creating a new issue on github.\n\nYou're welcome to submit PRs right away without creating a ticket (issue) first, but be aware that there\nis no guarantee your PR is going to be merged so your work might be for nothing.\n\n## Tests\n\nContinuous integration will run tests on your PR, needless to say it has to be green to be merged :).\n\nTo run the tests locally:\n\n- Run all tests with `sbt testAll` (if `java.lang.LinkageError`, just re-run the command)\n- Run a single project test, for example `sbt circeProject/test`\n\n## Formatting\n\nThe project using [Scalafmt](https://scalameta.org/scalafmt/) for formatting.\n\nBefore submitting your PR you can format the code by running `sbt format`, but the best way is to configure your IDE/Editor\nto pick up the Scalafmt config from the repo and format it automatically. It is supported at least by IntelliJ and VSCode.\n\n## Documentation\n\nTo have a locally running doc website and test your documentation changes:\n\n- `sbt ~docs/makeMicrosite`\n- `cd docs/target/site`\n- `jekyll serve -b /jwt-scala`\n- Go to [http://localhost:4000/jwt-scala/](http://localhost:4000/jwt-scala/)\n\n## Publishing\n\nCreate a release with a new tag on GitHub, and a new version with automatically be published to Sonatype by Github Actions with the corresponding version number.\n\nDocumentation (microsite + scaladoc) can be published with:\n\n- `sbt publish-doc`\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.md",
    "content": "# JWT Scala\n\nScala support for JSON Web Token ([JWT](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token)).\nSupports Java 8+, Scala 2.12, Scala 2.13 and Scala 3 (for json libraries that support it).\nDependency free.\nOptional helpers for Play Framework, Play JSON, Json4s Native, Json4s Jackson, Circe, uPickle and Argonaut.\n\n[Contributor's guide](https://github.com/jwt-scala/jwt-scala/blob/main/CONTRIBUTING.md)\n\n## Usage\n\nDetailed documentation is on the [Microsite](https://jwt-scala.github.io/jwt-scala).\n\nJWT Scala is divided in several sub-projects each targeting a specific JSON library,\ncheck the doc from the menu of the Microsite for installation and usage instructions.\n\n## Algorithms\n\nIf you are using `String` key, please keep in mind that such keys need to be parsed. Rather than implementing a super complex parser, the one in JWT Scala is pretty simple and might not work for all use-cases (especially for ECDSA keys). In such case, consider using `SecretKey` or `PrivateKey` or `PublicKey` directly. It is way better for you. All API support all those types.\n\nCheck [ECDSA samples](https://jwt-scala.github.io/jwt-scala/jwt-core-jwt-ecdsa.html) for more infos.\n\n| Name  | Description                    |\n| ----- | ------------------------------ |\n| HMD5  | HMAC using MD5 algorithm       |\n| HS224 | HMAC using SHA-224 algorithm   |\n| HS256 | HMAC using SHA-256 algorithm   |\n| HS384 | HMAC using SHA-384 algorithm   |\n| HS512 | HMAC using SHA-512 algorithm   |\n| RS256 | RSASSA using SHA-256 algorithm |\n| RS384 | RSASSA using SHA-384 algorithm |\n| RS512 | RSASSA using SHA-512 algorithm |\n| ES256 | ECDSA using SHA-256 algorithm  |\n| ES384 | ECDSA using SHA-384 algorithm  |\n| ES512 | ECDSA using SHA-512 algorithm  |\n| EdDSA | EdDSA signature algorithms     |\n\n## Security concerns\n\nThis lib doesn't want to impose anything, that's why, by default, a JWT claim is totally empty. That said, you should always add an `issuedAt` attribute to it, probably using `claim.issuedNow`.\nThe reason is that even HTTPS isn't perfect and having always the same chunk of data transfered can be of a big help to crack it. Generating a slightly different token at each request is way better even if it adds a bit of payload to the response.\nIf you are using a session timeout through the `expiration` attribute which is extended at each request, that's fine too. I can't find the article I read about that vulnerability but if someone has some resources about the topic, I would be glad to link them.\n\n## License\n\nThis software is licensed under the Apache 2 license, quoted below.\n\nCopyright 2021 JWT-Scala Contributors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this project except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "build.sbt",
    "content": "import scala.io.Source\nimport scala.sys.process._\n\nimport com.jsuereth.sbtpgp.PgpKeys._\nimport sbt.Keys._\nimport sbt.Tests._\nimport sbt._\n\nval previousVersion = \"9.4.0\"\nval buildVersion = \"9.4.1\"\n\nval scala212 = \"2.12.20\"\nval scala213 = \"2.13.16\"\nval scala3 = \"3.3.7\"\n\nGlobal / onChangedBuildSource := ReloadOnSourceChanges\nThisBuild / versionScheme := Some(\"early-semver\")\n\nval projects = Seq(\n  \"playJson\",\n  \"json4sNative\",\n  \"json4sJackson\",\n  \"zioJson\",\n  \"argonaut\",\n  \"playFramework\"\n)\nval crossProjects = Seq(\n  \"core\",\n  \"circe\",\n  \"upickle\"\n)\nval allProjects = crossProjects.flatMap(p => Seq(s\"${p}JVM\", s\"${p}JS\", s\"${p}Native\")) ++ projects\n\naddCommandAlias(\"publish-doc\", \"docs/makeMicrosite; docs/publishMicrosite\")\n\naddCommandAlias(\"testAll\", allProjects.map(p => p + \"/test\").mkString(\";\", \";\", \"\"))\n\naddCommandAlias(\"format\", \"all scalafmtAll scalafmtSbt\")\n\naddCommandAlias(\"formatCheck\", \"all scalafmtCheckAll scalafmtSbtCheck\")\n\nlazy val cleanScript = taskKey[Unit](\"Clean tmp files\")\ncleanScript := {\n  \"./scripts/clean.sh\" !\n}\n\nlazy val docsMappingsAPIDir: SettingKey[String] =\n  settingKey[String](\"Name of subdirectory in site target directory for api docs\")\n\nval crossVersionAll = Seq(scala212, scala213, scala3)\nval crossVersionNo212 = Seq(scala213, scala3)\n\nval baseSettings = Seq(\n  organization := \"com.github.jwt-scala\",\n  ThisBuild / scalaVersion := scala213,\n  crossScalaVersions := crossVersionAll,\n  autoAPIMappings := true,\n  libraryDependencies ++= Seq(Libs.munit.value, Libs.munitScalacheck.value),\n  testFrameworks += new TestFramework(\"munit.Framework\"),\n  mimaFailOnNoPrevious := false,\n  Test / aggregate := false,\n  Test / fork := true,\n  Test / parallelExecution := false,\n  Compile / doc / scalacOptions ~= (_.filterNot(_ == \"-Xfatal-warnings\")),\n  Compile / doc / scalacOptions ++= Seq(\n    \"-no-link-warnings\" // Suppresses problems with Scaladoc @throws links\n  ),\n  scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match {\n    case Some((2, _)) => Seq(\"-Xsource:3\")\n    case _            => Nil\n  }),\n  javacOptions ++= Seq(\"-source\", \"1.8\", \"-target\", \"1.8\")\n)\n\nval publishSettings = Seq(\n  homepage := Some(url(\"https://jwt-scala.github.io/jwt-scala/\")),\n  apiURL := Some(url(\"https://jwt-scala.github.io/jwt-scala/api/\")),\n  Test / publishArtifact := false,\n  licenses += (\"Apache-2.0\", url(\"http://www.apache.org/licenses/LICENSE-2.0\")),\n  pomIncludeRepository := { _ => false },\n  scmInfo := Some(\n    ScmInfo(\n      url(\"https://github.com/jwt-scala/jwt-scala\"),\n      \"scm:git@github.com:jwt-scala/jwt-scala.git\"\n    )\n  ),\n  developers := List(\n    Developer(\n      id = \"pdi\",\n      name = \"Paul Dijou\",\n      email = \"paul.dijou@gmail.com\",\n      url = url(\"http://pauldijou.fr\")\n    ),\n    Developer(\n      id = \"erwan\",\n      name = \"Erwan Loisant\",\n      email = \"erwan@loisant.com\",\n      url = url(\"https://caffeinelab.net\")\n    )\n  ),\n  publishConfiguration := publishConfiguration.value.withOverwrite(true),\n  publishSignedConfiguration := publishSignedConfiguration.value.withOverwrite(true),\n  publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true),\n  publishLocalSignedConfiguration := publishLocalSignedConfiguration.value.withOverwrite(true)\n)\n\nval noPublishSettings = Seq(\n  publish := (()),\n  publishLocal := (()),\n  publishArtifact := false\n)\n\nlazy val commonJsSettings = Seq(\n  Test / fork := false\n)\n\n// Normal published settings\nval releaseSettings = baseSettings ++ publishSettings\n\n// Local non-published projects\nval localSettings = baseSettings ++ noPublishSettings\n\nlazy val jwtScala = project\n  .in(file(\".\"))\n  .settings(localSettings)\n  .settings(\n    name := \"jwt-scala\"\n  )\n  .aggregate(\n    json4sNative,\n    json4sJackson,\n    circe.jvm,\n    circe.js,\n    circe.native,\n    upickle.jvm,\n    upickle.js,\n    upickle.native,\n    zioJson,\n    playFramework,\n    argonaut\n  )\n  .dependsOn(\n    json4sNative,\n    json4sJackson,\n    circe.jvm,\n    circe.js,\n    circe.native,\n    upickle.jvm,\n    upickle.js,\n    upickle.native,\n    zioJson,\n    playFramework,\n    argonaut\n  )\n  .settings(crossScalaVersions := List())\n\nlazy val docs = project\n  .in(file(\"docs\"))\n  .enablePlugins(\n    SitePreviewPlugin,\n    SiteScaladocPlugin,\n    ScalaUnidocPlugin,\n    ParadoxSitePlugin,\n    ParadoxMaterialThemePlugin\n  )\n  .settings(name := \"jwt-docs\")\n  .settings(localSettings)\n  .settings(\n    libraryDependencies ++= Seq(\"org.playframework\" %% \"play-test\" % Versions.play),\n    ScalaUnidoc / siteSubdirName := \"api\",\n    addMappingsToSiteDir(\n      ScalaUnidoc / packageDoc / mappings,\n      ScalaUnidoc / siteSubdirName\n    ),\n    ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(\n      core.jvm,\n      circe.jvm,\n      json4sNative,\n      upickle.jvm,\n      zioJson,\n      playJson,\n      playFramework,\n      argonaut,\n      zioJson\n    ),\n    baseSettings,\n    publishArtifact := false,\n    Compile / paradoxMaterialTheme ~= (_.withRepository(\n      uri(\"https://github.com/jwt-scala/jwt-scala\")\n    )),\n    packageSite / artifactPath := new java.io.File(\"target/artifact.zip\")\n  )\n  .dependsOn(playFramework, json4sNative, circe.jvm, upickle.jvm, zioJson, argonaut)\n\nlazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)\n  .crossType(CrossType.Full)\n  .settings(releaseSettings)\n  .settings(name := \"jwt-core\", libraryDependencies ++= Seq(Libs.bouncyCastle))\n  .jsSettings(commonJsSettings)\n  .jsSettings(\n    libraryDependencies ++= Seq(\n      Libs.scalaJavaTime.value,\n      Libs.scalajsSecureRandom.value\n    )\n  )\n  .nativeSettings(\n    libraryDependencies ++= Seq(\n      Libs.scalaJavaTime.value\n    ),\n    Test / fork := false\n  )\n\nlazy val jsonCommon = crossProject(JSPlatform, JVMPlatform, NativePlatform)\n  .crossType(CrossType.Pure)\n  .in(file(\"json/common\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-json-common\"\n  )\n  .jsSettings(commonJsSettings)\n  .nativeSettings(Test / fork := false)\n  .aggregate(core)\n  .dependsOn(core % \"compile->compile;test->test\")\n\nlazy val playJson = project\n  .in(file(\"json/play-json\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-play-json\",\n    libraryDependencies ++= Seq(Libs.playJson)\n  )\n  .aggregate(jsonCommon.jvm)\n  .dependsOn(jsonCommon.jvm % \"compile->compile;test->test\")\n\nlazy val circe = crossProject(JSPlatform, JVMPlatform, NativePlatform)\n  .crossType(CrossType.Full)\n  .in(file(\"json/circe\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-circe\",\n    libraryDependencies ++= Seq(\n      Libs.circeCore.value,\n      Libs.circeJawn.value,\n      Libs.circeParse.value,\n      Libs.circeGeneric.value % \"test\"\n    )\n  )\n  .jsSettings(commonJsSettings)\n  .nativeSettings(Test / fork := false)\n  .aggregate(jsonCommon)\n  .dependsOn(jsonCommon % \"compile->compile;test->test\")\n\nlazy val upickle = crossProject(JSPlatform, JVMPlatform, NativePlatform)\n  .crossType(CrossType.Full)\n  .in(file(\"json/upickle\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-upickle\",\n    libraryDependencies ++= Seq(Libs.upickle.value)\n  )\n  .jsSettings(commonJsSettings)\n  .nativeSettings(Test / fork := false)\n  .aggregate(jsonCommon)\n  .dependsOn(jsonCommon % \"compile->compile;test->test\")\n\nlazy val zioJson = project\n  .in(file(\"json/zio-json\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-zio-json\",\n    libraryDependencies ++= Seq(Libs.zioJson)\n  )\n  .aggregate(jsonCommon.jvm)\n  .dependsOn(jsonCommon.jvm % \"compile->compile;test->test\")\n\nlazy val json4sCommon = project\n  .in(file(\"json/json4s-common\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-json4s-common\",\n    libraryDependencies ++= Seq(Libs.json4sCore)\n  )\n  .aggregate(jsonCommon.jvm)\n  .dependsOn(jsonCommon.jvm % \"compile->compile;test->test\")\n\nlazy val json4sNative = project\n  .in(file(\"json/json4s-native\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-json4s-native\",\n    libraryDependencies ++= Seq(Libs.json4sNative)\n  )\n  .aggregate(json4sCommon)\n  .dependsOn(json4sCommon % \"compile->compile;test->test\")\n\nlazy val json4sJackson = project\n  .in(file(\"json/json4s-jackson\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-json4s-jackson\",\n    libraryDependencies ++= Seq(Libs.json4sJackson)\n  )\n  .aggregate(json4sCommon)\n  .dependsOn(json4sCommon % \"compile->compile;test->test\")\n\nlazy val argonaut = project\n  .in(file(\"json/argonaut\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-argonaut\",\n    libraryDependencies ++= Seq(Libs.argonaut)\n  )\n  .aggregate(jsonCommon.jvm)\n  .dependsOn(jsonCommon.jvm % \"compile->compile;test->test\")\n\ndef groupPlayTest(tests: Seq[TestDefinition], files: Seq[File]) = tests.map { t =>\n  val options = ForkOptions()\n  Group(t.name, Seq(t), SubProcess(options))\n}\n\nlazy val playFramework = project\n  .in(file(\"play\"))\n  .settings(releaseSettings)\n  .settings(\n    name := \"jwt-play\",\n    crossScalaVersions := crossVersionNo212,\n    libraryDependencies ++= Seq(Libs.play, Libs.playTest, Libs.guice),\n    Test / testGrouping := groupPlayTest(\n      (Test / definedTests).value,\n      (Test / dependencyClasspath).value.files\n    )\n  )\n  .aggregate(playJson)\n  .dependsOn(playJson % \"compile->compile;test->test\")\n"
  },
  {
    "path": "core/jvm/src/test/scala/JwtSpec.scala",
    "content": "package pdi.jwt\n\nimport scala.util.{Success, Try}\n\nimport pdi.jwt.algorithms.*\nimport pdi.jwt.exceptions.*\n\nclass JwtSpec extends munit.FunSuite with Fixture {\n  val afterExpirationJwt: Jwt = Jwt(afterExpirationClock)\n  val beforeNotBeforeJwt: Jwt = Jwt(beforeNotBeforeClock)\n  val afterNotBeforeJwt: Jwt = Jwt(afterNotBeforeClock)\n  val validTimeJwt: Jwt = Jwt(validTimeClock)\n\n  def battleTestEncode(d: DataEntryBase, key: String, jwt: Jwt) = {\n    assertEquals(d.tokenEmpty, jwt.encode(claim))\n    assertEquals(d.token, jwt.encode(d.header, claim, key, d.algo))\n    assertEquals(d.token, jwt.encode(claim, key, d.algo))\n    assertEquals(d.tokenEmpty, jwt.encode(claimClass))\n    assertEquals(d.token, jwt.encode(claimClass, key, d.algo))\n    assertEquals(d.token, jwt.encode(d.headerClass, claimClass, key))\n  }\n\n  test(\"should parse JSON with spaces\") {\n    assert(Jwt.isValid(tokenWithSpaces))\n  }\n\n  test(\"should decode subject with a dash\") {\n    Jwt.decode(validTimeJwt.encode(\"\"\"{\"sub\":\"das-hed\"\"\"\")) match {\n      case Success(jwt) => assertEquals(jwt.subject, Option(\"das-hed\"))\n      case _            => fail(\"failed decoding token\")\n    }\n  }\n\n  test(\"should decode subject with an underscore\") {\n    Jwt.decode(validTimeJwt.encode(\"\"\"{\"sub\":\"das_hed\"\"\"\")) match {\n      case Success(jwt) => assertEquals(jwt.subject, Option(\"das_hed\"))\n      case _            => fail(\"failed decoding token\")\n    }\n  }\n\n  test(\"should decode jti with dashes\") {\n    val id = java.util.UUID.randomUUID().toString\n    Jwt.decode(validTimeJwt.encode(s\"\"\"{\"jti\":\"$id\"\"\"\")) match {\n      case Success(jwt) => assertEquals(jwt.jwtId, Option(id))\n      case _            => fail(\"failed decoding token\")\n    }\n  }\n\n  test(\"should decode issuer with dashes\") {\n    Jwt.decode(validTimeJwt.encode(s\"\"\"{\"iss\":\"das-_hed\"\"\"\")) match {\n      case Success(jwt) => assertEquals(jwt.issuer, Option(\"das-_hed\"))\n      case _            => fail(\"failed decoding token\")\n    }\n  }\n  test(\"should encode Hmac\") {\n    data.foreach { d => battleTestEncode(d, secretKey, validTimeJwt) }\n  }\n\n  test(\"should encode RSA\") {\n    dataRSA.foreach { d => battleTestEncode(d, privateKeyRSA, validTimeJwt) }\n  }\n\n  test(\"should encode EdDSA\") {\n    dataEdDSA.foreach { d => battleTestEncode(d, privateKeyEd25519, validTimeJwt) }\n  }\n\n  test(\"should be symmetric\") {\n    data.foreach { d =>\n      testTryAll(\n        validTimeJwt.decodeAll(\n          validTimeJwt.encode(d.header, claim, secretKey, d.algo),\n          secretKey,\n          JwtAlgorithm.allHmac()\n        ),\n        (d.headerClass, claimClass, d.signature),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should be symmetric (RSA)\") {\n    dataRSA.foreach { d =>\n      testTryAllWithoutSignature(\n        validTimeJwt.decodeAll(\n          validTimeJwt.encode(d.header, claim, randomRSAKey.getPrivate, d.algo),\n          randomRSAKey.getPublic,\n          JwtAlgorithm.allRSA()\n        ),\n        (d.headerClass, claimClass),\n        d.algo.fullName\n      )\n\n      testTryAllWithoutSignature(\n        validTimeJwt.decodeAll(\n          validTimeJwt.encode(d.header, claim, randomRSAKey.getPrivate, d.algo),\n          randomRSAKey.getPublic\n        ),\n        (d.headerClass, claimClass),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should be symmetric (ECDSA)\") {\n    dataECDSA.foreach { d =>\n      testTryAllWithoutSignature(\n        validTimeJwt.decodeAll(\n          validTimeJwt.encode(d.header, claim, randomECKey.getPrivate, d.algo),\n          randomECKey.getPublic,\n          JwtAlgorithm.allECDSA()\n        ),\n        (d.headerClass, claimClass),\n        d.algo.fullName\n      )\n    }\n\n  }\n\n  test(\"should be symmetric (EdDSA)\") {\n    dataEdDSA.foreach { d =>\n      testTryAllWithoutSignature(\n        validTimeJwt.decodeAll(\n          validTimeJwt.encode(d.header, claim, randomEd25519Key.getPrivate, d.algo),\n          randomEd25519Key.getPublic,\n          JwtAlgorithm.allEdDSA()\n        ),\n        (d.headerClass, claimClass),\n        d.algo.fullName\n      )\n\n      testTryAllWithoutSignature(\n        validTimeJwt.decodeAll(\n          validTimeJwt.encode(d.header, claim, randomEd25519Key.getPrivate, d.algo),\n          randomEd25519Key.getPublic\n        ),\n        (d.headerClass, claimClass),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should decodeRawAll\") {\n    data.foreach { d =>\n      assertEquals(\n        validTimeJwt.decodeRawAll(d.token, secretKey, JwtAlgorithm.allHmac()),\n        Success((d.header, claim, d.signature)),\n        d.algo.fullName\n      )\n      assertEquals(\n        validTimeJwt.decodeRawAll(d.token, secretKeyOf(d.algo)),\n        Success((d.header, claim, d.signature)),\n        d.algo.fullName\n      )\n      assertEquals(\n        validTimeJwt.decodeRawAll(d.token, secretKeyOf(d.algo), JwtAlgorithm.allHmac()),\n        Success((d.header, claim, d.signature)),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should decodeRaw\") {\n    data.foreach { d =>\n      assertEquals(\n        validTimeJwt.decodeRaw(d.token, secretKey, JwtAlgorithm.allHmac()),\n        Success((claim)),\n        d.algo.fullName\n      )\n      assertEquals(\n        validTimeJwt.decodeRaw(d.token, secretKeyOf(d.algo)),\n        Success((claim)),\n        d.algo.fullName\n      )\n      assertEquals(\n        validTimeJwt.decodeRaw(d.token, secretKeyOf(d.algo), JwtAlgorithm.allHmac()),\n        Success((claim)),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should decodeAll\") {\n    data.foreach { d =>\n      testTryAll(\n        validTimeJwt.decodeAll(d.token, secretKey, JwtAlgorithm.allHmac()),\n        (d.headerClass, claimClass, d.signature),\n        d.algo.fullName\n      )\n\n      testTryAll(\n        validTimeJwt.decodeAll(d.token, secretKeyOf(d.algo)),\n        (d.headerClass, claimClass, d.signature),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should decode\") {\n    data.foreach { d =>\n      assertEquals(\n        validTimeJwt.decode(d.token, secretKey, JwtAlgorithm.allHmac()).get,\n        claimClass,\n        d.algo.fullName\n      )\n\n      assertEquals(\n        validTimeJwt.decode(d.token, secretKeyOf(d.algo)).get,\n        claimClass,\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should validate correct tokens\") {\n\n    data.foreach { d =>\n      assertEquals(\n        (),\n        validTimeJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac()),\n        d.algo.fullName\n      )\n      assert(validTimeJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac()), d.algo.fullName)\n      assertEquals((), validTimeJwt.validate(d.token, secretKeyOf(d.algo)), d.algo.fullName)\n      assert(validTimeJwt.isValid(d.token, secretKeyOf(d.algo)), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      assertEquals(\n        (),\n        validTimeJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        d.algo.fullName\n      )\n      assert(validTimeJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA()), d.algo.fullName)\n    }\n\n    dataEdDSA.foreach { d =>\n      assertEquals(\n        (),\n        validTimeJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),\n        d.algo.fullName\n      )\n      assert(\n        validTimeJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),\n        d.algo.fullName\n      )\n    }\n  }\n\n  def oneLine(key: String) = key.replaceAll(\"\\r\\n\", \" \").replaceAll(\"\\n\", \" \")\n\n  test(\"should validate using RSA keys converted to single line\") {\n    val pubKey = oneLine(publicKeyRSA)\n    dataRSA.foreach { d =>\n      assertEquals(\n        (),\n        validTimeJwt.validate(d.token, pubKey, JwtAlgorithm.allRSA()),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should validate ECDSA from other implementations\") {\n    val publicKey =\n      \"MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg=\"\n    val verifier = (token: String) => {\n      assert(Jwt.isValid(token, publicKey, Seq(JwtAlgorithm.ES512)))\n    }\n    // Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1\n    verifier(\n      \"eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ\"\n    )\n    // Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4\n    verifier(\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn\"\n    )\n  }\n\n  test(\"should invalidate WTF tokens\") {\n    val tokens = Seq(\"1\", \"abcde\", \"\", \"a.b.c.d\")\n\n    tokens.foreach { token =>\n      assert(Try(Jwt.validate(token, secretKey, JwtAlgorithm.allHmac())).isFailure)\n      assert(!Jwt.isValid(token, secretKey, JwtAlgorithm.allHmac()), token)\n    }\n  }\n\n  test(\"should invalidate non-base64 tokens\") {\n    val tokens = Seq(\"a.b\", \"a.b.c\", \"1.2.3\", \"abcde.azer.azer\", \"aze$.azer.azer\")\n\n    tokens.foreach { token =>\n      assert(Try(Jwt.validate(token, secretKey, JwtAlgorithm.allHmac())).isFailure)\n      assert(!Jwt.isValid(token, secretKey, JwtAlgorithm.allHmac()), token)\n    }\n  }\n\n  test(\"should invalidate expired tokens\") {\n    data.foreach { d =>\n      assert(Try(afterExpirationJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac())).isFailure)\n      assert(\n        !afterExpirationJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac()),\n        d.algo.fullName\n      )\n      assert(Try(afterExpirationJwt.validate(d.token, secretKeyOf(d.algo))).isFailure)\n      assert(!afterExpirationJwt.isValid(d.token, secretKeyOf(d.algo)), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      assert(\n        Try(afterExpirationJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA())).isFailure\n      )\n      assert(\n        !afterExpirationJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      assert(\n        Try(\n          afterExpirationJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA())\n        ).isFailure\n      )\n      assert(\n        !afterExpirationJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should validate expired tokens with leeway\") {\n    val options = JwtOptions(leeway = 60)\n\n    data.foreach { d =>\n      afterExpirationJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac(), options)\n      assert(\n        afterExpirationJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac(), options),\n        d.algo.fullName\n      )\n      afterExpirationJwt.validate(d.token, secretKeyOf(d.algo), options)\n      assert(afterExpirationJwt.isValid(d.token, secretKeyOf(d.algo), options), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      afterExpirationJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)\n      assert(\n        afterExpirationJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      afterExpirationJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options)\n      assert(\n        afterExpirationJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should invalidate early tokens\") {\n    data.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, secretKey, d.algo)\n\n      assert(Try(beforeNotBeforeJwt.validate(token, secretKey, JwtAlgorithm.allHmac())).isFailure)\n      assert(!beforeNotBeforeJwt.isValid(token, secretKey, JwtAlgorithm.allHmac()), d.algo.fullName)\n      assert(Try(beforeNotBeforeJwt.validate(token, secretKeyOf(d.algo))).isFailure)\n      assert(!beforeNotBeforeJwt.isValid(token, secretKeyOf(d.algo)), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyRSA, d.algo)\n\n      assert(Try(beforeNotBeforeJwt.validate(token, publicKeyRSA, JwtAlgorithm.allRSA())).isFailure)\n      assert(\n        !beforeNotBeforeJwt.isValid(token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyEd25519, d.algo)\n\n      assert(\n        Try(beforeNotBeforeJwt.validate(token, publicKeyEd25519, JwtAlgorithm.allEdDSA())).isFailure\n      )\n      assert(\n        !beforeNotBeforeJwt.isValid(token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should validate early tokens with leeway\") {\n    val options = JwtOptions(leeway = 60)\n\n    data.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, secretKey, d.algo)\n\n      beforeNotBeforeJwt.validate(token, secretKey, JwtAlgorithm.allHmac(), options)\n      assert(\n        beforeNotBeforeJwt.isValid(token, secretKey, JwtAlgorithm.allHmac(), options),\n        d.algo.fullName\n      )\n      beforeNotBeforeJwt.validate(token, secretKeyOf(d.algo), options)\n      assert(beforeNotBeforeJwt.isValid(token, secretKeyOf(d.algo), options), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyRSA, d.algo)\n\n      assert(Try(beforeNotBeforeJwt.validate(token, publicKeyRSA, JwtAlgorithm.allRSA())).isFailure)\n      assert(\n        !beforeNotBeforeJwt.isValid(token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyEd25519, d.algo)\n\n      assert(\n        Try(beforeNotBeforeJwt.validate(token, publicKeyEd25519, JwtAlgorithm.allEdDSA())).isFailure\n      )\n      assert(\n        !beforeNotBeforeJwt.isValid(token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should invalidate wrong keys\") {\n    data.foreach { d =>\n      assert(\n        Try(\n          validTimeJwt.validate(d.token, \"wrong key\", JwtAlgorithm.allHmac())\n        ).isFailure\n      )\n      assert(!validTimeJwt.isValid(d.token, \"wrong key\", JwtAlgorithm.allHmac()), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      assert(!validTimeJwt.isValid(d.token, \"wrong key\", JwtAlgorithm.allRSA()), d.algo.fullName)\n    }\n\n    dataEdDSA.foreach { d =>\n      assert(!validTimeJwt.isValid(d.token, \"wrong key\", JwtAlgorithm.allEdDSA()), d.algo.fullName)\n    }\n  }\n\n  test(\"should fail on non-exposed algorithms\") {\n    data.foreach { d =>\n      assert(\n        Try(\n          validTimeJwt.validate(d.token, secretKey, Seq.empty[JwtHmacAlgorithm])\n        ).isFailure\n      )\n      assert(\n        !validTimeJwt.isValid(d.token, secretKey, Seq.empty[JwtHmacAlgorithm]),\n        d.algo.fullName\n      )\n    }\n\n    data.foreach { d =>\n      assert(Try(validTimeJwt.validate(d.token, secretKey, JwtAlgorithm.allRSA())).isFailure)\n      assert(!validTimeJwt.isValid(d.token, secretKey, JwtAlgorithm.allRSA()), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      assert(\n        Try(\n          validTimeJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allHmac())\n        ).isFailure\n      )\n      assert(!validTimeJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allHmac()), d.algo.fullName)\n    }\n  }\n\n  test(\"should invalidate wrong algos\") {\n    val token = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJXVEYifQ.e30\"\n    assert(Jwt.decode(token).isFailure)\n    intercept[JwtNonSupportedAlgorithm] { Jwt.decode(token).get }\n  }\n\n  test(\"should decode tokens with unknown algos depending on options\") {\n    val token = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJXVEYifQ.e30\"\n    val decoded = Jwt.decode(token, options = JwtOptions(signature = false))\n    assert(decoded.isSuccess)\n  }\n\n  test(\"should skip expiration validation depending on options\") {\n    val options = JwtOptions(expiration = false)\n\n    data.foreach { d =>\n      afterExpirationJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac(), options)\n      assert(\n        afterExpirationJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac(), options),\n        d.algo.fullName\n      )\n      afterExpirationJwt.validate(d.token, secretKeyOf(d.algo), options)\n      assert(afterExpirationJwt.isValid(d.token, secretKeyOf(d.algo), options), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      afterExpirationJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)\n      assert(\n        afterExpirationJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      afterExpirationJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options)\n      assert(\n        afterExpirationJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should skip notBefore validation depending on options\") {\n    val options = JwtOptions(notBefore = false)\n\n    data.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, secretKey, d.algo)\n\n      beforeNotBeforeJwt.validate(token, secretKey, JwtAlgorithm.allHmac(), options)\n      assert(\n        beforeNotBeforeJwt.isValid(token, secretKey, JwtAlgorithm.allHmac(), options),\n        d.algo.fullName\n      )\n      beforeNotBeforeJwt.validate(token, secretKeyOf(d.algo), options)\n      assert(beforeNotBeforeJwt.isValid(token, secretKeyOf(d.algo), options), d.algo.fullName)\n    }\n\n    dataRSA.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyRSA, d.algo)\n\n      beforeNotBeforeJwt.validate(token, publicKeyRSA, JwtAlgorithm.allRSA(), options)\n      assert(\n        beforeNotBeforeJwt.isValid(token, publicKeyRSA, JwtAlgorithm.allRSA(), options),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      val claimNotBefore = claimClass.startsAt(notBefore)\n      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyEd25519, d.algo)\n\n      beforeNotBeforeJwt.validate(token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options)\n      assert(\n        beforeNotBeforeJwt.isValid(token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options),\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should skip signature validation depending on options\") {\n    val options = JwtOptions(signature = false)\n\n    data.foreach { d =>\n      validTimeJwt.validate(d.token, \"wrong key\", JwtAlgorithm.allHmac(), options)\n      assert(\n        validTimeJwt.isValid(d.token, \"wrong key\", JwtAlgorithm.allHmac(), options),\n        d.algo.fullName\n      )\n    }\n\n    dataRSA.foreach { d =>\n      assert(\n        validTimeJwt.isValid(d.token, \"wrong key\", JwtAlgorithm.allRSA(), options),\n        d.algo.fullName\n      )\n    }\n\n    dataEdDSA.foreach { d =>\n      assert(\n        validTimeJwt.isValid(d.token, \"wrong key\", JwtAlgorithm.allEdDSA(), options),\n        d.algo.fullName\n      )\n    }\n  }\n\n  def testTryAll(\n      t: Try[(JwtHeader, JwtClaim, String)],\n      exp: (JwtHeader, JwtClaim, String),\n      clue: String\n  ) = {\n    assert(t.isSuccess, clue)\n    val (h1, c1, s1) = t.get\n    val (h2, c2, s2) = exp\n    assertEquals(h1, h2)\n    assertEquals(c1, c2)\n    assertEquals(s1, s2)\n  }\n\n  def testTryAllWithoutSignature(\n      t: Try[(JwtHeader, JwtClaim, String)],\n      exp: (JwtHeader, JwtClaim, String),\n      clue: String\n  ) = {\n    assert(t.isSuccess, clue)\n    val (h1, c1, _) = t.get\n    val (h2, c2, _) = exp\n    assertEquals(h1, h2)\n    assertEquals(c1, c2)\n  }\n\n  def testTryAllWithoutSignature(\n      t: Try[(JwtHeader, JwtClaim, String)],\n      exp: (JwtHeader, JwtClaim),\n      clue: String\n  ) = {\n    assert(t.isSuccess, clue)\n    val (h1, c1, _) = t.get\n    val (h2, c2) = exp\n    assertEquals(h1, h2)\n    assertEquals(c1, c2)\n  }\n\n}\n"
  },
  {
    "path": "core/jvm/src/test/scala/JwtUtilsSpec.scala",
    "content": "package pdi.jwt\n\nimport java.security.spec.ECGenParameterSpec\nimport java.security.{KeyPairGenerator, SecureRandom}\n\nimport org.scalacheck.Gen\nimport org.scalacheck.Prop.*\nimport pdi.jwt.exceptions.JwtSignatureFormatException\n\ncase class TestObject(value: String) {\n  override def toString(): String = this.value\n}\n\nclass JwtUtilsSpec extends munit.ScalaCheckSuite with Fixture {\n  val ENCODING = JwtUtils.ENCODING\n\n  test(\"hashToJson should transform a seq of tuples to a valid JSON\") {\n    val values: Seq[(String, Seq[(String, Any)])] = Seq(\n      \"\"\"{\"a\":\"b\",\"c\":1,\"d\":true,\"e\":2,\"f\":3.4,\"g\":5.6}\"\"\" -> Seq(\n        \"a\" -> \"b\",\n        \"c\" -> 1,\n        \"d\" -> true,\n        \"e\" -> 2L,\n        \"f\" -> 3.4f,\n        \"g\" -> 5.6\n      ),\n      \"{}\" -> Seq(),\n      \"\"\"{\"a\\\"b\":\"a\\\"b\",\"c\\\"d\":\"c\\\"d\",\"e\\\"f\":[\"e\\\"f\",\"e\\\"f\"]}\"\"\" -> Seq(\n        \"\"\"a\"b\"\"\" -> \"\"\"a\"b\"\"\",\n        \"\"\"c\"d\"\"\" -> TestObject(\"\"\"c\"d\"\"\"),\n        \"\"\"e\"f\"\"\" -> Seq(\"\"\"e\"f\"\"\", TestObject(\"\"\"e\"f\"\"\"))\n      )\n    )\n\n    values.zipWithIndex.foreach { case (value, index) =>\n      assertEquals(value._1, JwtUtils.hashToJson(value._2), \"at index \" + index)\n    }\n  }\n\n  test(\"mergeJson should correctly merge 2 JSONs\") {\n    val values: Seq[(String, String, Seq[String])] = Seq(\n      (\"{}\", \"{}\", Seq(\"{}\")),\n      (\"\"\"{\"a\":1}\"\"\", \"\"\"{\"a\":1}\"\"\", Seq(\"\")),\n      (\"\"\"{\"a\":1}\"\"\", \"\"\"{\"a\":1}\"\"\", Seq(\"{}\")),\n      (\"\"\"{\"a\":1}\"\"\", \"\"\"{}\"\"\", Seq(\"\"\"{\"a\":1}\"\"\")),\n      (\"\"\"{\"a\":1}\"\"\", \"\", Seq(\"\"\"{\"a\":1}\"\"\")),\n      (\"\"\"{\"a\":1,\"b\":2}\"\"\", \"\"\"{\"a\":1}\"\"\", Seq(\"\"\"{\"b\":2}\"\"\")),\n      (\"\"\"{\"a\":1,\"b\":2,\"c\":\"d\"}\"\"\", \"\"\"{\"a\":1}\"\"\", Seq(\"\"\"{\"b\":2}\"\"\", \"\"\"{\"c\":\"d\"}\"\"\"))\n    )\n\n    values.zipWithIndex.foreach { case (value, index) =>\n      assertEquals(value._1, JwtUtils.mergeJson(value._2, value._3: _*), \"at index \" + index)\n    }\n  }\n\n  test(\"Claim.toJson should correctly encode a Claim to JSON\") {\n    val claim = JwtClaim(\n      issuer = Some(\"\"),\n      audience = Some(Set(\"\")),\n      subject = Some(\"da1b3852-6827-11e9-a923-1681be663d3e\"),\n      expiration = Some(1597914901),\n      issuedAt = Some(1566378901),\n      content = \"{\\\"a\\\":\\\"da1b3852-6827-11e9-a923-1681be663d3e\\\",\\\"b\\\":123.34}\"\n    )\n\n    val jsonClaim =\n      \"\"\"{\"iss\":\"\",\"sub\":\"da1b3852-6827-11e9-a923-1681be663d3e\",\"aud\":\"\",\"exp\":1597914901,\"iat\":1566378901,\"a\":\"da1b3852-6827-11e9-a923-1681be663d3e\",\"b\":123.34}\"\"\"\n\n    assertEquals(jsonClaim, claim.toJson)\n  }\n\n  test(\"transcodeSignatureToDER should throw JwtValidationException if signature is too long\") {\n    val signature = JwtUtils.bytify(\n      \"AU6-jw28DX1QMY0Ar8CTcnIAc0WKGe3nNVHkE7ayHSxvOLxE5YQSiZtbPn3y-vDHoQCOMId4rPdIJhD_NOUqnH_rAKA5w9ZlhtW0GwgpvOg1_5oLWnWXQvPjJjC5YsLqEssoMITtOmfkBsQMgLAF_LElaaCWhkJkOCtcZmroUW_b5CXB\"\n    )\n    interceptMessage[JwtSignatureFormatException](\"Invalid ECDSA signature format\") {\n      JwtUtils.transcodeSignatureToDER(signature ++ signature)\n    }\n  }\n\n  test(\"transcodeSignatureToDER should transocde empty signature\") {\n    val signature: Array[Byte] = Array[Byte](0)\n    JwtUtils.transcodeSignatureToDER(signature)\n  }\n\n  test(\"transcodeSignatureToConcat should throw JwtValidationException if length incorrect\") {\n    val signature = JwtUtils.bytify(\n      \"MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE\"\n    )\n    interceptMessage[JwtSignatureFormatException](\"Invalid ECDSA signature format\") {\n      JwtUtils.transcodeSignatureToConcat(signature, 132)\n    }\n  }\n\n  test(\n    \"transcodeSignatureToConcat should throw JwtValidationException if signature is incorrect \"\n  ) {\n    val signature = JwtUtils.bytify(\n      \"MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"\n    )\n    interceptMessage[JwtSignatureFormatException](\"Invalid ECDSA signature format\") {\n      JwtUtils.transcodeSignatureToConcat(signature, 132)\n    }\n  }\n\n  test(\n    \"transcodeSignatureToConcat should throw JwtValidationException if signature is incorrect 2\"\n  ) {\n    val signature = JwtUtils.bytify(\n      \"MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"\n    )\n    interceptMessage[JwtSignatureFormatException](\"Invalid ECDSA signature format\") {\n      JwtUtils.transcodeSignatureToConcat(signature, 132)\n    }\n  }\n\n  test(\"transcodeSignatureToConcat and transcodeSignatureToDER should be symmetric\") {\n    val signature = JwtUtils.bytify(\n      \"AbxLPbA3dm9V0jt6c_ahf8PYioFvnryTe3odgolhcgwBUl4ifpwUBJ--GgiXC8vms45c8vI40ZSdkm5NoNn1wTHOAfkepNy-RRKHmBzAoWrWmBIb76yPa0lsjdAPEAXcbGfaQV8pKq7W10dpB2B-KeJxVonMuCLJHPuqsUl9S7CfASu2\"\n    )\n    val dER: Array[Byte] = JwtUtils.transcodeSignatureToDER(signature)\n    val result = JwtUtils.transcodeSignatureToConcat(\n      dER,\n      JwtUtils.getSignatureByteArrayLength(JwtAlgorithm.ES512)\n    )\n    assertArrayEquals(signature, result)\n  }\n\n  test(\n    \"transcodeSignatureToConcat and transcodeSignatureToDER should be symmetric for generated tokens\"\n  ) {\n    val ecGenSpec = new ECGenParameterSpec(ecCurveName)\n    val generatorEC = KeyPairGenerator.getInstance(JwtUtils.ECDSA)\n    generatorEC.initialize(ecGenSpec, new SecureRandom())\n    val randomECKey = generatorEC.generateKeyPair()\n    val header = \"\"\"{\"typ\":\"JWT\",\"alg\":\"ES512\"}\"\"\"\n    val claim = \"\"\"{\"test\":\"t\"}\"\"\"\n\n    val signature = Jwt(validTimeClock)\n      .encode(header, claim, randomECKey.getPrivate, JwtAlgorithm.ES512)\n      .split(\"\\\\.\")(2)\n    assertEquals(\n      signature,\n      JwtUtils.stringify(\n        JwtUtils.transcodeSignatureToConcat(\n          JwtUtils.transcodeSignatureToDER(JwtUtils.bytify(signature)),\n          JwtUtils.getSignatureByteArrayLength(JwtAlgorithm.ES512)\n        )\n      )\n    )\n  }\n\n  test(\"splitString should do nothing\") {\n    forAll(Gen.asciiStr.suchThat(s => s.nonEmpty && !s.contains('a'))) { (value: String) =>\n      assertArrayEquals(\n        JwtUtils.splitString(value, 'a'),\n        Array(value)\n      )\n    }\n  }\n\n  test(\"splitString should split once\") {\n    assertArrayEquals(JwtUtils.splitString(\"qwertyAzxcvb\", 'A'), Array(\"qwerty\", \"zxcvb\"))\n  }\n\n  test(\"splitString should split a token\") {\n    assertArrayEquals(\n      JwtUtils.splitString(\"header.claim.signature\", '.'),\n      Array(\"header\", \"claim\", \"signature\")\n    )\n  }\n\n  test(\"splitString should split a token without signature\") {\n    assertArrayEquals(JwtUtils.splitString(\"header.claim\", '.'), Array(\"header\", \"claim\"))\n  }\n\n  test(\"splitString should split a token with an empty signature\") {\n    assertArrayEquals(JwtUtils.splitString(\"header.claim.\", '.'), Array(\"header\", \"claim\"))\n  }\n\n  test(\"splitString should split a token with an empty header\") {\n    assertArrayEquals(JwtUtils.splitString(\".claim.\", '.'), Array(\"\", \"claim\"))\n  }\n\n  test(\"splitString should be the same as normal split\") {\n    var token = \"header.claim.signature\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n    token = \"header.claim.\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n    token = \"header.claim\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n    token = \".claim.signature\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n    token = \".claim.\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n    token = \"1\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n    token = \"a.b.c.d\"\n    assertArrayEquals(token.split(\"\\\\.\"), JwtUtils.splitString(token, '.'))\n  }\n\n  private def assertArrayEquals[A](arr1: Array[A], arr2: Array[A]): Unit = {\n    assertEquals(arr1.toSeq, arr2.toSeq)\n  }\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/Jwt.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.util.matching.Regex\n\n/** Test implementation of [[JwtCore]] using only Strings. Most of the time, you should use a lib\n  * implementing JSON and shouldn't be using this object. But just in case you need pure Scala\n  * support, here it is.\n  *\n  * To see a full list of samples, check the\n  * [[https://jwt-scala.github.io/jwt-scala/jwt-core-jwt.html online documentation]].\n  *\n  * '''Warning''': since there is no JSON support in Scala, this object doesn't have any way to\n  * parse a JSON string as an AST, so it only uses regex with all the limitations it implies. Try\n  * not to use keys like `exp` and `nbf` in sub-objects of the claim. For example, if you try to use\n  * the following claim: `{\"user\":{\"exp\":1},\"exp\":1300819380}`, it should be correct but it will\n  * fail because the regex extracting the expiration will return `1` instead of `1300819380`. Sorry\n  * about that.\n  */\nobject Jwt extends Jwt(Clock.systemUTC) {\n  def apply(clock: Clock) = new Jwt(clock)\n}\n\nclass Jwt(override val clock: Clock) extends JwtCore[JwtHeader, JwtClaim] {\n  private val extractAlgorithmRegex = \"\\\"alg\\\" *: *\\\"([a-zA-Z0-9]+)\\\"\".r\n  protected def extractAlgorithm(header: String): Option[JwtAlgorithm] =\n    (extractAlgorithmRegex.findFirstMatchIn(header)).map(_.group(1)).flatMap {\n      case \"none\"       => None\n      case name: String => Some(JwtAlgorithm.fromString(name))\n    }\n\n  private val extractIssuerRegex = \"\\\"iss\\\" *: *\\\"([\\\\-a-zA-Z0-9_]*)\\\"\".r\n  protected def extractIssuer(claim: String): Option[String] =\n    (extractIssuerRegex.findFirstMatchIn(claim)).map(_.group(1))\n\n  private val extractSubjectRegex = \"\\\"sub\\\" *: *\\\"([\\\\-a-zA-Z0-9_]*)\\\"\".r\n  protected def extractSubject(claim: String): Option[String] =\n    (extractSubjectRegex.findFirstMatchIn(claim)).map(_.group(1))\n\n  private val extractExpirationRegex = \"\\\"exp\\\" *: *([0-9]+)\".r\n  protected def extractExpiration(claim: String): Option[Long] =\n    (extractExpirationRegex.findFirstMatchIn(claim)).map(_.group(1)).map(_.toLong)\n\n  private val extractNotBeforeRegex = \"\\\"nbf\\\" *: *([0-9]+)\".r\n  protected def extractNotBefore(claim: String): Option[Long] =\n    (extractNotBeforeRegex.findFirstMatchIn(claim)).map(_.group(1)).map(_.toLong)\n\n  private val extractIssuedAtRegex = \"\\\"iat\\\" *: *([0-9]+)\".r\n  protected def extractIssuedAt(claim: String): Option[Long] =\n    (extractIssuedAtRegex.findFirstMatchIn(claim)).map(_.group(1)).map(_.toLong)\n\n  private val extractJwtIdRegex = \"\\\"jti\\\" *: *\\\"([\\\\-a-zA-Z0-9_]*)\\\"\".r\n  protected def extractJwtId(claim: String): Option[String] =\n    (extractJwtIdRegex.findFirstMatchIn(claim)).map(_.group(1))\n\n  private val clearStartRegex = \"\\\\{ *,\".r\n  protected def clearStart(json: String): String =\n    clearStartRegex.replaceFirstIn(json, \"{\")\n\n  private val clearMiddleRegex = \", *(?=,)\".r\n  protected def clearMiddle(json: String): String =\n    clearMiddleRegex.replaceAllIn(json, \"\")\n\n  private val clearEndRegex = \", *\\\\}\".r\n  protected def clearEnd(json: String): String =\n    clearEndRegex.replaceFirstIn(json, \"}\")\n\n  protected def clearRegex(json: String, regex: Regex): String =\n    regex.replaceFirstIn(json, \"\")\n\n  protected def clearAll(json: String): String = {\n    val dirtyJson = List(\n      extractIssuerRegex,\n      extractSubjectRegex,\n      extractExpirationRegex,\n      extractNotBeforeRegex,\n      extractIssuedAtRegex,\n      extractJwtIdRegex\n    ).foldLeft(json)(clearRegex)\n\n    clearStart(clearEnd(clearMiddle(dirtyJson)))\n  }\n\n  protected def headerToJson(header: JwtHeader): String = header.toJson\n  protected def claimToJson(claim: JwtClaim): String = claim.toJson\n\n  protected def extractAlgorithm(header: JwtHeader): Option[JwtAlgorithm] = header.algorithm\n  protected def extractExpiration(claim: JwtClaim): Option[Long] = claim.expiration\n  protected def extractNotBefore(claim: JwtClaim): Option[Long] = claim.notBefore\n\n  protected def parseHeader(header: String): JwtHeader = JwtHeader(extractAlgorithm(header))\n\n  protected def parseClaim(claim: String): JwtClaim =\n    JwtClaim(\n      content = clearAll(claim),\n      issuer = extractIssuer(claim),\n      subject = extractSubject(claim),\n      expiration = extractExpiration(claim),\n      notBefore = extractNotBefore(claim),\n      issuedAt = extractIssuedAt(claim),\n      jwtId = extractJwtId(claim)\n    )\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtAlgorithm.scala",
    "content": "package pdi.jwt\n\nimport scala.annotation.nowarn\n\nimport pdi.jwt.algorithms.JwtUnknownAlgorithm\n\nsealed trait JwtAlgorithm {\n  def name: String\n  def fullName: String\n}\n\npackage algorithms {\n  sealed trait JwtAsymmetricAlgorithm extends JwtAlgorithm\n\n  sealed trait JwtHmacAlgorithm extends JwtAlgorithm\n  sealed trait JwtRSAAlgorithm extends JwtAsymmetricAlgorithm\n  sealed trait JwtECDSAAlgorithm extends JwtAsymmetricAlgorithm\n  sealed trait JwtEdDSAAlgorithm extends JwtAsymmetricAlgorithm\n  final case class JwtUnknownAlgorithm(name: String) extends JwtAlgorithm {\n    def fullName: String = name\n  }\n}\n\nobject JwtAlgorithm {\n\n  /** Deserialize an algorithm from its string equivalent. Only real algorithms supported, if you\n    * need to support \"none\", use \"optionFromString\".\n    *\n    * @return\n    *   the actual instance of the algorithm\n    * @param algo\n    *   the name of the algorithm (e.g. HS256 or HmacSHA256)\n    * @throws JwtNonSupportedAlgorithm\n    *   in case the string doesn't match any known algorithm\n    */\n  @nowarn(\"cat=deprecation\")\n  def fromString(algo: String): JwtAlgorithm = algo match {\n    case \"HMD5\"    => HMD5\n    case \"HS224\"   => HS224\n    case \"HS256\"   => HS256\n    case \"HS384\"   => HS384\n    case \"HS512\"   => HS512\n    case \"RS256\"   => RS256\n    case \"RS384\"   => RS384\n    case \"RS512\"   => RS512\n    case \"ES256\"   => ES256\n    case \"ES384\"   => ES384\n    case \"ES512\"   => ES512\n    case \"EdDSA\"   => EdDSA\n    case \"Ed25519\" => Ed25519\n    case other     => JwtUnknownAlgorithm(other)\n    // Missing PS256 PS384 PS512\n  }\n\n  /** Deserialize an algorithm from its string equivalent. If it's the special \"none\" algorithm,\n    * return None, else, return Some with the corresponding algorithm inside.\n    *\n    * @return\n    *   the actual instance of the algorithm\n    * @param algo\n    *   the name of the algorithm (e.g. none, HS256 or HmacSHA256)\n    */\n  def optionFromString(algo: String): Option[JwtAlgorithm] =\n    Option(algo).filterNot(_ == \"none\").map(fromString)\n\n  def allHmac(): Seq[algorithms.JwtHmacAlgorithm] = Seq(HMD5, HS224, HS256, HS384, HS512)\n\n  @nowarn(\"cat=deprecation\")\n  def allAsymmetric(): Seq[algorithms.JwtAsymmetricAlgorithm] =\n    Seq(RS256, RS384, RS512, ES256, ES384, ES512, EdDSA, Ed25519)\n\n  def allRSA(): Seq[algorithms.JwtRSAAlgorithm] = Seq(RS256, RS384, RS512)\n\n  def allECDSA(): Seq[algorithms.JwtECDSAAlgorithm] = Seq(ES256, ES384, ES512)\n\n  @nowarn(\"cat=deprecation\")\n  def allEdDSA(): Seq[algorithms.JwtEdDSAAlgorithm] = Seq(EdDSA, Ed25519)\n\n  case object HMD5 extends algorithms.JwtHmacAlgorithm {\n    def name = \"HMD5\"; def fullName = \"HmacMD5\"\n  }\n  case object HS224 extends algorithms.JwtHmacAlgorithm {\n    def name = \"HS224\"; def fullName = \"HmacSHA224\"\n  }\n  case object HS256 extends algorithms.JwtHmacAlgorithm {\n    def name = \"HS256\"; def fullName = \"HmacSHA256\"\n  }\n  case object HS384 extends algorithms.JwtHmacAlgorithm {\n    def name = \"HS384\"; def fullName = \"HmacSHA384\"\n  }\n  case object HS512 extends algorithms.JwtHmacAlgorithm {\n    def name = \"HS512\"; def fullName = \"HmacSHA512\"\n  }\n  case object RS256 extends algorithms.JwtRSAAlgorithm {\n    def name = \"RS256\"; def fullName = \"SHA256withRSA\"\n  }\n  case object RS384 extends algorithms.JwtRSAAlgorithm {\n    def name = \"RS384\"; def fullName = \"SHA384withRSA\"\n  }\n  case object RS512 extends algorithms.JwtRSAAlgorithm {\n    def name = \"RS512\"; def fullName = \"SHA512withRSA\"\n  }\n  case object ES256 extends algorithms.JwtECDSAAlgorithm {\n    def name = \"ES256\"; def fullName = \"SHA256withECDSA\"\n  }\n  case object ES384 extends algorithms.JwtECDSAAlgorithm {\n    def name = \"ES384\"; def fullName = \"SHA384withECDSA\"\n  }\n  case object ES512 extends algorithms.JwtECDSAAlgorithm {\n    def name = \"ES512\"; def fullName = \"SHA512withECDSA\"\n  }\n  case object EdDSA extends algorithms.JwtEdDSAAlgorithm {\n    def name = \"EdDSA\"; def fullName = \"EdDSA\"\n  }\n  @deprecated(\"Ed25519 is not a standard Json Web Algorithm name. Use EdDSA instead.\", \"9.3.0\")\n  case object Ed25519 extends algorithms.JwtEdDSAAlgorithm {\n    def name = \"Ed25519\"; def fullName = \"Ed25519\"\n  }\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtArrayUtils.scala",
    "content": "package pdi.jwt\n\nobject JwtArrayUtils {\n\n  /** A constant time equals comparison - does not terminate early if test will fail. For best\n    * results always pass the expected value as the first parameter.\n    *\n    * Ported from BouncyCastle to remove the need for a runtime dependency.\n    * https://github.com/bcgit/bc-java/blob/290df7b4edfc77b32d55d0a329bf15ef5b98733b/core/src/main/java/org/bouncycastle/util/Arrays.java#L136-L172\n    *\n    * @param expected\n    *   first array\n    * @param supplied\n    *   second array\n    * @return\n    *   true if arrays equal, false otherwise.\n    */\n  def constantTimeAreEqual(expected: Array[Byte], supplied: Array[Byte]): Boolean =\n    if (expected == supplied) true\n    else if (expected == null || supplied == null) false\n    else if (expected.length != supplied.length)\n      !JwtArrayUtils.constantTimeAreEqual(expected, expected)\n    else {\n      var nonEqual = 0\n      (0 until expected.length).foreach(i => nonEqual |= (expected(i) ^ supplied(i)))\n      nonEqual == 0\n    }\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtBase64.scala",
    "content": "package pdi.jwt\n\nobject JwtBase64 {\n  private lazy val encoder = java.util.Base64.getUrlEncoder()\n  private lazy val decoder = java.util.Base64.getUrlDecoder()\n  private lazy val decoderNonSafe = java.util.Base64.getDecoder()\n\n  def encode(value: Array[Byte]): Array[Byte] = encoder.encode(value)\n  def decode(value: Array[Byte]): Array[Byte] = decoder.decode(value)\n\n  def encode(value: String): Array[Byte] = encode(JwtUtils.bytify(value))\n  def decode(value: String): Array[Byte] = decoder.decode(value)\n\n  // Since the complement character \"=\" is optional,\n  // we can remove it to save some bits in the HTTP header\n  def encodeString(value: Array[Byte]): String = encoder.encodeToString(value).replaceAll(\"=\", \"\")\n  def decodeString(value: Array[Byte]): String = JwtUtils.stringify(decode(value))\n\n  def encodeString(value: String): String = encodeString(JwtUtils.bytify(value))\n  def decodeString(value: String): String = decodeString(JwtUtils.bytify(value))\n\n  def decodeNonSafe(value: Array[Byte]): Array[Byte] = decoderNonSafe.decode(value)\n  def decodeNonSafe(value: String): Array[Byte] = decoderNonSafe.decode(value)\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtClaim.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nobject JwtClaim {\n  def apply(\n      content: String = \"{}\",\n      issuer: Option[String] = None,\n      subject: Option[String] = None,\n      audience: Option[Set[String]] = None,\n      expiration: Option[Long] = None,\n      notBefore: Option[Long] = None,\n      issuedAt: Option[Long] = None,\n      jwtId: Option[String] = None\n  ) = new JwtClaim(content, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)\n}\n\nclass JwtClaim(\n    val content: String,\n    val issuer: Option[String],\n    val subject: Option[String],\n    val audience: Option[Set[String]],\n    val expiration: Option[Long],\n    val notBefore: Option[Long],\n    val issuedAt: Option[Long],\n    val jwtId: Option[String]\n) {\n\n  def toJson: String = JwtUtils.mergeJson(\n    JwtUtils.hashToJson(\n      Seq(\n        \"iss\" -> issuer,\n        \"sub\" -> subject,\n        \"aud\" -> audience.map(set => if (set.size == 1) set.head else set),\n        \"exp\" -> expiration,\n        \"nbf\" -> notBefore,\n        \"iat\" -> issuedAt,\n        \"jti\" -> jwtId\n      ).collect { case (key, Some(value)) =>\n        key -> value\n      }\n    ),\n    content\n  )\n\n  def +(json: String): JwtClaim = {\n    JwtClaim(\n      JwtUtils.mergeJson(this.content, json),\n      issuer,\n      subject,\n      audience,\n      expiration,\n      notBefore,\n      issuedAt,\n      jwtId\n    )\n  }\n\n  def +(key: String, value: Any): JwtClaim = {\n    JwtClaim(\n      JwtUtils.mergeJson(this.content, JwtUtils.hashToJson(Seq(key -> value))),\n      issuer,\n      subject,\n      audience,\n      expiration,\n      notBefore,\n      issuedAt,\n      jwtId\n    )\n  }\n\n  // Ok, it's Any, but just use \"primitive\" types\n  // It will not work with classes or case classes since, you know,\n  // there is no way to serialize them to JSON out of the box.\n  def ++[T <: Any](fields: (String, T)*): JwtClaim = {\n    JwtClaim(\n      JwtUtils.mergeJson(this.content, JwtUtils.hashToJson(fields)),\n      issuer,\n      subject,\n      audience,\n      expiration,\n      notBefore,\n      issuedAt,\n      jwtId\n    )\n  }\n\n  def by(issuer: String): JwtClaim = {\n    JwtClaim(content, Option(issuer), subject, audience, expiration, notBefore, issuedAt, jwtId)\n  }\n\n  // content should be a valid stringified JSON\n  def withContent(content: String): JwtClaim = {\n    JwtClaim(content, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)\n  }\n\n  def to(audience: String): JwtClaim = {\n    JwtClaim(\n      content,\n      issuer,\n      subject,\n      Option(Set(audience)),\n      expiration,\n      notBefore,\n      issuedAt,\n      jwtId\n    )\n  }\n\n  def to(audience: Set[String]): JwtClaim = {\n    JwtClaim(content, issuer, subject, Option(audience), expiration, notBefore, issuedAt, jwtId)\n  }\n\n  def about(subject: String): JwtClaim = {\n    JwtClaim(content, issuer, Option(subject), audience, expiration, notBefore, issuedAt, jwtId)\n  }\n\n  def withId(id: String): JwtClaim = {\n    JwtClaim(content, issuer, subject, audience, expiration, notBefore, issuedAt, Option(id))\n  }\n\n  def expiresAt(seconds: Long): JwtClaim =\n    JwtClaim(content, issuer, subject, audience, Option(seconds), notBefore, issuedAt, jwtId)\n\n  def expiresIn(seconds: Long)(implicit clock: Clock): JwtClaim = expiresAt(\n    JwtTime.nowSeconds + seconds\n  )\n\n  def expiresNow(implicit clock: Clock): JwtClaim = expiresAt(JwtTime.nowSeconds)\n\n  def startsAt(seconds: Long): JwtClaim =\n    JwtClaim(content, issuer, subject, audience, expiration, Option(seconds), issuedAt, jwtId)\n\n  def startsIn(seconds: Long)(implicit clock: Clock): JwtClaim = startsAt(\n    JwtTime.nowSeconds + seconds\n  )\n\n  def startsNow(implicit clock: Clock): JwtClaim = startsAt(JwtTime.nowSeconds)\n\n  def issuedAt(seconds: Long): JwtClaim =\n    JwtClaim(content, issuer, subject, audience, expiration, notBefore, Option(seconds), jwtId)\n\n  def issuedIn(seconds: Long)(implicit clock: Clock): JwtClaim = issuedAt(\n    JwtTime.nowSeconds + seconds\n  )\n\n  def issuedNow(implicit clock: Clock): JwtClaim = issuedAt(JwtTime.nowSeconds)\n\n  def isValid(issuer: String, audience: String)(implicit clock: Clock): Boolean =\n    this.audience.exists(_ contains audience) && this.isValid(issuer)\n\n  def isValid(issuer: String)(implicit clock: Clock): Boolean =\n    this.issuer.contains(issuer) && this.isValid\n\n  def isValid(implicit clock: Clock): Boolean =\n    JwtTime.nowIsBetweenSeconds(this.notBefore, this.expiration)\n\n  // equality code\n  def canEqual(other: Any): Boolean = other.isInstanceOf[JwtClaim]\n\n  override def equals(other: Any): Boolean = other match {\n    case that: JwtClaim =>\n      (that.canEqual(this)) &&\n      content == that.content &&\n      issuer == that.issuer &&\n      subject == that.subject &&\n      audience == that.audience &&\n      expiration == that.expiration &&\n      notBefore == that.notBefore &&\n      issuedAt == that.issuedAt &&\n      jwtId == that.jwtId\n    case _ => false\n  }\n\n  override def hashCode(): Int = {\n    val state = Seq(content, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)\n    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)\n  }\n\n  override def toString: String =\n    s\"JwtClaim($content, $issuer, $subject, $audience, $expiration, $notBefore, $issuedAt, $jwtId)\"\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtCore.scala",
    "content": "package pdi.jwt\n\nimport java.security.{Key, PrivateKey, PublicKey}\nimport java.time.Clock\nimport javax.crypto.SecretKey\nimport scala.util.Try\n\nimport pdi.jwt.algorithms.*\nimport pdi.jwt.exceptions.*\n\n/** Provide the main logic around Base64 encoding / decoding and signature using the correct\n  * algorithm. '''H''' and '''C''' types are respesctively the header type and the claim type. For\n  * the core project, they will be String but you are free to extend this trait using other types\n  * like JsObject or anything else.\n  *\n  * Please, check implementations, like [[Jwt]], for code samples.\n  *\n  * @tparam H\n  *   the type of the extracted header from a JSON Web Token\n  * @tparam C\n  *   the type of the extracted claim from a JSON Web Token\n  *\n  * @define token\n  *   a JSON Web Token as a Base64 url-safe encoded String which can be used inside an HTTP header\n  * @define headerString\n  *   a valid stringified JSON representing the header of the token\n  * @define claimString\n  *   a valid stringified JSON representing the claim of the token\n  * @define key\n  *   the key that will be used to check the token signature\n  * @define algo\n  *   the algorithm to sign the token\n  * @define algos\n  *   a list of possible algorithms that the token can use. See\n  *   [[https://jwt-scala.github.io/jwt-scala/#security-concerns Security concerns]] for more infos.\n  */\ntrait JwtCore[H, C] {\n  implicit private[jwt] def clock: Clock\n\n  // Abstract methods\n  protected def parseHeader(header: String): H\n  protected def parseClaim(claim: String): C\n\n  protected def extractAlgorithm(header: H): Option[JwtAlgorithm]\n  protected def extractExpiration(claim: C): Option[Long]\n  protected def extractNotBefore(claim: C): Option[Long]\n\n  def encode(header: String, claim: String): String = {\n    JwtBase64.encodeString(header) + \".\" + JwtBase64.encodeString(claim) + \".\"\n  }\n\n  /** Encode a JSON Web Token from its different parts. Both the header and the claim will be\n    * encoded to Base64 url-safe, then a signature will be eventually generated from it if you did\n    * pass a key and an algorithm, and finally, those three parts will be merged as a single string,\n    * using dots as separator.\n    *\n    * @return\n    *   $token\n    * @param header\n    *   $headerString\n    * @param claim\n    *   $claimString\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(header: String, claim: String, key: String, algorithm: JwtAlgorithm): String = {\n    val data = JwtBase64.encodeString(header) + \".\" + JwtBase64.encodeString(claim)\n    data + \".\" + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))\n  }\n\n  def encode(header: String, claim: String, key: SecretKey, algorithm: JwtHmacAlgorithm): String = {\n    val data = JwtBase64.encodeString(header) + \".\" + JwtBase64.encodeString(claim)\n    data + \".\" + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))\n  }\n\n  def encode(\n      header: String,\n      claim: String,\n      key: PrivateKey,\n      algorithm: JwtAsymmetricAlgorithm\n  ): String = {\n    val data = JwtBase64.encodeString(header) + \".\" + JwtBase64.encodeString(claim)\n    data + \".\" + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))\n  }\n\n  /** An alias to `encode` which will provide an automatically generated header.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   $claimString\n    */\n  def encode(claim: String): String = encode(JwtHeader().toJson, claim)\n\n  /** An alias to `encode` which will provide an automatically generated header and allowing you to\n    * get rid of Option for the key and the algorithm.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   $claimString\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(claim: String, key: String, algorithm: JwtAlgorithm): String =\n    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)\n\n  /** An alias to `encode` which will provide an automatically generated header and allowing you to\n    * get rid of Option for the key and the algorithm.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   $claimString\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(claim: String, key: SecretKey, algorithm: JwtHmacAlgorithm): String =\n    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)\n\n  /** An alias to `encode` which will provide an automatically generated header and allowing you to\n    * get rid of Option for the key and the algorithm.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   $claimString\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(claim: String, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =\n    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)\n\n  /** An alias to `encode` which will provide an automatically generated header and setting both key\n    * and algorithm to None.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   the claim of the JSON Web Token\n    */\n  def encode(claim: JwtClaim): String = encode(claim.toJson)\n\n  /** An alias to `encode` which will provide an automatically generated header and use the claim as\n    * a case class.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   the claim of the JSON Web Token\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(claim: JwtClaim, key: String, algorithm: JwtAlgorithm): String =\n    encode(claim.toJson, key, algorithm)\n\n  /** An alias to `encode` which will provide an automatically generated header and use the claim as\n    * a case class.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   the claim of the JSON Web Token\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(claim: JwtClaim, key: SecretKey, algorithm: JwtHmacAlgorithm): String =\n    encode(claim.toJson, key, algorithm)\n\n  /** An alias to `encode` which will provide an automatically generated header and use the claim as\n    * a case class.\n    *\n    * @return\n    *   $token\n    * @param claim\n    *   the claim of the JSON Web Token\n    * @param key\n    *   $key\n    * @param algorithm\n    *   $algo\n    */\n  def encode(claim: JwtClaim, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =\n    encode(claim.toJson, key, algorithm)\n\n  /** An alias to `encode` if you want to use case classes for the header and the claim rather than\n    * strings, they will just be stringified to JSON format.\n    *\n    * @return\n    *   $token\n    * @param header\n    *   the header to stringify as a JSON before encoding the token\n    * @param claim\n    *   the claim to stringify as a JSON before encoding the token\n    */\n  def encode(header: JwtHeader, claim: JwtClaim): String = header.algorithm match {\n    case None => encode(header.toJson, claim.toJson)\n    case _    => throw new JwtNonEmptyAlgorithmException()\n  }\n\n  /** An alias of `encode` if you only want to pass a string as the key, the algorithm will be\n    * deduced from the header.\n    *\n    * @return\n    *   $token\n    * @param header\n    *   the header to stringify as a JSON before encoding the token\n    * @param claim\n    *   the claim to stringify as a JSON before encoding the token\n    * @param key\n    *   the secret key to use to sign the token (note that the algorithm will be deduced from the\n    *   header)\n    */\n  def encode(header: JwtHeader, claim: JwtClaim, key: String): String = header.algorithm match {\n    case Some(algo: JwtAlgorithm) => encode(header.toJson, claim.toJson, key, algo)\n    case _                        => throw new JwtEmptyAlgorithmException()\n  }\n\n  /** An alias of `encode` if you only want to pass a string as the key, the algorithm will be\n    * deduced from the header.\n    *\n    * @return\n    *   $token\n    * @param header\n    *   the header to stringify as a JSON before encoding the token\n    * @param claim\n    *   the claim to stringify as a JSON before encoding the token\n    * @param key\n    *   the secret key to use to sign the token (note that the algorithm will be deduced from the\n    *   header)\n    */\n  def encode(header: JwtHeader, claim: JwtClaim, key: Key): String = (header.algorithm, key) match {\n    case (Some(algo: JwtHmacAlgorithm), k: SecretKey) =>\n      encode(header.toJson, claim.toJson, k, algo)\n    case (Some(algo: JwtAsymmetricAlgorithm), k: PrivateKey) =>\n      encode(header.toJson, claim.toJson, k, algo)\n    case _ =>\n      throw new JwtValidationException(\n        \"The key type doesn't match the algorithm type. It's either a SecretKey and a HMAC algorithm or a PrivateKey and a RSA or ECDSA algorithm. And an algorithm is required of course.\"\n      )\n  }\n\n  /** @return\n    *   a tuple of (header64, header, claim64, claim, signature or empty string if none)\n    * @throws JwtLengthException\n    *   if there is not 2 or 3 parts in the token\n    */\n  private def splitToken(token: String): (String, String, String, String, String) = {\n    val parts = JwtUtils.splitString(token, '.')\n\n    val signature = parts.length match {\n      case 2 => \"\"\n      case 3 => parts(2)\n      case _ =>\n        throw new JwtLengthException(\n          s\"Expected token [$token] to be composed of 2 or 3 parts separated by dots.\"\n        )\n    }\n\n    (\n      parts(0),\n      JwtBase64.decodeString(parts(0)),\n      parts(1),\n      JwtBase64.decodeString(parts(1)),\n      signature\n    )\n  }\n\n  /** Will try to decode a JSON Web Token to raw strings\n    *\n    * @return\n    *   if successful, a tuple of 3 strings, the header, the claim and the signature\n    * @param token\n    *   $token\n    */\n  def decodeRawAll(token: String, options: JwtOptions): Try[(String, String, String)] = Try {\n    val (_, header, _, claim, signature) = splitToken(token)\n    validate(parseHeader(header), parseClaim(claim), signature, options)\n    (header, claim, signature)\n  }\n\n  def decodeRawAll(token: String): Try[(String, String, String)] =\n    decodeRawAll(token, JwtOptions.DEFAULT)\n\n  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm\n    *\n    * @return\n    *   if successful, a tuple of 3 strings, the header, the claim and the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRawAll(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[(String, String, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n    (header, claim, signature)\n  }\n\n  def decodeRawAll(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Try[(String, String, String)] =\n    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Will try to decode a JSON Web Token to raw strings using an asymmetric algorithm\n    *\n    * @return\n    *   if successful, a tuple of 3 strings, the header, the claim and the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRawAll(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[(String, String, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n    (header, claim, signature)\n  }\n\n  def decodeRawAll(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm]\n  ): Try[(String, String, String)] =\n    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm\n    *\n    * @return\n    *   if successful, a tuple of 3 strings, the header, the claim and the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRawAll(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[(String, String, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n    (header, claim, signature)\n  }\n\n  def decodeRawAll(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Try[(String, String, String)] =\n    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeRawAll(\n      token: String,\n      key: SecretKey,\n      options: JwtOptions\n  ): Try[(String, String, String)] =\n    decodeRawAll(token, key, JwtAlgorithm.allHmac(), options)\n\n  def decodeRawAll(token: String, key: SecretKey): Try[(String, String, String)] =\n    decodeRawAll(token, key, JwtOptions.DEFAULT)\n\n  /** Will try to decode a JSON Web Token to raw strings using an asymmetric algorithm\n    *\n    * @return\n    *   if successful, a tuple of 3 strings, the header, the claim and the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRawAll(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[(String, String, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n    (header, claim, signature)\n  }\n\n  def decodeRawAll(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm]\n  ): Try[(String, String, String)] =\n    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeRawAll(\n      token: String,\n      key: PublicKey,\n      options: JwtOptions\n  ): Try[(String, String, String)] =\n    decodeRawAll(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def decodeRawAll(token: String, key: PublicKey): Try[(String, String, String)] =\n    decodeRawAll(token, key, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    */\n  def decodeRaw(token: String, options: JwtOptions): Try[String] =\n    decodeRawAll(token, options).map(_._2)\n\n  def decodeRaw(token: String): Try[String] = decodeRaw(token, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRaw(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[String] =\n    decodeRawAll(token, key, algorithms, options).map(_._2)\n\n  def decodeRaw(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[String] =\n    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRaw(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[String] =\n    decodeRawAll(token, key, algorithms, options).map(_._2)\n\n  def decodeRaw(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm]\n  ): Try[String] =\n    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRaw(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[String] =\n    decodeRawAll(token, key, algorithms, options).map(_._2)\n\n  def decodeRaw(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[String] =\n    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    */\n  def decodeRaw(token: String, key: SecretKey, options: JwtOptions): Try[String] =\n    decodeRaw(token, key, JwtAlgorithm.allHmac(), options)\n\n  def decodeRaw(token: String, key: SecretKey): Try[String] =\n    decodeRaw(token, key, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeRaw(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[String] =\n    decodeRawAll(token, key, algorithms, options).map(_._2)\n\n  def decodeRaw(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm]\n  ): Try[String] =\n    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the\n    * time)\n    *\n    * @return\n    *   if successful, a string representing the JSON version of the claim\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    */\n  def decodeRaw(token: String, key: PublicKey, options: JwtOptions): Try[String] =\n    decodeRaw(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def decodeRaw(token: String, key: PublicKey): Try[String] =\n    decodeRaw(token, key, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    */\n  def decodeAll(token: String, options: JwtOptions): Try[(H, C, String)] = Try {\n    val (_, header, _, claim, signature) = splitToken(token)\n    val (h, c) = (parseHeader(header), parseClaim(claim))\n    validate(h, c, signature, options)\n    (h, c, signature)\n  }\n\n  def decodeAll(token: String): Try[(H, C, String)] = decodeAll(token, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeAll(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[(H, C, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    val (h, c) = (parseHeader(header), parseClaim(claim))\n    validate(header64, h, claim64, c, signature, key, algorithms, options)\n    (h, c, signature)\n  }\n\n  def decodeAll(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Try[(H, C, String)] =\n    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeAll(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[(H, C, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    val (h, c) = (parseHeader(header), parseClaim(claim))\n    validate(header64, h, claim64, c, signature, key, algorithms, options)\n    (h, c, signature)\n  }\n\n  def decodeAll(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm]\n  ): Try[(H, C, String)] =\n    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeAll(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[(H, C, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    val (h, c) = (parseHeader(header), parseClaim(claim))\n    validate(header64, h, claim64, c, signature, key, algorithms, options)\n    (h, c, signature)\n  }\n\n  def decodeAll(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Try[(H, C, String)] =\n    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    */\n  def decodeAll(token: String, key: SecretKey, options: JwtOptions): Try[(H, C, String)] =\n    decodeAll(token, key, JwtAlgorithm.allHmac(), options)\n\n  def decodeAll(token: String, key: SecretKey): Try[(H, C, String)] =\n    decodeAll(token, key, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decodeAll(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[(H, C, String)] = Try {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    val (h, c) = (parseHeader(header), parseClaim(claim))\n    validate(header64, h, claim64, c, signature, key, algorithms, options)\n    (h, c, signature)\n  }\n\n  def decodeAll(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm]\n  ): Try[(H, C, String)] =\n    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeRawAll` but return the real header and claim types\n    *\n    * @return\n    *   if successful, a tuple representing the header, the claim and eventually the signature\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    */\n  def decodeAll(token: String, key: PublicKey, options: JwtOptions): Try[(H, C, String)] =\n    decodeAll(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def decodeAll(token: String, key: PublicKey): Try[(H, C, String)] =\n    decodeAll(token, key, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    */\n  def decode(token: String, options: JwtOptions): Try[C] = decodeAll(token, options).map(_._2)\n\n  def decode(token: String): Try[C] = decode(token, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decode(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[C] =\n    decodeAll(token, key, algorithms, options).map(_._2)\n\n  def decode(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[C] =\n    decode(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decode(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[C] =\n    decodeAll(token, key, algorithms, options).map(_._2)\n\n  def decode(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[C] =\n    decode(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decode(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[C] =\n    decodeAll(token, key, algorithms, options).map(_._2)\n\n  def decode(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[C] =\n    decode(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    */\n  def decode(token: String, key: SecretKey, options: JwtOptions): Try[C] =\n    decode(token, key, JwtAlgorithm.allHmac(), options)\n\n  def decode(token: String, key: SecretKey): Try[C] = decode(token, key, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def decode(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[C] =\n    decodeAll(token, key, algorithms, options).map(_._2)\n\n  def decode(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[C] =\n    decode(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** Same as `decodeAll` but only return the claim\n    *\n    * @return\n    *   if successful, the claim of the token in its correct type\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    */\n  def decode(token: String, key: PublicKey, options: JwtOptions): Try[C] =\n    decode(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def decode(token: String, key: PublicKey): Try[C] = decode(token, key, JwtOptions.DEFAULT)\n\n  // Validate\n  protected def validateTiming(claim: C, options: JwtOptions): Try[Unit] = {\n    val maybeExpiration: Option[Long] =\n      if (options.expiration) extractExpiration(claim) else None\n\n    val maybeNotBefore: Option[Long] =\n      if (options.notBefore) extractNotBefore(claim) else None\n\n    JwtTime.validateNowIsBetweenSeconds(\n      maybeNotBefore.map(_ - options.leeway),\n      maybeExpiration.map(_ + options.leeway)\n    )\n  }\n\n  // Validate if an algorithm is inside the authorized range\n  protected def validateHmacAlgorithm(\n      algorithm: JwtHmacAlgorithm,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Boolean = algorithms.contains(algorithm)\n\n  // Validate if an algorithm is inside the authorized range\n  protected def validateAsymmetricAlgorithm(\n      algorithm: JwtAsymmetricAlgorithm,\n      algorithms: Seq[JwtAsymmetricAlgorithm]\n  ): Boolean = algorithms.contains(algorithm)\n\n  // Validation when no key and no algorithm (or unknown)\n  protected def validate(header: H, claim: C, signature: String, options: JwtOptions) = {\n    if (options.signature) {\n      if (!signature.isEmpty) {\n        throw new JwtNonEmptySignatureException()\n      }\n\n      extractAlgorithm(header).foreach {\n        case JwtUnknownAlgorithm(name) => throw new JwtNonSupportedAlgorithm(name)\n        case _                         => throw new JwtNonEmptyAlgorithmException()\n      }\n    }\n\n    validateTiming(claim, options).get\n  }\n\n  // Validation when both key and algorithm\n  protected def validate(\n      header64: String,\n      header: H,\n      claim64: String,\n      claim: C,\n      signature: String,\n      options: JwtOptions,\n      verify: (Array[Byte], Array[Byte], JwtAlgorithm) => Boolean\n  ): Unit = {\n    if (options.signature) {\n      val maybeAlgo = extractAlgorithm(header)\n\n      if (options.signature && signature.isEmpty) {\n        throw new JwtEmptySignatureException()\n      } else if (maybeAlgo.isEmpty) {\n        throw new JwtEmptyAlgorithmException()\n      } else if (\n        !verify(\n          JwtUtils.bytify(header64 + \".\" + claim64),\n          JwtBase64.decode(signature),\n          maybeAlgo.get\n        )\n      ) {\n        throw new JwtValidationException(\"Invalid signature for this token or wrong algorithm.\")\n      }\n    }\n    validateTiming(claim, options).get\n  }\n\n  // Generic validation on String Key for HMAC algorithms\n  protected def validate(\n      header64: String,\n      header: H,\n      claim64: String,\n      claim: C,\n      signature: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Unit = validate(\n    header64,\n    header,\n    claim64,\n    claim,\n    signature,\n    options,\n    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>\n      algorithm match {\n        case algo: JwtHmacAlgorithm =>\n          validateHmacAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)\n        case _ => false\n      }\n  )\n\n  // Generic validation on String Key for asymmetric algorithms\n  protected def validate(\n      header64: String,\n      header: H,\n      claim64: String,\n      claim: C,\n      signature: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Unit = validate(\n    header64,\n    header,\n    claim64,\n    claim,\n    signature,\n    options,\n    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>\n      algorithm match {\n        case algo: JwtAsymmetricAlgorithm =>\n          validateAsymmetricAlgorithm(algo, algorithms) && JwtUtils.verify(\n            data,\n            signature,\n            key,\n            algo\n          )\n        case _ => false\n      }\n  )\n\n  // Validation for HMAC algorithm using a SecretKey\n  protected def validate(\n      header64: String,\n      header: H,\n      claim64: String,\n      claim: C,\n      signature: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Unit = validate(\n    header64,\n    header,\n    claim64,\n    claim,\n    signature,\n    options,\n    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>\n      algorithm match {\n        case algo: JwtHmacAlgorithm =>\n          validateHmacAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)\n        case _ => false\n      }\n  )\n\n  // Validation for RSA and ECDSA algorithms using PublicKey\n  protected def validate(\n      header64: String,\n      header: H,\n      claim64: String,\n      claim: C,\n      signature: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Unit = validate(\n    header64,\n    header,\n    claim64,\n    claim,\n    signature,\n    options,\n    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>\n      algorithm match {\n        case algo: JwtAsymmetricAlgorithm =>\n          validateAsymmetricAlgorithm(algo, algorithms) && JwtUtils.verify(\n            data,\n            signature,\n            key,\n            algo\n          )\n        case _ => false\n      }\n  )\n\n  /** Valid a token: doesn't return anything but will thrown exceptions if there are any errors.\n    *\n    * @param token\n    *   $token\n    * @throws JwtValidationException\n    *   default validation exception\n    * @throws JwtLengthException\n    *   the number of parts separated by dots is wrong\n    * @throws JwtNotBeforeException\n    *   the token isn't valid yet because its `notBefore` attribute is in the future\n    * @throws JwtExpirationException\n    *   the token isn't valid anymore because its `expiration` attribute is in the past\n    * @throws IllegalArgumentException\n    *   couldn't decode the token since it's not a valid base64 string\n    */\n  def validate(token: String, options: JwtOptions): Unit = {\n    val (_, header, _, claim, signature) = splitToken(token)\n    validate(parseHeader(header), parseClaim(claim), signature, options)\n  }\n\n  def validate(token: String): Unit = validate(token, JwtOptions.DEFAULT)\n\n  /** An alias of `validate` in case you want to directly pass a string key for HMAC algorithms.\n    *\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    * @throws JwtValidationException\n    *   default validation exception\n    * @throws JwtLengthException\n    *   the number of parts separated by dots is wrong\n    * @throws JwtNotBeforeException\n    *   the token isn't valid yet because its `notBefore` attribute is in the future\n    * @throws JwtExpirationException\n    *   the token isn't valid anymore because its `expiration` attribute is in the past\n    * @throws IllegalArgumentException\n    *   couldn't decode the token since it's not a valid base64 string\n    */\n  def validate(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Unit = {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n  }\n\n  def validate(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Unit =\n    validate(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** An alias of `validate` in case you want to directly pass a string key for asymmetric\n    * algorithms.\n    *\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    * @throws JwtValidationException\n    *   default validation exception\n    * @throws JwtLengthException\n    *   the number of parts separated by dots is wrong\n    * @throws JwtNotBeforeException\n    *   the token isn't valid yet because its `notBefore` attribute is in the future\n    * @throws JwtExpirationException\n    *   the token isn't valid anymore because its `expiration` attribute is in the past\n    * @throws IllegalArgumentException\n    *   couldn't decode the token since it's not a valid base64 string\n    */\n  def validate(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Unit = {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n  }\n\n  def validate(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Unit =\n    validate(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** An alias of `validate` in case you want to directly pass a string key.\n    *\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    * @throws JwtValidationException\n    *   default validation exception\n    * @throws JwtLengthException\n    *   the number of parts separated by dots is wrong\n    * @throws JwtNotBeforeException\n    *   the token isn't valid yet because its `notBefore` attribute is in the future\n    * @throws JwtExpirationException\n    *   the token isn't valid anymore because its `expiration` attribute is in the past\n    * @throws IllegalArgumentException\n    *   couldn't decode the token since it's not a valid base64 string\n    */\n  def validate(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Unit = {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n  }\n\n  def validate(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Unit =\n    validate(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def validate(token: String, key: SecretKey, options: JwtOptions): Unit =\n    validate(token, key, JwtAlgorithm.allHmac(), options)\n\n  def validate(token: String, key: SecretKey): Unit = validate(token, key, JwtOptions.DEFAULT)\n\n  /** An alias of `validate` in case you want to directly pass a string key.\n    *\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    * @throws JwtValidationException\n    *   default validation exception\n    * @throws JwtLengthException\n    *   the number of parts separated by dots is wrong\n    * @throws JwtNotBeforeException\n    *   the token isn't valid yet because its `notBefore` attribute is in the future\n    * @throws JwtExpirationException\n    *   the token isn't valid anymore because its `expiration` attribute is in the past\n    * @throws IllegalArgumentException\n    *   couldn't decode the token since it's not a valid base64 string\n    */\n  def validate(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Unit = {\n    val (header64, header, claim64, claim, signature) = splitToken(token)\n    validate(\n      header64,\n      parseHeader(header),\n      claim64,\n      parseClaim(claim),\n      signature,\n      key,\n      algorithms,\n      options\n    )\n  }\n\n  def validate(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Unit =\n    validate(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def validate(token: String, key: PublicKey, options: JwtOptions): Unit =\n    validate(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def validate(token: String, key: PublicKey): Unit = validate(token, key, JwtOptions.DEFAULT)\n\n  /** Test if a token is valid. Doesn't throw any exception.\n    *\n    * @return\n    *   a boolean value indicating if the token is valid or not\n    * @param token\n    *   $token\n    */\n  def isValid(token: String, options: JwtOptions): Boolean = Try(validate(token, options)).isSuccess\n\n  def isValid(token: String): Boolean = isValid(token, JwtOptions.DEFAULT)\n\n  /** An alias for `isValid` if you want to directly pass a string as the key for HMAC algorithms\n    *\n    * @return\n    *   a boolean value indicating if the token is valid or not\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def isValid(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess\n\n  def isValid(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Boolean =\n    isValid(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** An alias for `isValid` if you want to directly pass a string as the key for asymmetric\n    * algorithms\n    *\n    * @return\n    *   a boolean value indicating if the token is valid or not\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def isValid(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess\n\n  def isValid(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Boolean =\n    isValid(token, key, algorithms, JwtOptions.DEFAULT)\n\n  /** An alias for `isValid` if you want to directly pass a string as the key\n    *\n    * @return\n    *   a boolean value indicating if the token is valid or not\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def isValid(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess\n\n  def isValid(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Boolean =\n    isValid(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def isValid(token: String, key: SecretKey, options: JwtOptions): Boolean =\n    isValid(token, key, JwtAlgorithm.allHmac(), options)\n\n  def isValid(token: String, key: SecretKey): Boolean = isValid(token, key, JwtOptions.DEFAULT)\n\n  /** An alias for `isValid` if you want to directly pass a string as the key\n    *\n    * @return\n    *   a boolean value indicating if the token is valid or not\n    * @param token\n    *   $token\n    * @param key\n    *   $key\n    * @param algorithms\n    *   $algos\n    */\n  def isValid(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess\n\n  def isValid(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Boolean =\n    isValid(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def isValid(token: String, key: PublicKey, options: JwtOptions): Boolean =\n    isValid(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def isValid(token: String, key: PublicKey): Boolean = isValid(token, key, JwtOptions.DEFAULT)\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtException.scala",
    "content": "package pdi.jwt.exceptions\n\nimport pdi.jwt.JwtTime\n\nsealed abstract class JwtException(message: String) extends RuntimeException(message)\n\nclass JwtLengthException(message: String) extends JwtException(message)\n\nclass JwtValidationException(message: String) extends JwtException(message)\n\nclass JwtSignatureFormatException(message: String) extends JwtException(message)\n\nclass JwtEmptySignatureException()\n    extends JwtException(\n      \"No signature found inside the token while trying to verify it with a key.\"\n    )\n\nclass JwtNonEmptySignatureException()\n    extends JwtException(\n      \"Non-empty signature found inside the token while trying to verify without a key.\"\n    )\n\nclass JwtEmptyAlgorithmException()\n    extends JwtException(\n      \"No algorithm found inside the token header while having a key to sign or verify it.\"\n    )\n\nclass JwtNonEmptyAlgorithmException()\n    extends JwtException(\n      \"Algorithm found inside the token header while trying to sign or verify without a key.\"\n    )\n\nclass JwtExpirationException(expiration: Long)\n    extends JwtException(\"The token is expired since \" + JwtTime.format(expiration))\n\nclass JwtNotBeforeException(notBefore: Long)\n    extends JwtException(\"The token will only be valid after \" + JwtTime.format(notBefore))\n\nclass JwtNonSupportedAlgorithm(algo: String)\n    extends JwtException(s\"The algorithm [$algo] is not currently supported.\")\n\nclass JwtNonSupportedCurve(curve: String)\n    extends JwtException(s\"The curve [$curve] is not currently supported.\")\n\nclass JwtNonStringException(val key: String)\n    extends JwtException(s\"During JSON parsing, expected a String for key [$key]\") {\n  @deprecated(\"Use key instead\", since = \"9.0.1\")\n  def getKey = key\n}\n\nobject JwtNonStringException {\n  def unapply(e: JwtNonStringException) = Some(e.key)\n}\n\nclass JwtNonStringSetOrStringException(val key: String)\n    extends JwtException(s\"During JSON parsing, expected a Set[String] or String for key [$key]\") {\n  @deprecated(\"Use key instead\", since = \"9.0.1\")\n  def getKey = key\n}\n\nclass JwtNonNumberException(val key: String)\n    extends JwtException(s\"During JSON parsing, expected a Number for key [$key]\") {\n  @deprecated(\"Use key instead\", since = \"9.0.1\")\n  def getKey = key\n}\n\nobject JwtNonNumberException {\n  def unapply(e: JwtNonNumberException) = Some(e.key)\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtHeader.scala",
    "content": "package pdi.jwt\n\nobject JwtHeader {\n  val DEFAULT_TYPE = \"JWT\"\n\n  def apply(\n      algorithm: Option[JwtAlgorithm] = None,\n      typ: Option[String] = None,\n      contentType: Option[String] = None,\n      keyId: Option[String] = None\n  ) = new JwtHeader(algorithm, typ, contentType, keyId)\n\n  def apply(algorithm: Option[JwtAlgorithm]): JwtHeader = algorithm match {\n    case Some(algo) => JwtHeader(algo)\n    case _          => new JwtHeader(None, None, None, None)\n  }\n\n  def apply(algorithm: JwtAlgorithm): JwtHeader =\n    new JwtHeader(Option(algorithm), Option(DEFAULT_TYPE), None, None)\n\n  def apply(algorithm: JwtAlgorithm, typ: String): JwtHeader =\n    new JwtHeader(Option(algorithm), Option(typ), None, None)\n\n  def apply(algorithm: JwtAlgorithm, typ: String, contentType: String): JwtHeader =\n    new JwtHeader(Option(algorithm), Option(typ), Option(contentType), None)\n\n  def apply(algorithm: JwtAlgorithm, typ: String, contentType: String, keyId: String): JwtHeader =\n    new JwtHeader(Option(algorithm), Option(typ), Option(contentType), Option(keyId))\n}\n\nclass JwtHeader(\n    val algorithm: Option[JwtAlgorithm],\n    val typ: Option[String],\n    val contentType: Option[String],\n    val keyId: Option[String]\n) {\n  def toJson: String = JwtUtils.hashToJson(\n    Seq(\n      \"typ\" -> typ,\n      \"alg\" -> algorithm.map(_.name).orElse(Option(\"none\")),\n      \"cty\" -> contentType,\n      \"kid\" -> keyId\n    ).collect { case (key, Some(value)) =>\n      (key -> value)\n    }\n  )\n\n  /** Assign the type to the header */\n  def withType(typ: String): JwtHeader = {\n    JwtHeader(algorithm, Option(typ), contentType, keyId)\n  }\n\n  /** Assign the default type `JWT` to the header */\n  def withType: JwtHeader = this.withType(JwtHeader.DEFAULT_TYPE)\n\n  /** Assign a key id to the header */\n  def withKeyId(keyId: String): JwtHeader = {\n    JwtHeader(algorithm, typ, contentType, Option(keyId))\n  }\n\n  // equality code\n  def canEqual(other: Any): Boolean = other.isInstanceOf[JwtHeader]\n\n  override def equals(other: Any): Boolean = other match {\n    case that: JwtHeader =>\n      (that.canEqual(this)) &&\n      algorithm == that.algorithm &&\n      typ == that.typ &&\n      contentType == that.contentType &&\n      keyId == that.keyId\n    case _ => false\n  }\n\n  override def hashCode(): Int = {\n    val state = Seq(algorithm, typ, contentType, keyId)\n    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)\n  }\n\n  override def toString: String = s\"JwtHeader($algorithm, $typ, $contentType, $keyId)\"\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtOptions.scala",
    "content": "package pdi.jwt\n\ncase class JwtOptions(\n    signature: Boolean = true,\n    expiration: Boolean = true,\n    notBefore: Boolean = true,\n    leeway: Long = 0 // in seconds\n)\n\nobject JwtOptions {\n  val DEFAULT = new JwtOptions()\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtTime.scala",
    "content": "package pdi.jwt\n\nimport java.time.{Clock, Instant}\nimport scala.util.{Failure, Success, Try}\n\nimport pdi.jwt.exceptions.{JwtExpirationException, JwtNotBeforeException}\n\n/** Util object to handle time operations */\nobject JwtTime {\n\n  /** Returns the number of millis since the 01.01.1970\n    *\n    * @return\n    *   the number of millis since the 01.01.1970\n    */\n  def now(implicit clock: Clock): Long = clock.instant().toEpochMilli\n\n  /** Returns the number of seconds since the 01.01.1970\n    *\n    * @return\n    *   the number of seconds since the 01.01.1970\n    */\n  def nowSeconds(implicit clock: Clock): Long = this.now / 1000\n\n  def format(time: Long): String = Instant.ofEpochMilli(time).toString\n\n  /** Test if the current time is between the two prams\n    *\n    * @return\n    *   the result of the test\n    * @param start\n    *   if set, the instant that must be before now (in millis)\n    * @param end\n    *   if set, the instant that must be after now (in millis)\n    */\n  def nowIsBetween(start: Option[Long], end: Option[Long])(implicit clock: Clock): Boolean =\n    validateNowIsBetween(start, end).isSuccess\n\n  /** Same as `nowIsBetween` but using seconds rather than millis.\n    *\n    * @param start\n    *   if set, the instant that must be before now (in seconds)\n    * @param end\n    *   if set, the instant that must be after now (in seconds)\n    */\n  def nowIsBetweenSeconds(start: Option[Long], end: Option[Long])(implicit clock: Clock): Boolean =\n    nowIsBetween(start.map(_ * 1000), end.map(_ * 1000))\n\n  /** Test if the current time is between the two params and throw an exception if we don't have\n    * `start` <= now < `end`\n    *\n    * @param start\n    *   if set, the instant that must be before now (in millis)\n    * @param end\n    *   if set, the instant that must be after now (in millis)\n    * @return\n    *   Failure(JwtNotBeforeException) if `start` > now, Failure(JwtExpirationException) if now >=\n    *   `end`\n    */\n  def validateNowIsBetween(start: Option[Long], end: Option[Long])(implicit\n      clock: Clock\n  ): Try[Unit] = {\n    val timeNow = now\n\n    (start, end) match {\n      case (Some(s), _) if s > timeNow  => Failure(new JwtNotBeforeException(s))\n      case (_, Some(e)) if e <= timeNow => Failure(new JwtExpirationException(e))\n      case _                            => Success(())\n    }\n  }\n\n  /** Same as `validateNowIsBetween` but using seconds rather than millis.\n    *\n    * @param start\n    *   if set, the instant that must be before now (in seconds)\n    * @param end\n    *   if set, the instant that must be after now (in seconds)\n    * @return\n    *   Failure(JwtNotBeforeException) if `start` > now, Failure(JwtExpirationException) if now >\n    *   `end`\n    */\n  def validateNowIsBetweenSeconds(start: Option[Long], end: Option[Long])(implicit\n      clock: Clock\n  ): Try[Unit] =\n    validateNowIsBetween(start.map(_ * 1000), end.map(_ * 1000))\n}\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtUtils.scala",
    "content": "package pdi.jwt\n\nimport java.security.spec.{PKCS8EncodedKeySpec, X509EncodedKeySpec}\nimport java.security.{KeyFactory, PrivateKey, PublicKey, Signature}\nimport javax.crypto.spec.SecretKeySpec\nimport javax.crypto.{Mac, SecretKey}\n\nimport pdi.jwt.JwtAlgorithm.{ES256, ES384, ES512}\nimport pdi.jwt.algorithms.*\nimport pdi.jwt.exceptions.{JwtNonSupportedAlgorithm, JwtSignatureFormatException}\n\nobject JwtUtils {\n  val ENCODING = \"UTF-8\"\n  val RSA = \"RSA\"\n  val ECDSA = \"EC\"\n  val EdDSA = \"EdDSA\"\n\n  /** Convert an array of bytes to its corresponding string using the default encoding.\n    *\n    * @return\n    *   the final string\n    * @param arr\n    *   the array of bytes to transform\n    */\n  def stringify(arr: Array[Byte]): String = new String(arr, ENCODING)\n\n  /** Convert a string to its corresponding array of bytes using the default encoding.\n    *\n    * @return\n    *   the final array of bytes\n    * @param str\n    *   the string to convert\n    */\n  def bytify(str: String): Array[Byte] = str.getBytes(ENCODING)\n\n  private def escape(value: String): String = value.replaceAll(\"\\\"\", \"\\\\\\\\\\\"\")\n\n  /** Convert a sequence to a JSON array\n    */\n  def seqToJson(seq: Seq[Any]): String = seq\n    .map {\n      case value: String        => \"\\\"\" + escape(value) + \"\\\"\"\n      case value: Boolean       => if (value) \"true\" else \"false\"\n      case value: Double        => value.toString\n      case value: Short         => value.toString\n      case value: Float         => value.toString\n      case value: Long          => value.toString\n      case value: Int           => value.toString\n      case value: BigDecimal    => value.toString\n      case value: BigInt        => value.toString\n      case (key: String, value) => hashToJson(Seq(key -> value))\n      case value: Any           => \"\\\"\" + escape(value.toString) + \"\\\"\"\n    }\n    .mkString(\"[\", \",\", \"]\")\n\n  /** Convert a sequence of tuples to a JSON object\n    */\n  def hashToJson(hash: Seq[(String, Any)]): String = hash\n    .map {\n      case (key, value: String)     => \"\\\"\" + escape(key) + \"\\\":\\\"\" + escape(value) + \"\\\"\"\n      case (key, value: Boolean)    => \"\\\"\" + escape(key) + \"\\\":\" + (if (value) \"true\" else \"false\")\n      case (key, value: Double)     => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, value: Short)      => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, value: Float)      => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, value: Long)       => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, value: Int)        => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, value: BigDecimal) => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, value: BigInt)     => \"\\\"\" + escape(key) + \"\\\":\" + value.toString\n      case (key, (vKey: String, vValue)) =>\n        \"\\\"\" + escape(key) + \"\\\":\" + hashToJson(Seq(vKey -> vValue))\n      case (key, value: Seq[Any]) => \"\\\"\" + escape(key) + \"\\\":\" + seqToJson(value)\n      case (key, value: Set[_])   => \"\\\"\" + escape(key) + \"\\\":\" + seqToJson(value.toSeq)\n      case (key, value: Any)      => \"\\\"\" + escape(key) + \"\\\":\\\"\" + escape(value.toString) + \"\\\"\"\n    }\n    .mkString(\"{\", \",\", \"}\")\n\n  /** Merge multiple JSON strings to a unique one\n    */\n  def mergeJson(json: String, jsonSeq: String*): String = {\n    val initJson = json.trim match {\n      case \"\"    => \"\"\n      case value => value.drop(1).dropRight(1)\n    }\n\n    \"{\" + jsonSeq.map(_.trim).fold(initJson) {\n      case (j1, result) if j1.length < 5 => result.drop(1).dropRight(1)\n      case (result, j2) if j2.length < 7 => result\n      case (j1, j2)                      => j1 + \",\" + j2.drop(1).dropRight(1)\n    } + \"}\"\n  }\n\n  private def parseKey(key: String): Array[Byte] = JwtBase64.decodeNonSafe(\n    key.replaceAll(\"-----BEGIN ([^-]*)-----|-----END ([^-]*)-----|\\\\s*\", \"\")\n  )\n\n  private def parsePrivateKey(key: String, keyAlgo: String) = {\n    val spec = new PKCS8EncodedKeySpec(parseKey(key))\n    KeyFactory.getInstance(keyAlgo).generatePrivate(spec)\n  }\n\n  private def parsePublicKey(key: String, keyAlgo: String): PublicKey = {\n    val spec = new X509EncodedKeySpec(parseKey(key))\n    KeyFactory.getInstance(keyAlgo).generatePublic(spec)\n  }\n\n  /** Generate the signature for a given data using the key and HMAC algorithm provided.\n    */\n  def sign(data: Array[Byte], key: SecretKey, algorithm: JwtHmacAlgorithm): Array[Byte] = {\n    val mac = Mac.getInstance(algorithm.fullName)\n    mac.init(key)\n    mac.doFinal(data)\n  }\n\n  def sign(data: String, key: SecretKey, algorithm: JwtHmacAlgorithm): Array[Byte] =\n    sign(bytify(data), key, algorithm)\n\n  /** Generate the signature for a given data using the key and RSA or ECDSA algorithm provided.\n    */\n  def sign(data: Array[Byte], key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): Array[Byte] = {\n    val signer = Signature.getInstance(algorithm.fullName)\n    signer.initSign(key)\n    signer.update(data)\n    algorithm match {\n      case _: JwtRSAAlgorithm           => signer.sign\n      case algorithm: JwtECDSAAlgorithm =>\n        transcodeSignatureToConcat(signer.sign, getSignatureByteArrayLength(algorithm))\n      case _: JwtEdDSAAlgorithm => signer.sign\n    }\n  }\n\n  def sign(data: String, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): Array[Byte] =\n    sign(bytify(data), key, algorithm)\n\n  /** Will try to sign some given data by parsing the provided key, if parsing fail, please consider\n    * retrieving the SecretKey or the PrivateKey on your side and then use another \"sign\" method.\n    */\n  def sign(data: Array[Byte], key: String, algorithm: JwtAlgorithm): Array[Byte] =\n    algorithm match {\n      case algo: JwtHmacAlgorithm => sign(data, new SecretKeySpec(bytify(key), algo.fullName), algo)\n      case algo: JwtRSAAlgorithm  => sign(data, parsePrivateKey(key, RSA), algo)\n      case algo: JwtECDSAAlgorithm   => sign(data, parsePrivateKey(key, ECDSA), algo)\n      case algo: JwtEdDSAAlgorithm   => sign(data, parsePrivateKey(key, EdDSA), algo)\n      case algo: JwtUnknownAlgorithm => throw new JwtNonSupportedAlgorithm(algo.fullName)\n    }\n\n  /** Alias to `sign` using a String data which will be converted to an array of bytes.\n    */\n  def sign(data: String, key: String, algorithm: JwtAlgorithm): Array[Byte] =\n    sign(bytify(data), key, algorithm)\n\n  /** Check if a signature is valid for a given data using the key and the HMAC algorithm provided.\n    */\n  def verify(\n      data: Array[Byte],\n      signature: Array[Byte],\n      key: SecretKey,\n      algorithm: JwtHmacAlgorithm\n  ): Boolean = {\n    JwtArrayUtils.constantTimeAreEqual(sign(data, key, algorithm), signature)\n  }\n\n  /** Check if a signature is valid for a given data using the key and the RSA or ECDSA algorithm\n    * provided.\n    */\n  def verify(\n      data: Array[Byte],\n      signature: Array[Byte],\n      key: PublicKey,\n      algorithm: JwtAsymmetricAlgorithm\n  ): Boolean = {\n    val signer = Signature.getInstance(algorithm.fullName)\n    signer.initVerify(key)\n    signer.update(data)\n    algorithm match {\n      case _: JwtRSAAlgorithm   => signer.verify(signature)\n      case _: JwtECDSAAlgorithm => signer.verify(transcodeSignatureToDER(signature))\n      case _: JwtEdDSAAlgorithm => signer.verify(signature)\n    }\n  }\n\n  /** Will try to check if a signature is valid for a given data by parsing the provided key, if\n    * parsing fail, please consider retrieving the SecretKey or the PublicKey on your side and then\n    * use another \"verify\" method.\n    */\n  def verify(\n      data: Array[Byte],\n      signature: Array[Byte],\n      key: String,\n      algorithm: JwtAlgorithm\n  ): Boolean = algorithm match {\n    case algo: JwtHmacAlgorithm =>\n      verify(data, signature, new SecretKeySpec(bytify(key), algo.fullName), algo)\n    case algo: JwtRSAAlgorithm     => verify(data, signature, parsePublicKey(key, RSA), algo)\n    case algo: JwtECDSAAlgorithm   => verify(data, signature, parsePublicKey(key, ECDSA), algo)\n    case algo: JwtEdDSAAlgorithm   => verify(data, signature, parsePublicKey(key, EdDSA), algo)\n    case algo: JwtUnknownAlgorithm => throw new JwtNonSupportedAlgorithm(algo.fullName)\n  }\n\n  /** Alias for `verify`\n    */\n  def verify(data: String, signature: String, key: String, algorithm: JwtAlgorithm): Boolean =\n    verify(bytify(data), bytify(signature), key, algorithm)\n\n  /** Returns the expected signature byte array length (R + S parts) for the specified ECDSA\n    * algorithm.\n    *\n    * @param algorithm\n    *   The ECDSA algorithm. Must be supported and not {@code null} .\n    * @return\n    *   The expected byte array length for the signature.\n    */\n  def getSignatureByteArrayLength(algorithm: JwtECDSAAlgorithm): Int = algorithm match {\n    case ES256 => 64\n    case ES384 => 96\n    case ES512 => 132\n  }\n\n  /** Transcodes the JCA ASN.1/DER-encoded signature into the concatenated R + S format expected by\n    * ECDSA JWS.\n    *\n    * @param derSignature\n    *   The ASN1./DER-encoded. Must not be {@code null} .\n    * @param outputLength\n    *   The expected length of the ECDSA JWS signature.\n    * @return\n    *   The ECDSA JWS encoded signature.\n    * @throws JwtSignatureFormatException\n    *   If the ASN.1/DER signature format is invalid.\n    */\n  @throws[JwtSignatureFormatException]\n  def transcodeSignatureToConcat(derSignature: Array[Byte], outputLength: Int): Array[Byte] = {\n    if (derSignature.length < 8 || derSignature(0) != 48)\n      throw new JwtSignatureFormatException(\"Invalid ECDSA signature format\")\n\n    val offset: Int = derSignature(1) match {\n      case s if s > 0            => 2\n      case s if s == 0x81.toByte => 3\n      case _ => throw new JwtSignatureFormatException(\"Invalid ECDSA signature format\")\n    }\n\n    val rLength: Byte = derSignature(offset + 1)\n    var i = rLength.toInt\n    while ((i > 0) && (derSignature((offset + 2 + rLength) - i) == 0)) {\n      i -= 1\n    }\n\n    val sLength: Byte = derSignature(offset + 2 + rLength + 1)\n    var j = sLength.toInt\n    while ((j > 0) && (derSignature((offset + 2 + rLength + 2 + sLength) - j) == 0)) {\n      j -= 1\n    }\n\n    val rawLen: Int = Math.max(Math.max(i, j), outputLength / 2)\n\n    if (\n      (derSignature(offset - 1) & 0xff) != derSignature.length - offset\n      || (derSignature(offset - 1) & 0xff) != 2 + rLength + 2 + sLength\n      || derSignature(offset) != 2 || derSignature(offset + 2 + rLength) != 2\n    )\n      throw new JwtSignatureFormatException(\"Invalid ECDSA signature format\")\n\n    val concatSignature: Array[Byte] = new Array[Byte](2 * rawLen)\n    System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i)\n    System.arraycopy(\n      derSignature,\n      (offset + 2 + rLength + 2 + sLength) - j,\n      concatSignature,\n      2 * rawLen - j,\n      j\n    )\n    concatSignature\n  }\n\n  /** Transcodes the ECDSA JWS signature into ASN.1/DER format for use by the JCA verifier.\n    *\n    * @param signature\n    *   The JWS signature, consisting of the concatenated R and S values. Must not be {@code null} .\n    * @return\n    *   The ASN.1/DER encoded signature.\n    * @throws JwtSignatureFormatException\n    *   If the ECDSA JWS signature format is invalid.\n    */\n  @throws[JwtSignatureFormatException]\n  def transcodeSignatureToDER(signature: Array[Byte]): Array[Byte] = {\n    var (r, s) = signature.splitAt(signature.length / 2)\n    r = r.dropWhile(_ == 0)\n    if (r.length > 0 && r(0) < 0)\n      r +:= 0.toByte\n\n    s = s.dropWhile(_ == 0)\n    if (s.length > 0 && s(0) < 0)\n      s +:= 0.toByte\n\n    val signatureLength = 2 + r.length + 2 + s.length\n\n    if (signatureLength > 255)\n      throw new JwtSignatureFormatException(\"Invalid ECDSA signature format\")\n\n    val signatureDER = scala.collection.mutable.ListBuffer.empty[Byte]\n    signatureDER += 48\n    if (signatureLength >= 128)\n      signatureDER += 0x81.toByte\n\n    signatureDER += signatureLength.toByte\n    signatureDER ++= (Seq(2.toByte, r.length.toByte) ++ r)\n    signatureDER ++= (Seq(2.toByte, s.length.toByte) ++ s)\n\n    signatureDER.toArray\n  }\n\n  def splitString(input: String, separator: Char): Array[String] = {\n    val builder = scala.collection.mutable.ArrayBuffer.empty[String]\n    var lastIndex = 0\n    var index = input.indexOf(separator.toInt, lastIndex)\n    while (index != -1) {\n      builder += input.substring(lastIndex, index)\n      lastIndex = index + 1\n      index = input.indexOf(separator.toInt, lastIndex)\n    }\n    // Add the remainder\n    if (lastIndex < input.length) {\n      builder += input.substring(lastIndex, input.length)\n    }\n    builder.toArray\n  }\n\n}\n"
  },
  {
    "path": "core/shared/src/test/scala/Fixture.scala",
    "content": "package pdi.jwt\n\nimport java.security.spec.*\nimport java.security.{KeyFactory, KeyPairGenerator, SecureRandom, Security}\nimport java.time.{Clock, Instant, ZoneOffset}\nimport javax.crypto.spec.SecretKeySpec\n\nimport org.bouncycastle.jce.ECNamedCurveTable\nimport org.bouncycastle.jce.provider.BouncyCastleProvider\nimport org.bouncycastle.jce.spec.ECNamedCurveSpec\nimport pdi.jwt.algorithms.{JwtECDSAAlgorithm, JwtHmacAlgorithm, JwtRSAAlgorithm}\n\ntrait DataEntryBase {\n  def algo: JwtAlgorithm\n  def header: String\n  def headerClass: JwtHeader\n  def header64: String\n  def signature: String\n  def token: String\n  def tokenUnsigned: String\n  def tokenEmpty: String\n}\n\ncase class DataEntry[A <: JwtAlgorithm](\n    algo: A,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String = \"\",\n    tokenUnsigned: String = \"\",\n    tokenEmpty: String = \"\"\n) extends DataEntryBase\n\ntrait ClockFixture {\n  val expiration: Long = 1300819380\n  val expirationMillis: Long = expiration * 1000\n  val beforeExpirationMillis: Long = expirationMillis - 1\n  val afterExpirationMillis: Long = expirationMillis + 1\n\n  def fixedUTC(millis: Long): Clock = Clock.fixed(Instant.ofEpochMilli(millis), ZoneOffset.UTC)\n\n  val afterExpirationClock: Clock = fixedUTC(afterExpirationMillis)\n\n  val notBefore: Long = 1300819320\n  val notBeforeMillis: Long = notBefore * 1000\n  val beforeNotBeforeMillis: Long = notBeforeMillis - 1\n  val afterNotBeforeMillis: Long = notBeforeMillis + 1\n\n  val beforeNotBeforeClock: Clock = fixedUTC(beforeNotBeforeMillis)\n  val afterNotBeforeClock: Clock = fixedUTC(afterNotBeforeMillis)\n\n  val validTime: Long = (expiration + notBefore) / 2\n  val validTimeMillis: Long = validTime * 1000\n  val validTimeClock: Clock = fixedUTC(validTimeMillis)\n\n  val ecCurveName = \"secp521r1\"\n}\n\ntrait Fixture extends ClockFixture {\n\n  // Bouncycastle is not included by default. Add it for each test.\n  if (Security.getProvider(\"BC\") == null) {\n    Security.addProvider(new BouncyCastleProvider())\n    ()\n  }\n\n  val Ed25519 = \"Ed25519\"\n\n  val secretKey =\n    \"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"\n  val secretKeyBytes = JwtUtils.bytify(secretKey)\n  def secretKeyOf(algo: JwtAlgorithm) = new SecretKeySpec(secretKeyBytes, algo.fullName)\n\n  val claim = s\"\"\"{\"iss\":\"joe\",\"exp\":$expiration,\"http://example.com/is_root\":true}\"\"\"\n  val claimClass = JwtClaim(\n    \"\"\"{\"http://example.com/is_root\":true}\"\"\",\n    issuer = Option(\"joe\"),\n    expiration = Option(expiration)\n  )\n  val claim64 =\n    \"eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ\"\n\n  val headerEmpty = \"\"\"{\"alg\":\"none\"}\"\"\"\n  val headerClassEmpty = JwtHeader()\n  val header64Empty = \"eyJhbGciOiJub25lIn0\"\n\n  val tokenEmpty = header64Empty + \".\" + claim64 + \".\"\n\n  val headerWithSpaces = \"\"\"{\"alg\"  :   \"none\"}\"\"\"\n  val claimWithSpaces = \"\"\"{\"nbf\"  :0  , \"foo\"  : \"bar\"  , \"exp\":    32086368000}\"\"\"\n  val tokenWithSpaces =\n    JwtBase64.encodeString(headerWithSpaces) + \".\" + JwtBase64.encodeString(claimWithSpaces) + \".\"\n\n  val publicKeyRSA = \"\"\"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzoCEC2rpSpJQaWZbUml\nsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHu\nvyrVfwY0dINk+nkqB74QcT2oCCH9XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/\nW7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN\n40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQ\nCwyzee7bOJqXUDAuLcFARzPw1EsZAyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpp\ntQIDAQAB\n-----END PUBLIC KEY-----\"\"\"\n\n  val privateKeyRSA = \"\"\"-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ\n7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9\nXduJjDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qs\nu3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8N\niK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZ\nAyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQABAoIBAQCsssO4Pra8hFMC\ngX7tr0x+tAYy1ewmpW8stiDFilYT33YPLKJ9HjHbSms0MwqHftwwTm8JDc/GXmW6\nqUui+I64gQOtIzpuW1fvyUtHEMSisI83QRMkF6fCSQm6jJ6oQAtOdZO6R/gYOPNb\n3gayeS8PbMilQcSRSwp6tNTVGyC33p43uUUKAKHnpvAwUSc61aVOtw2wkD062XzM\nhJjYpHm65i4V31AzXo8HF42NrAtZ8K/AuQZne5F/6F4QFVlMKzUoHkSUnTp60XZx\nX77GuyDeDmCgSc2J7xvR5o6VpjsHMo3ek0gJk5ZBnTgkHvnpbULCRxTmDfjeVPue\nv3NN2TBFAoGBAPxbqNEsXPOckGTvG3tUOAAkrK1hfW3TwvrW/7YXg1/6aNV4sklc\nvqn/40kCK0v9xJIv9FM/l0Nq+CMWcrb4sjLeGwHAa8ASfk6hKHbeiTFamA6FBkvQ\n//7GP5khD+y62RlWi9PmwJY21lEkn2mP99THxqvZjQiAVNiqlYdwiIc7AoGBAMH8\nf2Ay7Egc2KYRYU2qwa5E/Cljn/9sdvUnWM+gOzUXpc5sBi+/SUUQT8y/rY4AUVW6\nYaK7chG9YokZQq7ZwTCsYxTfxHK2pnG/tXjOxLFQKBwppQfJcFSRLbw0lMbQoZBk\nS+zb0ufZzxc2fJfXE+XeJxmKs0TS9ltQuJiSqCPPAoGBALEc84K7DBG+FGmCl1sb\nZKJVGwwknA90zCeYtadrIT0/VkxchWSPvxE5Ep+u8gxHcqrXFTdILjWW4chefOyF\n5ytkTrgQAI+xawxsdyXWUZtd5dJq8lxLtx9srD4gwjh3et8ZqtFx5kCHBCu29Fr2\nPA4OmBUMfrs0tlfKgV+pT2j5AoGBAKnA0Z5XMZlxVM0OTH3wvYhI6fk2Kx8TxY2G\nnxsh9m3hgcD/mvJRjEaZnZto6PFoqcRBU4taSNnpRr7+kfH8sCht0k7D+l8AIutL\nffx3xHv9zvvGHZqQ1nHKkaEuyjqo+5kli6N8QjWNzsFbdvBQ0CLJoqGhVHsXuWnz\nW3Z4cBbVAoGAEtnwY1OJM7+R2u1CW0tTjqDlYU2hUNa9t1AbhyGdI2arYp+p+umA\nb5VoYLNsdvZhqjVFTrYNEuhTJFYCF7jAiZLYvYm0C99BqcJnJPl7JjWynoNHNKw3\n9f6PIOE1rAmPE8Cfz/GFF5115ZKVlq+2BY8EKNxbCIy2d/vMEvisnXI=\n-----END RSA PRIVATE KEY-----\"\"\"\n\n  val generatorRSA = KeyPairGenerator.getInstance(JwtUtils.RSA)\n  generatorRSA.initialize(1024)\n  val randomRSAKey = generatorRSA.generateKeyPair()\n\n  val ecGenSpec = new ECGenParameterSpec(ecCurveName)\n  val generatorEC = KeyPairGenerator.getInstance(JwtUtils.ECDSA)\n  generatorEC.initialize(ecGenSpec, new SecureRandom())\n\n  val randomECKey = generatorEC.generateKeyPair()\n\n  val S = BigInt(\n    \"1ed498eedf499e5dd12b1ab94ee03d1a722eaca3ed890630c8b25f1015dd4ec5630a02ddb603f3248a3b87c88637e147ecc7a6e2a1c2f9ff1103be74e5d42def37d\",\n    16\n  )\n  val X = BigInt(\n    \"16528ac15dc4c8e0559fad628ac3ffbf5c7cfefe12d50a97c7d088cc10b408d4ab03ac0d543bde862699a74925c1f2fe7c247c00fddc1442099dfa0671fc032e10a\",\n    16\n  )\n  val Y = BigInt(\n    \"b7f22b3c1322beef766cadd1a5f0363840195b7be10d9a518802d8d528e03bc164c9588c5e63f1473d05195510676008b6808508539367d2893e1aa4b7cb9f9dab\",\n    16\n  )\n\n  val curveParams = ECNamedCurveTable.getParameterSpec(ecCurveName)\n  val curveSpec: ECParameterSpec = new ECNamedCurveSpec(\n    ecCurveName,\n    curveParams.getCurve(),\n    curveParams.getG(),\n    curveParams.getN(),\n    curveParams.getH()\n  )\n\n  val privateSpec = new ECPrivateKeySpec(S.underlying(), curveSpec)\n  val publicSpec = new ECPublicKeySpec(new ECPoint(X.underlying(), Y.underlying()), curveSpec)\n\n  val privateKeyEC = KeyFactory.getInstance(JwtUtils.ECDSA).generatePrivate(privateSpec)\n  val publicKeyEC = KeyFactory.getInstance(JwtUtils.ECDSA).generatePublic(publicSpec)\n\n  def setToken[A <: JwtAlgorithm](entry: DataEntry[A]): DataEntry[A] = {\n    entry.copy(\n      token = Seq(entry.header64, claim64, entry.signature).mkString(\".\"),\n      tokenUnsigned = Seq(entry.header64, claim64, \"\").mkString(\".\"),\n      tokenEmpty = Seq(header64Empty, claim64, \"\").mkString(\".\")\n    )\n  }\n\n  val privateKeyEd25519 = \"MC4CAQAwBQYDK2VwBCIEIHf3EQMqRKbBYOEjmrRm6Zu5hIYombr3DoWaRjZqK7uv\"\n  val publicKeyEd25519 = \"MCowBQYDK2VwAyEAMGx9f797iAEdcI/QULMQFxgnt3ANZAqlTHavvAf3nD4=\"\n\n  val generatorEd25519 = KeyPairGenerator.getInstance(Ed25519)\n  val randomEd25519Key = generatorEd25519.generateKeyPair()\n\n  val data = Seq(\n    DataEntry[JwtHmacAlgorithm](\n      JwtAlgorithm.HMD5,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"HMD5\"}\"\"\",\n      JwtHeader(JwtAlgorithm.HMD5, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJITUQ1In0\",\n      \"BVRxj65Lk3DXIug2IosRvw\"\n    ),\n    DataEntry[JwtHmacAlgorithm](\n      JwtAlgorithm.HS256,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"HS256\"}\"\"\",\n      JwtHeader(JwtAlgorithm.HS256, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\",\n      \"IPSERPZc5wyxrZ4Yiq7l31wFk_qaDY5YrnfLjIC0Lmc\"\n    ),\n    DataEntry[JwtHmacAlgorithm](\n      JwtAlgorithm.HS384,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"HS384\"}\"\"\",\n      JwtHeader(JwtAlgorithm.HS384, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9\",\n      \"tCjCk4PefnNV6E_PByT5xumMVm6KAt_asxP8DXwcDnwsldVJi_Y7SfTVJzvyuGBY\"\n    ),\n    DataEntry[JwtHmacAlgorithm](\n      JwtAlgorithm.HS512,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"HS512\"}\"\"\",\n      JwtHeader(JwtAlgorithm.HS512, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9\",\n      \"ngZsdQj8p2wvUAo8xCbJPwganGPnG5UnLkg7VrE6NgmQdV16UITjlBajZxcai_U5PjQdeN-yJtyA5kxf8O5BOQ\"\n    )\n  ).map(setToken)\n\n  val dataRSA = Seq(\n    DataEntry[JwtRSAAlgorithm](\n      JwtAlgorithm.RS256,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"RS256\"}\"\"\",\n      JwtHeader(JwtAlgorithm.RS256, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9\",\n      \"h943pccw-3rOGJW_RlURooRk7SawDZcQiyP9iziq_LUKHtZME_UMGATeVpoc1aoGK0SlWPlVgV1HaB9fNEyziRYPi7i2K_l9XysRHhdo-luCL_D2rNK4Kv_034knQdC_pZPQ4vMviLDqHVL7w0edG-5-96fzFiP3jwV7FIz7r86fvtNgmKw8cH-cSZfEbj_vgWXT_bE_MHcCE0g4UBiXvTUbd9FpkiTugM6Lr9SXLiFKUtAraOxaKKeZ0VSLMTATK8M2PqLq4I0NnJMaZpcIp1pP9DFz07GomTpMP49Ag4CGzutFIUXz-J277OYDrLjfIT7jDnQIYuzrwE3vatwp2g\"\n    ),\n    DataEntry[JwtRSAAlgorithm](\n      JwtAlgorithm.RS384,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"RS384\"}\"\"\",\n      JwtHeader(JwtAlgorithm.RS384, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9\",\n      \"jpusk3t1NPdT7VaZLB6mO3_L4R59gSbgRM866HVZzN6qkH3vYy9y91eMs6YQZLXgg1nBi1ZY8pb4R9G_on4Xsenh-K7odRCHX-XzVbzAtnljMMChdqKp7zTAlAWF03ZrFyv91kxAQeyQSkwxDP4vP70SCLtt3_kevAzon5fE1L1DD1TNySe52TDCofd2RUPFhWzsfdAPvo_Qj1s_zG-DThHSMXXMY9GOtugyJjbDCDrl8uGeF_0XQm-wBuYQ_EGw0S9TsoI_8dggmeEyv8XwT2XKB20fKOc298GNWJ6q6E01hI0EjmWKXEtTyLG0edAF-QrNkXtkz-yX9WJmjmyVfA\"\n    ),\n    DataEntry[JwtRSAAlgorithm](\n      JwtAlgorithm.RS512,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"RS512\"}\"\"\",\n      JwtHeader(JwtAlgorithm.RS512, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9\",\n      \"UJLq3LjgGgxQHpIHYMc48mCW1GrqwNT4sVF7IpyT6Vtbk0b_TcZ-MoWzkdYnP4-V0D8fl7kxJlLooXDWVso25UQMC66t35pAjFsQHvz7WGn1MQf5F2IOVeS2T_Qg0ckfhykw-jqXgOCrtgI-8lq_A0W8lATLoWjaQSosZxH7oYk6XJY3v5gi3reurAsrbqRCi6Gc87MdB_Yl29acAMr2_G3hun6h_VJckemOsBudLf8kGj_3lCSCY8TLncJYTLB9ZAtWhS92LpKRwPGS2CED2sQcHbq4BK10yJh-YrLrUnhCibBNMVWt1EyFf2obqSl-4Qllv4_WRnCOE4HLrosIYQ\"\n    )\n  ).map(setToken)\n\n  val dataECDSA = Seq(\n    DataEntry[JwtECDSAAlgorithm](\n      JwtAlgorithm.ES256,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"ES256\"}\"\"\",\n      JwtHeader(JwtAlgorithm.ES256, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9\",\n      \"MIGIAkIBFmPwOO2eBdtkCko3pjjJs5Wpdi2GBhRywwptlosRQVlQxmT95uOoKE9BUjqVdyjd8o9TcNHHqM6ayPmQml0aTYICQgGDYkPc5EUfJ1F9VFvbPW1bIpX_sZ3XwyXIeL_4jt7BeKmB_LPorgeO-agmx4UdqMyCG1-Y31m8cJEPNm7h5x5V-Q\"\n    ),\n    DataEntry[JwtECDSAAlgorithm](\n      JwtAlgorithm.ES384,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"ES384\"}\"\"\",\n      JwtHeader(JwtAlgorithm.ES384, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9\",\n      \"MEUCIQC5trx72Z6QKUKxoK_DIX9S3X5QOJBu9tC3f5i6C_1gRQIgOYnA7NoLI3CNVLbibqAwQHSyU44f-yLYGn0YaJvReMA\"\n    ),\n    DataEntry[JwtECDSAAlgorithm](\n      JwtAlgorithm.ES512,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"ES512\"}\"\"\",\n      JwtHeader(JwtAlgorithm.ES512, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9\",\n      \"MEUCICcluU9j5N40Mcr_Mo5_r5KVexcgrXH0LMVC_k1EPswPAiEA-8W2vz2bVZCzPv-S6CNDlbxNktEkOtTAg0XXiZ0ghLk\"\n    )\n  ).map(setToken)\n\n  val dataEdDSA = Seq(\n    DataEntry(\n      JwtAlgorithm.EdDSA,\n      \"\"\"{\"typ\":\"JWT\",\"alg\":\"EdDSA\"}\"\"\",\n      JwtHeader(JwtAlgorithm.EdDSA, \"JWT\"),\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9\",\n      \"I4phqhsuywTyv0Fb12v0X-ILw8tFdDlDExRTsBUYMB2yjo340KXC8L_QfUyO7-8NoMzO5k4rHPkxq8cC2xu8CQ\"\n    )\n  ).map(setToken)\n}\n"
  },
  {
    "path": "core/shared/src/test/scala/JwtBase64Spec.scala",
    "content": "package pdi.jwt\n\nclass JwtBase64Spec extends munit.FunSuite {\n  val eol = System.getProperty(\"line.separator\")\n\n  val values: Seq[(String, String)] = Seq(\n    (\"\", \"\"),\n    (\"a\", \"YQ\"),\n    (\"1\", \"MQ\"),\n    (\n      \"\"\"{\"alg\": \"algo\"}.{\"user\": 1, \"admin\": true, \"value\": \"foo\"}\"\"\",\n      \"eyJhbGciOiAiYWxnbyJ9LnsidXNlciI6IDEsICJhZG1pbiI6IHRydWUsICJ2YWx1ZSI6ICJmb28ifQ\"\n    ),\n    (\n      \"azeklZJEKL,93l,zae:km838{az:e}lekr[l874:e]aze\",\n      \"YXpla2xaSkVLTCw5M2wsemFlOmttODM4e2F6OmV9bGVrcltsODc0OmVdYXpl\"\n    ),\n    (\n      \"\"\"azeqsdwxcrtyfghvbnuyiopjhkml1234567890&é'(-è_çà)=$£ù%*µ,?;.:/!+-*/§äâêëûüîïÂÄÊËÎÏÜÛÔÖZRTYPQSDFGHJKLMWXCVBN<>#{}[]|`\\^@¤\"\"\",\n      \"YXplcXNkd3hjcnR5ZmdodmJudXlpb3BqaGttbDEyMzQ1Njc4OTAmw6knKC3DqF_Dp8OgKT0kwqPDuSUqwrUsPzsuOi8hKy0qL8Knw6TDosOqw6vDu8O8w67Dr8OCw4TDisOLw47Dj8Ocw5vDlMOWWlJUWVBRU0RGR0hKS0xNV1hDVkJOPD4je31bXXxgXF5AwqQ\"\n    )\n  )\n\n  test(\"should encode string\") {\n    values.foreach { value =>\n      assertEquals(value._2, JwtBase64.encodeString(value._1))\n    }\n  }\n\n  test(\"should decode strings\") {\n    values.foreach { value =>\n      assertEquals(value._1, JwtBase64.decodeString(value._2))\n    }\n  }\n\n  test(\"should be symmetrical\") {\n    values.foreach { value =>\n      assertEquals(value._1, JwtBase64.decodeString(JwtBase64.encodeString(value._1)))\n    }\n\n    values.foreach { value =>\n      assertEquals(value._2, JwtBase64.encodeString(JwtBase64.decodeString(value._2)))\n    }\n  }\n\n  test(\"should throw when invalid string\") {\n    val vals = Seq(\"a\", \"abcde\", \"*\", \"aze$\")\n    vals.foreach { v =>\n      intercept[IllegalArgumentException] { JwtBase64.decode(v) }\n    }\n  }\n}\n"
  },
  {
    "path": "core/shared/src/test/scala/JwtClaimSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.{Clock, Instant, ZoneOffset}\n\nimport munit.ScalaCheckSuite\nimport org.scalacheck.Prop.*\n\nclass JwtClaimSpec extends ScalaCheckSuite {\n\n  val fakeNowSeconds = 1615411490L\n  implicit val clock: Clock = Clock.fixed(Instant.ofEpochSecond(fakeNowSeconds), ZoneOffset.UTC)\n  val claim = JwtClaim()\n\n  test(\"JwtClaim.+ should add a json\") {\n    forAll { (value: Long) =>\n      val result = claim + s\"\"\"{\"foo\": $value}\"\"\"\n      assertEquals(result.content, s\"\"\"{\"foo\": $value}\"\"\")\n    }\n  }\n\n  test(\"JwtClaim.+ should add a key/value\") {\n    forAll { (value: Long) =>\n      val result = claim + (\"foo\", value)\n      assertEquals(result.content, s\"\"\"{\"foo\":$value}\"\"\")\n    }\n  }\n\n  test(\"JwtClaim.++ should add a key/value\") {\n    forAll { (value: Long) =>\n      val result = claim ++ (\"foo\" -> value)\n      assertEquals(result.content, s\"\"\"{\"foo\":$value}\"\"\")\n    }\n  }\n\n  test(\"JwtClaim.expireIn should set the expiration time\") {\n    forAll { (delta: Long) =>\n      val result = claim.expiresIn(delta)\n      assertEquals(result.expiration, Some(fakeNowSeconds + delta))\n    }\n  }\n\n  test(\"JwtClaim.expireNow should set the expiration time\") {\n    val result = claim.expiresNow\n    assertEquals(result.expiration, Some(fakeNowSeconds))\n  }\n\n  test(\"JwtClaim.expireAt should set the expiration time\") {\n    forAll { (epoch: Long) =>\n      val result = claim.expiresAt(epoch)\n      assertEquals(result.expiration, Some(epoch))\n    }\n  }\n\n  test(\"JwtClaim.startIn should set the notBefore\") {\n    forAll { (delta: Long) =>\n      val result = claim.startsIn(delta)\n      assertEquals(result.notBefore, Some(fakeNowSeconds + delta))\n    }\n  }\n\n  test(\"JwtClaim.startAt should set the notBefore\") {\n    forAll { (epoch: Long) =>\n      val result = claim.startsAt(epoch)\n      assertEquals(result.notBefore, Some(epoch))\n    }\n  }\n\n  test(\"JwtClaim.startNow should set the notBefore\") {\n    val result = claim.startsNow\n    assertEquals(result.notBefore, Some(fakeNowSeconds))\n  }\n\n  test(\"JwtClaim.issuedIn should set the issuedAt\") {\n    forAll { (delta: Long) =>\n      val result = claim.issuedIn(delta)\n      assertEquals(result.issuedAt, Some(fakeNowSeconds + delta))\n    }\n  }\n\n  test(\"JwtClaim.issuedAt should set the issuedAt\") {\n    forAll { (epoch: Long) =>\n      val result = claim.issuedAt(epoch)\n      assertEquals(result.issuedAt, Some(epoch))\n    }\n  }\n\n  test(\"JwtClaim.issuedNow should set the issuedAt\") {\n    val result = claim.issuedNow\n    assertEquals(result.issuedAt, Some(fakeNowSeconds))\n  }\n}\n"
  },
  {
    "path": "docs/src/main/paradox/index.md",
    "content": "# JWT Scala\n\n@@@ index\n\n- [Native](jwt-core/index.md)\n- [Argonaut](jwt-argonaut.md)\n- [Circe](jwt-circe.md)\n- [Json4S](jwt-json4s.md)\n- [Play Json](jwt-play-json.md)\n- [Play Framework](jwt-play-jwt-session.md)\n- [upickle](jwt-upickle.md)\n- [ZIO Json](jwt-zio-json.md)\n\n@@@\n\nScala support for JSON Web Token ([JWT](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token)).\nSupports Java 8+, Scala 2.12, Scala 2.13 and Scala 3 (for json libraries that support it).\nDependency free.\nOptional helpers for Play Framework, Play JSON, Json4s Native, Json4s Jackson, Circe, uPickle and Argonaut.\n\n## Usage\n\nJWT Scala is divided in several sub-projects each targeting a specific JSON library,\ncheck the doc from the menu for installation and usage instructions.\n\n## Algorithms\n\nIf you are using `String` key, please keep in mind that such keys need to be parsed. Rather than implementing a super complex parser, the one in JWT Scala is pretty simple and might not work for all use-cases (especially for ECDSA keys). In such case, consider using `SecretKey` or `PrivateKey` or `PublicKey` directly. It is way better for you. All API support all those types.\n\nCheck @ref:[ECDSA samples](jwt-core/jwt-ecdsa.md) for more infos.\n\n| Name  | Description                    |\n| ----- | ------------------------------ |\n| HMD5  | HMAC using MD5 algorithm       |\n| HS224 | HMAC using SHA-224 algorithm   |\n| HS256 | HMAC using SHA-256 algorithm   |\n| HS384 | HMAC using SHA-384 algorithm   |\n| HS512 | HMAC using SHA-512 algorithm   |\n| RS256 | RSASSA using SHA-256 algorithm |\n| RS384 | RSASSA using SHA-384 algorithm |\n| RS512 | RSASSA using SHA-512 algorithm |\n| ES256 | ECDSA using SHA-256 algorithm  |\n| ES384 | ECDSA using SHA-384 algorithm  |\n| ES512 | ECDSA using SHA-512 algorithm  |\n| EdDSA | EdDSA signature algorithms     |\n\n## Security concerns\n\nThis lib doesn't want to impose anything, that's why, by default, a JWT claim is totally empty. That said, you should always add an `issuedAt` attribute to it, probably using `claim.issuedNow`.\nThe reason is that even HTTPS isn't perfect and having always the same chunk of data transfered can be of a big help to crack it. Generating a slightly different token at each request is way better even if it adds a bit of payload to the response.\nIf you are using a session timeout through the `expiration` attribute which is extended at each request, that's fine too. I can't find the article I read about that vulnerability but if someone has some resources about the topic, I would be glad to link them.\n\n## License\n\nThis software is licensed under the Apache 2 license, quoted below.\n\nCopyright 2021 JWT-Scala Contributors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this project except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-argonaut.md",
    "content": "## Argonaut\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtArgonaut$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-argonaut\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n@@snip [JwtArgonautDoc.scala](/docs/src/main/scala/JwtArgonautDoc.scala) { #example }\n\n### Encoding\n\n@@snip [JwtArgonautDoc.scala](/docs/src/main/scala/JwtArgonautDoc.scala) { #encoding }\n\n### Decoding\n\n@@snip [JwtArgonautDoc.scala](/docs/src/main/scala/JwtArgonautDoc.scala) { #decoding }\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-circe.md",
    "content": "## Circe\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtCirce$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-circe\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n@@snip [JwtCirceDoc.scala](/docs/src/main/scala/JwtCirceDoc.scala) { #example }\n\n### Encoding\n\n@@snip [JwtCirceDoc.scala](/docs/src/main/scala/JwtCirceDoc.scala) { #encoding }\n\n### Decoding\n\n@@snip [JwtCirceDoc.scala](/docs/src/main/scala/JwtCirceDoc.scala) { #decoding }\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/index.md",
    "content": "@@@ index\n\n- [Claim](jwt-claim.md)\n- [Claim Private](jwt-claim-private.md)\n- [Header](jwt-header.md)\n- [ECDSA](jwt-ecdsa.md)\n\n@@@\n\nThis module doesn't use any dependency, it is useful if you don't have any Json library in your project.\n\nIt is based a naive parsing of Json strings, and doesn't support any custom parameter in the Claim so if you need any custom parameter, or if you're already using one of the supported Json libraries, consider using that instead.\n\n## Native\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/Jwt$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-core\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n```scala mdoc:reset\nimport java.time.Clock\nimport pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions}\nimplicit val clock: Clock = Clock.systemUTC\nval token = Jwt.encode(\"\"\"{\"user\":1}\"\"\", \"secretKey\", JwtAlgorithm.HS256)\nJwt.decodeRawAll(token, \"secretKey\", Seq(JwtAlgorithm.HS256))\nJwt.decodeRawAll(token, \"wrongKey\", Seq(JwtAlgorithm.HS256))\n```\n\n### Encoding\n\n```scala mdoc\n// Encode from string, header automatically generated\nJwt.encode(\"\"\"{\"user\":1}\"\"\", \"secretKey\", JwtAlgorithm.HS384)\n\n// Encode from case class, header automatically generated\n// Set that the token has been issued now and expires in 10 seconds\nJwt.encode(JwtClaim({\"\"\"{\"user\":1}\"\"\"}).issuedNow.expiresIn(10), \"secretKey\", JwtAlgorithm.HS512)\n\n// You can encode without signing it\nJwt.encode(\"\"\"{\"user\":1}\"\"\")\n\n// You can specify a string header but also need to specify the algorithm just to be sure\n// This is not really typesafe, so please use it with care\nJwt.encode(\"\"\"{\"typ\":\"JWT\",\"alg\":\"HS256\"}\"\"\", \"\"\"{\"user\":1}\"\"\", \"key\", JwtAlgorithm.HS256)\n\n// If using a case class header, no need to repeat the algorithm\n// This is way better than the previous one\nJwt.encode(JwtHeader(JwtAlgorithm.HS256), JwtClaim(\"\"\"{\"user\":1}\"\"\"), \"key\")\n```\n\n### Decoding\n\nIn JWT Scala, espcially when using raw strings which are not typesafe at all, there are a lot of possible errors. This is why nearly all `decode` functions will return a `Try` rather than directly the expected result. In case of failure, the wrapped exception should tell you what went wrong.\n\nTake note that nearly all decoding methods (including those from helper libs) support either a `String` key, or a `PrivateKey` with a Hmac algorithm or a `PublicKey` with a RSA or ECDSA algorithm.\n\n```scala mdoc\n// Decode all parts of the token as string\nJwt.decodeRawAll(token, \"secretKey\", JwtAlgorithm.allHmac())\n\n// Decode only the claim as a string\nJwt.decodeRaw(token, \"secretKey\", Seq(JwtAlgorithm.HS256))\n\n// Decode all parts and cast them as a better type if possible.\n// Since the implementation in JWT Core only use string, it is the same as decodeRawAll\n// But check the result in JWT Play JSON to see the difference\nJwt.decodeAll(token, \"secretKey\", Seq(JwtAlgorithm.HS256))\n\n// Same as before, but only the claim\n// (you should start to see a pattern in the naming convention of the functions)\nJwt.decode(token, \"secretKey\", Seq(JwtAlgorithm.HS256))\n\n// Failure because the token is not a token at all\nJwt.decode(\"Hey there!\")\n\n// Failure if not Base64 encoded\nJwt.decode(\"a.b.c\")\n\n// Failure in case we use the wrong key\nJwt.decode(token, \"wrongKey\", Seq(JwtAlgorithm.HS256))\n\n// Failure if the token only starts in 5 seconds\nJwt.decode(Jwt.encode(JwtClaim().startsIn(5)))\n```\n\n### Validating\n\nIf you only want to check if a token is valid without decoding it. You have two options: `validate` functions that will throw the exceptions we saw in the decoding section, so you know what went wrong, or `isValid` functions that will return a boolean in case you don't care about the actual error and don't want to bother with catching exception.\n\n```scala mdoc:crash\n// All good\nJwt.validate(token, \"secretKey\", Seq(JwtAlgorithm.HS256))\nJwt.isValid(token, \"secretKey\", Seq(JwtAlgorithm.HS256))\n\n// Wrong key here\nJwt.validate(token, \"wrongKey\", Seq(JwtAlgorithm.HS256))\nJwt.isValid(token, \"wrongKey\", Seq(JwtAlgorithm.HS256))\n\n// No key for unsigned token => ok\nJwt.validate(Jwt.encode(\"{}\"))\nJwt.isValid(Jwt.encode(\"{}\"))\n\n// No key while the token is actually signed => wrong\nJwt.validate(token)\nJwt.isValid(token)\n\n// The token hasn't started yet!\nJwt.validate(Jwt.encode(JwtClaim().startsIn(5)))\nJwt.isValid(Jwt.encode(JwtClaim().startsIn(5)))\n\n// This is no token\nJwt.validate(\"a.b.c\")\nJwt.isValid(\"a.b.c\")\n```\n\n### Using a custom clock\n\nFor testing, it can sometimes be useful to use a fake clock that will always return a fixed time. It can be done by instanciating\n`Jwt` instead of using the object (based on the system clock):\n\n```scala mdoc\nimport java.time.{Clock, Instant, ZoneId}\n\nval startTime = Clock.fixed(Instant.ofEpochSecond(0), ZoneId.of(\"UTC\"))\nval endTime = Clock.fixed(Instant.ofEpochSecond(5), ZoneId.of(\"UTC\"))\n\nval customJwt = Jwt(endTime)\n\nval claim = JwtClaim().issuedNow(startTime).expiresIn(10)(startTime)\nval encoded = customJwt.encode(claim, \"key\", JwtAlgorithm.HS256)\n\ncustomJwt.decode(encoded, \"key\", JwtAlgorithm.allHmac())\n```\n\n### Options\n\nAll validating and decoding methods support a final optional argument as a `JwtOptions` which allow you to disable validation checks. This is useful if you need to access data from an expired token for example. You can disable `expiration`, `notBefore` and `signature` checks. Be warned that if you disable the last one, you have no guarantee that the user didn't change the content of the token.\n\n```scala mdoc\nval expiredToken = Jwt.encode(JwtClaim().by(\"me\").expiresIn(-1))\n\n// Fail since the token is expired\nJwt.isValid(expiredToken)\nJwt.decode(expiredToken)\n\n// Let's disable expiration check\nJwt.isValid(expiredToken, JwtOptions(expiration = false))\nJwt.decode(expiredToken, JwtOptions(expiration = false))\n```\n\nYou can also specify a leeway, in seconds, to account for clock skew.\n\n```scala mdoc\n// Allow 30sec leeway\nJwt.isValid(expiredToken, JwtOptions(leeway = 30))\nJwt.decode(expiredToken, JwtOptions(leeway = 30))\n```\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-claim-private.md",
    "content": "## Jwt Reserved Claims and Private Claims\n\nA common use-case of Jwt-Scala (and JWT at large) is developing so-called \"public\" or \"private\" claims (and or header params).  These are functionally no different than \"reserved\" claims/header params, other than that they have no standard definition and may only be distinguished within your network or niche industry. \"issuer\", \"subject\", \"audience\" etc. are all examples of reserved claims, whereas \"user\" is a fairly common example of a non-reserved claim.\n\nGiven that there may be many of these public/private claims, rather than parsing them yourself separate from how reserved claims are parsed (see the *JwtClaim Class*), you can simply compose `JwtClaim` with your own custom claims that extend from the `JwtReservedClaim` trait.\n\nHere is an example where reserved headers, along with a private \"user\" claim, is used:\n\n```scala mdoc:reset\nimport pdi.jwt.{Jwt, JwtHeader, JwtClaim, JwtUtils, JwtJson4sParser}\nimport java.time.Clock\n\n// define your network-specific claims, and compose them with the usual reservedClaims\ncase class JwtPrivateClaim(user: Option[String] = None, reservedClaims: JwtClaim = JwtClaim()) {\n  // merge your json definition along with the reserved claims too\n  def toJson: String = JwtUtils.mergeJson(JwtUtils.hashToJson(Seq(\n      \"user\" -> user,\n    ).collect {\n      case (key, Some(value)) => (key -> value)\n    }), reservedClaims.toJson)\n}\n\n// create a parser with claim type set to the one you just defined\n// notice that the default `JwtHeader` class was used since we're only interested in overriding with a custom private claims type in this example\nobject JwtJson4sPrivate extends JwtJson4sParser[JwtHeader, JwtPrivateClaim] {\n  override implicit val clock = Clock.systemUTC\n\n  override protected def parseClaim(claim: String): JwtPrivateClaim = {\n    val claimJson = super.parse(claim)\n    val jwtReservedClaim: JwtClaim = super.readClaim(claimJson)\n    val content = super.parse(jwtReservedClaim.content)\n    JwtPrivateClaim(super.extractString(content, \"user\"), jwtReservedClaim.withContent(\"{}\"))\n  }\n\n  // here is the only boilerplate (but if you chose to also specify a custom header type then you would make use of this)\n  override protected def parseHeader(header: String): JwtHeader = super.readHeader(parse(header))\n\n  // marginal boilerplate to ensure consistency with isValid checks now that your nesting reserved claims into your custom private claims\n  override protected def extractExpiration(claim: JwtPrivateClaim): Option[Long] = claim.reservedClaims.expiration\n  override protected def extractNotBefore(claim: JwtPrivateClaim): Option[Long] = claim.reservedClaims.notBefore\n}\n```\n\nYou can then use the same decodeAll method as you would before, now with your fully objectified claims:\n\n```scala mdoc\nimport scala.util.Try\n// this example chose to use JwtJson4s, but any Json implementation would work the same\nval token: String = Jwt.encode(\"\"\"{\"user\":\"someone\", \"iss\": \"me\"}\"\"\");\nval decoded: Try[(JwtHeader, JwtPrivateClaim, String)] = JwtJson4sPrivate.decodeAll(token)\n```\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-claim.md",
    "content": "## JwtClaim Class\n\n```scala mdoc:reset\nimport java.time.Clock\nimport pdi.jwt.JwtClaim\n\nJwtClaim()\n\nimplicit val clock: Clock = Clock.systemUTC\n\n// Specify the content as JSON string\n// (don't use var in your code if possible, this is just to ease the sample)\nvar claim = JwtClaim(\"\"\"{\"user\":1}\"\"\")\n\n// Append new content\nclaim = claim + \"\"\"{\"key1\":\"value1\"}\"\"\"\nclaim = claim + (\"key2\", true)\nclaim = claim ++ ((\"key3\", 3), (\"key4\", Seq(1, 2)), (\"key5\", (\"key5.1\", \"Subkey\")))\n\n// Stringify as JSON\nclaim.toJson\n\n// Manipulate basic attributes\n// Set the issuer\nclaim = claim.by(\"Me\")\n\n// Set the audience\nclaim = claim.to(\"You\")\n\n// Set the subject\nclaim = claim.about(\"Something\")\n\n// Set the id\nclaim = claim.withId(\"42\")\n\n// Set the expiration\n// In 10 seconds from now\nclaim = claim.expiresIn(5)\n// At a specific timestamp (in seconds)\nclaim.expiresAt(1431520421)\n// Right now! (the token is directly invalid...)\nclaim.expiresNow\n\n// Set the beginning of the token (aka the \"not before\" attribute)\n// 5 seconds ago\nclaim.startsIn(-5)\n// At a specific timestamp (in seconds)\nclaim.startsAt(1431520421)\n// Right now!\nclaim = claim.startsNow\n\n// Set the date when the token was created\n// (you should always use claim.issuedNow, but I let you do otherwise if needed)\n// 5 seconds ago\nclaim.issuedIn(-5)\n// At a specific timestamp (in seconds)\nclaim.issuedAt(1431520421)\n// Right now!\nclaim = claim.issuedNow\n\n// We can test if the claim is valid => testing if the current time is between \"not before\" and \"expiration\"\nclaim.isValid\n\n// Also test the issuer and audience\nclaim.isValid(\"Me\", \"You\")\n\n// Let's stringify the final version\nclaim.toJson\n```\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-ecdsa.md",
    "content": "## Jwt with ECDSA algorithms\n\n### With generated keys\n\n#### Generation\n\n```scala\nimport org.bouncycastle.jce.provider.BouncyCastleProvider\nimport java.security.spec.{ECPrivateKeySpec, ECPublicKeySpec, ECGenParameterSpec, ECParameterSpec, ECPoint}\nimport java.security.{SecureRandom, KeyFactory, KeyPairGenerator, Security}\nimport pdi.jwt.{Jwt, JwtAlgorithm}\n// We specify the curve we want to use\nval ecGenSpec = new ECGenParameterSpec(\"P-521\")\n// We are going to use a ECDSA algorithm\n// and the Bouncy Castle provider\nif (Security.getProvider(\"BC\") == null) {\n  Security.addProvider(new BouncyCastleProvider())\n}\nval generatorEC = KeyPairGenerator.getInstance(\"ECDSA\", \"BC\")\ngeneratorEC.initialize(ecGenSpec, new SecureRandom())\n// Generate a pair of keys, one private for encoding\n// and one public for decoding\nval ecKey = generatorEC.generateKeyPair()\n```\n\n#### Usage\n\n```scala\nval token = Jwt.encode(\"\"\"{\"user\":1}\"\"\", ecKey.getPrivate, JwtAlgorithm.ES512)\n\nJwt.decode(token, ecKey.getPublic, JwtAlgorithm.allECDSA)\n```\n\n### With saved keys\n\nLet's say you already have your keys, it means you know the **S** param for the private key and both **(X, Y)** for the public key. So we will first recreate the keys from those params and then use them just as we did for the previously generated keys.\n\n#### Creation\n\n```scala\nimport org.bouncycastle.jce.ECNamedCurveTable\nimport org.bouncycastle.jce.spec.ECNamedCurveSpec\n\n// Our saved params\nval S = BigInt(\"1ed498eedf499e5dd12b1ab94ee03d1a722eaca3ed890630c8b25f1015dd4ec5630a02ddb603f3248a3b87c88637e147ecc7a6e2a1c2f9ff1103be74e5d42def37d\", 16)\nval X = BigInt(\"16528ac15dc4c8e0559fad628ac3ffbf5c7cfefe12d50a97c7d088cc10b408d4ab03ac0d543bde862699a74925c1f2fe7c247c00fddc1442099dfa0671fc032e10a\", 16)\nval Y = BigInt(\"b7f22b3c1322beef766cadd1a5f0363840195b7be10d9a518802d8d528e03bc164c9588c5e63f1473d05195510676008b6808508539367d2893e1aa4b7cb9f9dab\", 16)\n\n// Here we are using the P-521 curve but you need to change it\n// to your own curve\nval curveParams = ECNamedCurveTable.getParameterSpec(\"P-521\")\nval curveSpec: ECParameterSpec = new ECNamedCurveSpec( \"P-521\", curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());\n\nval privateSpec = new ECPrivateKeySpec(S.underlying(), curveSpec)\nval publicSpec = new ECPublicKeySpec(new ECPoint(X.underlying(), Y.underlying()), curveSpec)\n\nval privateKeyEC = KeyFactory.getInstance(\"ECDSA\", \"BC\").generatePrivate(privateSpec)\nval publicKeyEC = KeyFactory.getInstance(\"ECDSA\", \"BC\").generatePublic(publicSpec)\n```\n\n#### Usage\n\n```scala\nval token = Jwt.encode(\"\"\"{\"user\":1}\"\"\", privateKeyEC, JwtAlgorithm.ES512)\n\nJwt.decode(token, publicKeyEC, Seq(JwtAlgorithm.ES512))\n\n// Wrong key...\nJwt.decode(token, ecKey.getPublic, Seq(JwtAlgorithm.ES512))\n```\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-header.md",
    "content": "## JwtHeader Case Class\n\n```scala mdoc:reset\nimport pdi.jwt.{JwtHeader, JwtAlgorithm}\n\nJwtHeader()\nJwtHeader(JwtAlgorithm.HS256)\nJwtHeader(JwtAlgorithm.HS256, \"JWT\")\n\n// You can stringify it to JSON\nJwtHeader(JwtAlgorithm.HS256, \"JWT\").toJson\n\n// You can assign the default type (but it would have be done automatically anyway)\nJwtHeader(JwtAlgorithm.HS256).withType\n```\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-json4s.md",
    "content": "## Json4s\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtJson4s$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-json4s\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n@@snip [JwtJson4sDoc.scala](/docs/src/main/scala/JwtJson4sDoc.scala) { #example }\n\n### Encoding\n\n@@snip [JwtJson4sDoc.scala](/docs/src/main/scala/JwtJson4sDoc.scala) { #encode }\n\n### Decoding\n\n@@snip [JwtJson4sDoc.scala](/docs/src/main/scala/JwtJson4sDoc.scala) { #decode }\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-play-json.md",
    "content": "## Play Json\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtJson$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-play-json\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #example }\n\n### Encoding\n\n@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #encode }\n\n### Decoding\n\n@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #decode }\n\n### Formating\n\nThe project provides implicit reader and writer for both `JwtHeader` and `JwtClaim`\n\n@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #format }\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-play-jwt-session.md",
    "content": "## JwtSession case class\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-play\" % \"$project.version$\"\n```\n\n@@@\n\nProvides an API similar to the Play [Session](https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.mvc.Session) but using `JsValue` rather than `String` as values. It also separates `headerData` from `claimData` rather than having only one `data`.\n\n### Basic usage\n\n@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #example }\n\n### Using implicits\n\nIf you have implicit `Reads` and/or `Writes`, you can access and/or add data directly as case class or object.\n\n@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #implicits }\n\n## Play RequestHeader\n\nYou can extract a `JwtSession` from a `RequestHeader`.\n\n@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #requestheader }\n\n## Play Result\n\nThere are also implicit helpers around `Result` to help you manipulate the session inside it.\n\n@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #result }\n\n## Play configuration\n\n### Secret key\n\n`play.http.secret.key`\n\n> Default: none\n\nThe secret key is used to secure cryptographics functions. We are using the same key to sign Json Web Tokens so you don't need to worry about it.\n\n### Private key\n\n`play.http.session.privateKey`\n\n> Default: none\n\nThe PKCS8 format private key is used to sign JWT session. If `play.http.session.privateKey` is missing `play.http.secret.key` used instead.\n\n### Public key\n\n`play.http.session.publicKey`\n\n> Default: none\n\nThe X.509 format public key is used to verify JWT session signed with private key `play.http.session.privateKey`\n\n### Session timeout\n\n`play.http.session.maxAge`\n\n> Default: none\n\nJust like for the cookie session, you can use this key to specify the duration, in milliseconds or using the duration syntax (for example 30m or 1h), after which the user should be logout, which mean the token will no longer be valid. It means you need to refresh the expiration date at each request\n\n### Signature algorithm\n\n`play.http.session.algorithm`\n\n> Default: HS256\n>\n> Supported: HMD5, HS1, HS224, HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512\n\nYou can specify which algorithm you want to use, among the supported ones, in order to create the signature which will assure you that nobody can actually change the token. You should probably stick with the default one or use HmacSHA512 for maximum security.\n\n### Header name\n\n`play.http.session.jwtName`\n\n> Default: Authorization\n\nYou can change the name of the header in which the token should be stored. It will be used for both requests and responses.\n\n### Response header name\n\n`play.http.session.jwtResponseName`\n\n> Default: none\n\nIf you need to have a different header for request and response, you can override the response header using this key.\n\n### Token prefix\n\n`play.http.session.tokenPrefix`\n\n> Default: \"Bearer \"\n\nAuthorization header should have a prefix before the token, like \"Basic\" for example. For a JWT token, it should be \"Bearer\" (which is the default value) but you can freely change or remove it (using an empty string). The token prefix will be directly prepend before the token, so be sure to put any necessary whitespaces in it.\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-upickle.md",
    "content": "## upickle\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtUpickle$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-upickle\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n@@snip [JwtUpickleDoc.scala](/docs/src/main/scala/JwtUpickleDoc.scala) { #example }\n\n### Encoding\n\n@@snip [JwtUpickleDoc.scala](/docs/src/main/scala/JwtUpickleDoc.scala) { #encoding }\n\n### Decoding\n\n@@snip [JwtUpickleDoc.scala](/docs/src/main/scala/JwtUpickleDoc.scala) { #decoding }\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-zio-json.md",
    "content": "## ZIO Json\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtZioJson$.html)\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-zio-json\" % \"$project.version$\"\n```\n\n@@@\n\n### Basic usage\n\n@@snip [JwtZioDoc.scala](/docs/src/main/scala/JwtZioDoc.scala) { #example }\n\n### Encoding\n\n@@snip [JwtZioDoc.scala](/docs/src/main/scala/JwtZioDoc.scala) { #encoding }\n\n### Decoding\n\n@@snip [JwtZioDoc.scala](/docs/src/main/scala/JwtZioDoc.scala) { #decoding }\n"
  },
  {
    "path": "docs/src/main/paradox/project/build.properties",
    "content": "sbt.version=1.5.0\n"
  },
  {
    "path": "docs/src/main/scala/JwtArgonautDoc.scala",
    "content": "package pdi.jwt.docs\n\nobject JwtArgonautDoc {\n\n  // #example\n  import java.time.Instant\n  import scala.util.Try\n\n  import argonaut.Json\n  import pdi.jwt.{JwtAlgorithm, JwtArgonaut, JwtClaim}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now().plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n\n  val key = \"secretKey\"\n  val alg = JwtAlgorithm.HS512\n\n  val token = JwtArgonaut.encode(claim, key, alg)\n  val decodedJson: Try[Json] = JwtArgonaut.decodeJson(token, key, Seq(alg))\n  val decodedClaim: Try[JwtClaim] = JwtArgonaut.decode(token, key, Seq(alg))\n  // #example\n}\n\nobject JwtArgonautDocEncoding {\n  // #encoding\n  import java.time.Instant\n\n  import argonaut.Parse\n  import pdi.jwt.{JwtAlgorithm, JwtArgonaut}\n\n  val key = \"secretKey\"\n  val alg = JwtAlgorithm.HS512\n\n  val jsonClaim = Parse.parseOption(s\"\"\"{\"expires\":${Instant.now().getEpochSecond}}\"\"\").get\n  val jsonHeader = Parse.parseOption(\"\"\"{\"typ\":\"JWT\",\"alg\":\"HS512\"}\"\"\").get\n\n  val token1: String = JwtArgonaut.encode(jsonClaim)\n  val token2: String = JwtArgonaut.encode(jsonClaim, key, alg)\n  val token3: String = JwtArgonaut.encode(jsonHeader, jsonClaim, key)\n  // #encoding\n}\n\nobject JwtArgonautDocDecoding {\n  import java.time.Instant\n  // #decoding\n  import scala.util.Try\n\n  import argonaut.Json\n  import pdi.jwt.{JwtAlgorithm, JwtArgonaut, JwtClaim, JwtHeader}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val alg = JwtAlgorithm.HS512\n\n  val token = JwtArgonaut.encode(claim, key, alg)\n\n  val decodedJsonClaim: Try[Json] = JwtArgonaut.decodeJson(token, key, Seq(alg))\n  val decodedJson: Try[(Json, Json, String)] = JwtArgonaut.decodeJsonAll(token, key, Seq(alg))\n\n  val decodedClaim: Try[JwtClaim] = JwtArgonaut.decode(token, key, Seq(alg))\n  val decodedToken: Try[(JwtHeader, JwtClaim, String)] = JwtArgonaut.decodeAll(token, key, Seq(alg))\n  // #decoding\n}\n"
  },
  {
    "path": "docs/src/main/scala/JwtCirceDoc.scala",
    "content": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject CirceExample {\n\n  // #example\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val token = JwtCirce.encode(claim, key, algo)\n\n  JwtCirce.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtCirce.decode(token, key, Seq(JwtAlgorithm.HS256))\n  // #example\n}\n\n@nowarn\nobject CirceEncoding {\n  // #encoding\n  import java.time.Instant\n\n  import io.circe._\n  import jawn.{parse => jawnParse}\n  import pdi.jwt.{JwtAlgorithm, JwtCirce}\n\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val Right(claimJson) = jawnParse(s\"\"\"{\"expires\":${Instant.now.getEpochSecond}}\"\"\")\n  val Right(header) = jawnParse(\"\"\"{\"typ\":\"JWT\",\"alg\":\"HS256\"}\"\"\")\n  // From just the claim to all possible attributes\n  JwtCirce.encode(claimJson)\n  JwtCirce.encode(claimJson, key, algo)\n  JwtCirce.encode(header, claimJson, key)\n  // #encoding\n}\n\n@nowarn\nobject CirceDecoding {\n  // #decoding\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val token = JwtCirce.encode(claim, key, algo)\n\n  // You can decode to JsObject\n  JwtCirce.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtCirce.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))\n  // Or to case classes\n  JwtCirce.decode(token, key, Seq(JwtAlgorithm.HS256))\n  JwtCirce.decodeAll(token, key, Seq(JwtAlgorithm.HS256))\n  // #decoding\n}\n"
  },
  {
    "path": "docs/src/main/scala/JwtJson4sDoc.scala",
    "content": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtJson4sDoc {\n  // #example\n  import org.json4s.JsonDSL.WithBigDecimal._\n  import org.json4s._\n  import pdi.jwt.{JwtAlgorithm, JwtJson4s}\n\n  val claim = JObject((\"user\", 1), (\"nbf\", 1431520421))\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  JwtJson4s.encode(claim)\n\n  val token = JwtJson4s.encode(claim, key, algo)\n\n  JwtJson4s.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n\n  JwtJson4s.decode(token, key, Seq(JwtAlgorithm.HS256))\n  // #example\n\n  // #encode\n  val header = JObject((\"typ\", \"JWT\"), (\"alg\", \"HS256\"))\n\n  JwtJson4s.encode(claim)\n  JwtJson4s.encode(claim, key, algo)\n  JwtJson4s.encode(header, claim, key)\n  // #encode\n\n  // #decode\n  // You can decode to JsObject\n  JwtJson4s.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtJson4s.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))\n  // Or to case classes\n  JwtJson4s.decode(token, key, Seq(JwtAlgorithm.HS256))\n  JwtJson4s.decodeAll(token, key, Seq(JwtAlgorithm.HS256))\n  // #decode\n}\n"
  },
  {
    "path": "docs/src/main/scala/JwtPlayJsonDoc.scala",
    "content": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtPlayJsonDoc {\n  // #example\n  import java.time.Clock\n\n  import pdi.jwt._\n  import play.api.libs.json.Json\n\n  implicit val clock: Clock = Clock.systemUTC\n\n  val claim = Json.obj((\"user\", 1), (\"nbf\", 1431520421))\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  JwtJson.encode(claim)\n\n  val token = JwtJson.encode(claim, key, algo)\n\n  JwtJson.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n\n  JwtJson.decode(token, key, Seq(JwtAlgorithm.HS256))\n  // #example\n\n  // #encode\n  val header = Json.obj((\"typ\", \"JWT\"), (\"alg\", \"HS256\"))\n  // From just the claim to all possible attributes\n  JwtJson.encode(claim)\n  JwtJson.encode(claim, key, algo)\n  JwtJson.encode(header, claim, key)\n  // #encode\n\n  // #decode\n  // You can decode to JsObject\n  JwtJson.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtJson.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))\n  // Or to case classes\n  JwtJson.decode(token, key, Seq(JwtAlgorithm.HS256))\n  JwtJson.decodeAll(token, key, Seq(JwtAlgorithm.HS256))\n  // #decode\n\n  implicit val c: Clock = Clock.systemUTC\n  // #format\n  import pdi.jwt.JwtJson._\n\n  // Reads\n  Json.fromJson[JwtHeader](header)\n  Json.fromJson[JwtClaim](claim)\n\n  // Writes\n  Json.toJson(JwtHeader(JwtAlgorithm.HS256))\n  Json.toJson(JwtClaim(\"\"\"{\"user\":1}\"\"\").issuedNow.expiresIn(10))\n  // Or\n  JwtHeader(JwtAlgorithm.HS256).toJsValue()\n  JwtClaim(\"\"\"{\"user\":1}\"\"\").issuedNow.expiresIn(10).toJsValue()\n  // #format\n}\n"
  },
  {
    "path": "docs/src/main/scala/JwtPlayJwtSessionDoc.scala",
    "content": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtPlayJwtSessionDoc {\n  // #example\n  import java.time.Clock\n\n  import pdi.jwt.JwtSession\n  import play.api.Configuration\n\n  implicit val clock: Clock = Clock.systemUTC\n\n//In a real Play! App this should normally be injected in the constructor with @Inject()\n  implicit val conf: Configuration = Configuration.reference\n\n// Let's create a session, it will automatically assign a default header. No\n// In your app, the default header would be generated from \"application.conf\" file\n// but here, it will just use the default values (which are all empty)\n  var session = JwtSession()\n\n// We can add a (key, value)\n  session = session + (\"user\", 1)\n\n// Or several of them\n  session = session ++ ((\"nbf\", 1431520421), (\"key\", \"value\"), (\"key2\", 2), (\"key3\", 3))\n\n// Also remove a key\n  session = session - \"key\"\n\n// Or several\n  session = session -- (\"key2\", \"key3\")\n\n// We can access a specific key\n  session.get(\"user\")\n\n// Test if the session is empty or not\n// (it is not here since we have several keys in the claimData)\n  session.isEmpty()\n\n// Serializing the session is the same as encoding it as a JSON Web Token\n  val token = session.serialize\n\n// You can create a JwtSession from a token of course\n  JwtSession.deserialize(token)\n\n// You could refresh the session to set its expiration in a few seconds from now\n// but you need to set \"session.maxAge\" in your \"application.conf\" and since this\n// is not a real Play application, we cannot do that, so here, the refresh will do nothing.\n  session = session.refresh()\n  // #example\n  import pdi.jwt.JwtSession\n// #implicits\n// First, creating the implicits\n  import play.api.libs.json._\n\n  case class User(id: Long, name: String)\n  implicit val formatUser: Format[User] = Json.format[User]\n\n// Next, adding it to a new session\n  val session2 = JwtSession() + (\"user\", User(42, \"Paul\"))\n\n// Finally, accessing it\n  session2.getAs[User](\"user\")\n// #implicits\n// #requestheader\n  import pdi.jwt.JwtSession._\n  import play.api.Configuration\n  import play.api.test.FakeRequest\n\n// Default JwtSession\n  FakeRequest().jwtSession\n\n// What about some headers?\n// (the default header for a JSON Web Token is \"Authorization\" and it should be prefixed by \"Bearer \")\n  val request = FakeRequest().withHeaders((\"Authorization\", \"Bearer \" + session2.serialize))\n  request.jwtSession\n\n// It means you can directly read case classes from the session!\n// And that's pretty cool\n  request.jwtSession.getAs[User](\"user\")\n// #requestheader\n// #result\n  import play.api.mvc._\n  implicit val implRequest: FakeRequest[AnyContentAsEmpty.type] = request\n\n// Let's begin by creating a Result\n  var result: play.api.mvc.Result = play.api.mvc.Results.Ok\n\n// We can already get a JwtSession from our implicit RequestHeader\n  result.jwtSession\n\n// Setting a new empty JwtSession\n  result = result.withNewJwtSession\n\n// Or from an existing JwtSession\n  result = result.withJwtSession(session2)\n\n// Or from a JsObject\n  result = result.withJwtSession(Json.obj((\"id\", 1), (\"key\", \"value\")))\n\n// Or from (key, value)\n  result = result.withJwtSession((\"id\", 1), (\"key\", \"value\"))\n\n// We can add stuff to the current session (only (String, String))\n  result = result.addingToJwtSession((\"key2\", \"value2\"), (\"key3\", \"value3\"))\n\n// Or directly classes or objects if you have the correct implicit Writes\n  result = result.addingToJwtSession(\"user\", User(1, \"Paul\"))\n\n// Removing from session\n  result = result.removingFromJwtSession(\"key2\", \"key3\")\n\n// Refresh the current session\n  result = result.refreshJwtSession\n\n// So, at the end, you can do\n  result.jwtSession.getAs[User](\"user\")\n// #result\n\n}\n"
  },
  {
    "path": "docs/src/main/scala/JwtUpickleDoc.scala",
    "content": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtUpickleDoc {\n  // #example\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtClaim, JwtUpickle}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val token = JwtUpickle.encode(claim, key, algo)\n\n  JwtUpickle.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtUpickle.decode(token, key, Seq(JwtAlgorithm.HS256))\n  // #example\n\n  // #encoding\n}\n\n@nowarn\nobject UpickleEncode {\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtUpickle}\n  import upickle.default._\n\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val claimJson = read[ujson.Value](s\"\"\"{\"expires\":${Instant.now.getEpochSecond}}\"\"\")\n  val header = read[ujson.Value](\"\"\"{\"typ\":\"JWT\",\"alg\":\"HS256\"}\"\"\")\n  // From just the claim to all possible attributes\n  JwtUpickle.encode(claimJson)\n  JwtUpickle.encode(claimJson, key, algo)\n  JwtUpickle.encode(header, claimJson, key)\n  // #encoding\n}\n\n@nowarn\nobject UpickleDecode {\n  // #decoding\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtClaim, JwtUpickle}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val token = JwtUpickle.encode(claim, key, algo)\n\n  // You can decode to JsObject\n  JwtUpickle.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtUpickle.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))\n  // Or to case classes\n  JwtUpickle.decode(token, key, Seq(JwtAlgorithm.HS256))\n  JwtUpickle.decodeAll(token, key, Seq(JwtAlgorithm.HS256))\n  // #decoding\n}\n"
  },
  {
    "path": "docs/src/main/scala/JwtZioDoc.scala",
    "content": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtZioDoc {\n  // #example\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtClaim, JwtZIOJson}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val token = JwtZIOJson.encode(claim, key, algo)\n\n  JwtZIOJson.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtZIOJson.decode(token, key, Seq(JwtAlgorithm.HS256))\n  // #example\n}\n\n@nowarn\nobject ZioEncoding {\n  // #encoding\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtZIOJson}\n  import zio.json._\n  import zio.json.ast._\n\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val claimJsonEither = s\"\"\"{\"expires\":${Instant.now.getEpochSecond}}\"\"\".fromJson[Json]\n  val headerEither = \"\"\"{\"typ\":\"JWT\",\"alg\":\"HS256\"}\"\"\".fromJson[Json]\n// From just the claim to all possible attributes\n  for {\n    claimJson <- claimJsonEither\n    header <- headerEither\n  } yield {\n    JwtZIOJson.encode(claimJson)\n    JwtZIOJson.encode(claimJson, key, algo)\n    JwtZIOJson.encode(header, claimJson, key)\n  }\n  // #encoding\n}\n\n@nowarn\nobject ZioDecoding {\n  // #decoding\n  import java.time.Instant\n\n  import pdi.jwt.{JwtAlgorithm, JwtClaim, JwtZIOJson}\n\n  val claim = JwtClaim(\n    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),\n    issuedAt = Some(Instant.now.getEpochSecond)\n  )\n  val key = \"secretKey\"\n  val algo = JwtAlgorithm.HS256\n\n  val token = JwtZIOJson.encode(claim, key, algo)\n\n// You can decode to JsObject\n  JwtZIOJson.decodeJson(token, key, Seq(JwtAlgorithm.HS256))\n  JwtZIOJson.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))\n// Or to case classes\n  JwtZIOJson.decode(token, key, Seq(JwtAlgorithm.HS256))\n  JwtZIOJson.decodeAll(token, key, Seq(JwtAlgorithm.HS256))\n// #decoding\n}\n"
  },
  {
    "path": "json/argonaut/src/main/scala/JwtArgonaut.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport argonaut.*\nimport argonaut.Argonaut.*\n\ntrait JwtArgonautParser[H, C] extends JwtJsonCommon[Json, H, C] {\n  override protected def parse(value: String): Json = Parse.parseOption(value).get\n\n  override protected def stringify(value: Json): String = value.nospaces\n\n  override protected def getAlgorithm(header: Json): Option[JwtAlgorithm] = header\n    .field(\"alg\")\n    .map(_.stringOrEmpty)\n    .filterNot(_ == \"none\")\n    .map(JwtAlgorithm.fromString)\n}\n\nobject JwtArgonaut extends JwtArgonaut(Clock.systemUTC) {\n  def apply(clock: Clock): JwtArgonaut = new JwtArgonaut(clock)\n\n}\n\nclass JwtArgonaut(override val clock: Clock) extends JwtArgonautParser[JwtHeader, JwtClaim] {\n\n  override protected def parseClaim(claim: String): JwtClaim = parseClaimHelp(claim)\n\n  override protected def parseHeader(header: String): JwtHeader = parseHeaderHelp(header)\n\n  implicit class ExtractJsonFieldToType(json: Json) {\n\n    def -|>[T](field: String)(f: Json => T): Option[T] =\n      (json -| field).map(f)\n\n    def -|>>(field: String): Option[String] =\n      (json -|> field)(_.stringOrEmpty)\n\n    def -||>(field: String): Option[Long] =\n      (json -|> field)(_.nospaces.toLong)\n\n    def -|||(field: String): Option[Set[String]] =\n      (json -|> field)(_.arrayOrEmpty.map(_.nospaces).toSet)\n  }\n\n  private def parseClaimHelp(claim: String): JwtClaim =\n    Parse.parseOption(claim) match {\n      case Some(value) => jsonToJwtClaim(value)\n      case None        => JwtClaim()\n    }\n\n  private def parseHeaderHelp(header: String): JwtHeader =\n    Parse.parseOption(header).map(jsonToJwtHeader) match {\n      case Some(value) => value\n      case None        => JwtHeader(None)\n    }\n\n  private def jsonToJwtHeader(json: Json): JwtHeader = {\n    val alg = getAlgorithm(json)\n    val typ = json -|>> \"typ\"\n    val contentType = json -|>> \"cty\"\n    val keyId = json -|>> \"kid\"\n    JwtHeader(alg, typ, contentType, keyId)\n  }\n\n  private val jwtSpecificFieldNames = List(\"iss\", \"sub\", \"aud\", \"exp\", \"nbf\", \"iat\", \"jti\")\n\n  private def jsonToJwtClaim(json: Json): JwtClaim = {\n    val issuer = json -|>> \"iss\"\n    val subject = json -|>> \"sub\"\n    val audience = json -||| \"aud\"\n    val expiration = json -||> \"exp\"\n    val notBefore = json -||> \"nbf\"\n    val issuedAt = json -||> \"iat\"\n    val jwtId = json -|>> \"jti\"\n    val content = json.objectFieldsOrEmpty\n      .filterNot(jwtSpecificFieldNames.contains)\n      .map { field =>\n        (field, (json -| field).get)\n      }\n      .foldRight(jEmptyObject) { case ((fieldName, field), obj) =>\n        (fieldName := field) ->: obj\n      }\n    JwtClaim(content.nospaces, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)\n  }\n}\n"
  },
  {
    "path": "json/argonaut/src/test/scala/ArgonautFixture.scala",
    "content": "package pdi.jwt\n\nimport argonaut.*\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String,\n    tokenUnsigned: String,\n    tokenEmpty: String,\n    headerJson: Json\n) extends JsonDataEntryTrait[Json]\n\ntrait ArgonautFixture extends JsonCommonFixture[Json] {\n  protected def parse(string: String): Json = Parse.parseOption(string).get\n\n  override val claimJson: Json = parse(claim)\n\n  override val headerEmptyJson: Json = parse(headerEmpty)\n\n  override def mapData(data: DataEntryBase): JsonDataEntry = JsonDataEntry(\n    data.algo,\n    data.header,\n    data.headerClass,\n    data.header64,\n    data.signature,\n    data.token,\n    data.tokenUnsigned,\n    data.tokenEmpty,\n    parse(data.header)\n  )\n}\n"
  },
  {
    "path": "json/argonaut/src/test/scala/JwtArgonautSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport argonaut.Json\n\nclass JwtArgonautSpec extends JwtJsonCommonSpec[Json] with ArgonautFixture {\n  override def jwtJsonCommon(clock: Clock): JwtJsonCommon[Json, JwtHeader, JwtClaim] = JwtArgonaut(\n    clock\n  )\n}\n"
  },
  {
    "path": "json/circe/jvm/src/test/scala/CirceFixture.scala",
    "content": "package pdi.jwt\n\nimport io.circe.*\nimport io.circe.jawn.{parse => jawnParse}\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String,\n    tokenUnsigned: String,\n    tokenEmpty: String,\n    headerJson: Json\n) extends JsonDataEntryTrait[Json]\n\ntrait CirceFixture extends JsonCommonFixture[Json] {\n  def parseString(value: String): Json = jawnParse(value).toOption.get\n\n  val claimJson = parseString(claim)\n  val headerEmptyJson = parseString(headerEmpty)\n\n  def mapData(data: DataEntryBase): JsonDataEntry = JsonDataEntry(\n    algo = data.algo,\n    header = data.header,\n    headerClass = data.headerClass,\n    header64 = data.header64,\n    signature = data.signature,\n    token = data.token,\n    tokenUnsigned = data.tokenUnsigned,\n    tokenEmpty = data.tokenEmpty,\n    headerJson = parseString(data.header)\n  )\n}\n"
  },
  {
    "path": "json/circe/jvm/src/test/scala/JwtCirceSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport io.circe.*\n\nclass JwtCirceSpec extends JwtJsonCommonSpec[Json] with CirceFixture {\n  override def jwtJsonCommon(clock: Clock): JwtCirce = JwtCirce(clock)\n}\n"
  },
  {
    "path": "json/circe/shared/src/main/scala/JwtCirce.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport io.circe.*\nimport io.circe.jawn.{parse => jawnParse}\nimport io.circe.syntax.*\n\n/** Implementation of `JwtCore` using `Json` from Circe.\n  */\ntrait JwtCirceParser[H, C] extends JwtJsonCommon[Json, H, C] {\n  protected def parse(value: String): Json = jawnParse(value).fold(throw _, identity)\n  protected def stringify(value: Json): String = value.asJson.noSpaces\n  protected def getAlgorithm(header: Json): Option[JwtAlgorithm] = getAlg(header.hcursor)\n\n  protected def getAlg(cursor: HCursor): Option[JwtAlgorithm] = cursor\n    .get[String](\"alg\")\n    .toOption\n    .filterNot(_ == \"none\")\n    .map(JwtAlgorithm.fromString)\n}\n\nobject JwtCirce extends JwtCirce(Clock.systemUTC) {\n  def apply(clock: Clock): JwtCirce = new JwtCirce(clock)\n\n}\n\nclass JwtCirce(override val clock: Clock) extends JwtCirceParser[JwtHeader, JwtClaim] {\n  def parseHeader(header: String): JwtHeader = parseHeaderHelp(header)\n\n  def parseClaim(claim: String): JwtClaim = parseClaimHelp(claim)\n\n  private def parseHeaderHelp(header: String): JwtHeader = {\n    val cursor = parse(header).hcursor\n    JwtHeader(\n      algorithm = getAlg(cursor),\n      typ = cursor.get[String](\"typ\").toOption,\n      contentType = cursor.get[String](\"cty\").toOption,\n      keyId = cursor.get[String](\"kid\").toOption\n    )\n  }\n\n  private def parseClaimHelp(claim: String): JwtClaim = {\n    val cursor = parse(claim).hcursor\n    val contentCursor = List(\"iss\", \"sub\", \"aud\", \"exp\", \"nbf\", \"iat\", \"jti\").foldLeft(cursor) {\n      (cursor, field) =>\n        cursor.downField(field).delete.success match {\n          case Some(newCursor) => newCursor\n          case None            => cursor\n        }\n    }\n    JwtClaim(\n      content = contentCursor.top.asJson.noSpaces,\n      issuer = cursor.get[String](\"iss\").toOption,\n      subject = cursor.get[String](\"sub\").toOption,\n      audience = cursor\n        .get[Set[String]](\"aud\")\n        .toOption\n        .orElse(cursor.get[String](\"aud\").map(s => Set(s)).toOption),\n      expiration = cursor.get[Long](\"exp\").toOption,\n      notBefore = cursor.get[Long](\"nbf\").toOption,\n      issuedAt = cursor.get[Long](\"iat\").toOption,\n      jwtId = cursor.get[String](\"jti\").toOption\n    )\n  }\n\n}\n"
  },
  {
    "path": "json/common/src/main/scala/JwtJsonCommon.scala",
    "content": "package pdi.jwt\n\nimport java.security.{Key, PrivateKey, PublicKey}\nimport javax.crypto.SecretKey\nimport scala.util.Try\n\nimport pdi.jwt.algorithms.*\nimport pdi.jwt.exceptions.{\n  JwtEmptyAlgorithmException,\n  JwtNonEmptyAlgorithmException,\n  JwtValidationException\n}\n\ntrait JwtJsonCommon[J, H, C] extends JwtCore[H, C] {\n  protected def parse(value: String): J\n  protected def stringify(value: J): String\n  protected def getAlgorithm(header: J): Option[JwtAlgorithm]\n\n  protected def extractAlgorithm(header: JwtHeader): Option[JwtAlgorithm] = header.algorithm\n  protected def extractExpiration(claim: JwtClaim): Option[Long] = claim.expiration\n  protected def extractNotBefore(claim: JwtClaim): Option[Long] = claim.notBefore\n\n  def encode(header: J, claim: J): String = getAlgorithm(header) match {\n    case None => encode(stringify(header), stringify(claim))\n    case _    => throw new JwtNonEmptyAlgorithmException()\n  }\n\n  def encode(header: J, claim: J, key: String): String = getAlgorithm(header) match {\n    case Some(algo: JwtAlgorithm) => encode(stringify(header), stringify(claim), key, algo)\n    case _                        => throw new JwtEmptyAlgorithmException()\n  }\n\n  def encode(header: J, claim: J, key: Key): String = (getAlgorithm(header), key) match {\n    case (Some(algo: JwtHmacAlgorithm), k: SecretKey) =>\n      encode(stringify(header), stringify(claim), k, algo)\n    case (Some(algo: JwtAsymmetricAlgorithm), k: PrivateKey) =>\n      encode(stringify(header), stringify(claim), k, algo)\n    case _ =>\n      throw new JwtValidationException(\n        \"The key type doesn't match the algorithm type. It's either a SecretKey and a HMAC algorithm or a PrivateKey and a RSA or ECDSA algorithm. And an algorithm is required of course.\"\n      )\n  }\n\n  def encode(claim: J): String =\n    encode(stringify(claim))\n\n  def encode(claim: J, key: String, algorithm: JwtAlgorithm): String =\n    encode(stringify(claim), key, algorithm)\n\n  def encode(claim: J, key: SecretKey, algorithm: JwtHmacAlgorithm): String =\n    encode(stringify(claim), key, algorithm)\n\n  def encode(claim: J, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =\n    encode(stringify(claim), key, algorithm)\n\n  def decodeJsonAll(token: String, options: JwtOptions): Try[(J, J, String)] =\n    decodeRawAll(token, options).map { tuple => (parse(tuple._1), parse(tuple._2), tuple._3) }\n\n  def decodeJsonAll(token: String): Try[(J, J, String)] =\n    decodeJsonAll(token, JwtOptions.DEFAULT)\n\n  def decodeJsonAll(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[(J, J, String)] =\n    decodeRawAll(token, key, algorithms, options).map { tuple =>\n      (parse(tuple._1), parse(tuple._2), tuple._3)\n    }\n\n  def decodeJsonAll(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Try[(J, J, String)] =\n    decodeJsonAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJsonAll(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[(J, J, String)] =\n    decodeRawAll(token, key, algorithms, options).map { tuple =>\n      (parse(tuple._1), parse(tuple._2), tuple._3)\n    }\n\n  def decodeJsonAll(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm]\n  ): Try[(J, J, String)] =\n    decodeJsonAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJsonAll(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[(J, J, String)] =\n    decodeRawAll(token, key, algorithms, options).map { tuple =>\n      (parse(tuple._1), parse(tuple._2), tuple._3)\n    }\n\n  def decodeJsonAll(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm]\n  ): Try[(J, J, String)] =\n    decodeJsonAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJsonAll(token: String, key: SecretKey, options: JwtOptions): Try[(J, J, String)] =\n    decodeJsonAll(token, key, JwtAlgorithm.allHmac(), options)\n\n  def decodeJsonAll(token: String, key: SecretKey): Try[(J, J, String)] =\n    decodeJsonAll(token, key, JwtOptions.DEFAULT)\n\n  def decodeJsonAll(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[(J, J, String)] =\n    decodeRawAll(token, key, algorithms, options).map { tuple =>\n      (parse(tuple._1), parse(tuple._2), tuple._3)\n    }\n\n  def decodeJsonAll(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm]\n  ): Try[(J, J, String)] =\n    decodeJsonAll(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJsonAll(token: String, key: PublicKey, options: JwtOptions): Try[(J, J, String)] =\n    decodeJsonAll(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def decodeJsonAll(token: String, key: PublicKey): Try[(J, J, String)] =\n    decodeJsonAll(token, key, JwtOptions.DEFAULT)\n\n  def decodeJson(token: String, options: JwtOptions): Try[J] =\n    decodeJsonAll(token, options).map(_._2)\n\n  def decodeJson(token: String): Try[J] =\n    decodeJson(token, JwtOptions.DEFAULT)\n\n  def decodeJson(\n      token: String,\n      key: String,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[J] =\n    decodeJsonAll(token, key, algorithms, options).map(_._2)\n\n  def decodeJson(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[J] =\n    decodeJson(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJson(\n      token: String,\n      key: String,\n      algorithms: => Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[J] =\n    decodeJsonAll(token, key, algorithms, options).map(_._2)\n\n  def decodeJson(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[J] =\n    decodeJson(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJson(\n      token: String,\n      key: SecretKey,\n      algorithms: Seq[JwtHmacAlgorithm],\n      options: JwtOptions\n  ): Try[J] =\n    decodeJsonAll(token, key, algorithms, options).map(_._2)\n\n  def decodeJson(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[J] =\n    decodeJson(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJson(token: String, key: SecretKey, options: JwtOptions): Try[J] =\n    decodeJson(token, key, JwtAlgorithm.allHmac(), options)\n\n  def decodeJson(token: String, key: SecretKey): Try[J] =\n    decodeJson(token, key, JwtOptions.DEFAULT)\n\n  def decodeJson(\n      token: String,\n      key: PublicKey,\n      algorithms: Seq[JwtAsymmetricAlgorithm],\n      options: JwtOptions\n  ): Try[J] =\n    decodeJsonAll(token, key, algorithms, options).map(_._2)\n\n  def decodeJson(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[J] =\n    decodeJson(token, key, algorithms, JwtOptions.DEFAULT)\n\n  def decodeJson(token: String, key: PublicKey, options: JwtOptions): Try[J] =\n    decodeJson(token, key, JwtAlgorithm.allAsymmetric(), options)\n\n  def decodeJson(token: String, key: PublicKey): Try[J] =\n    decodeJson(token, key, JwtOptions.DEFAULT)\n}\n"
  },
  {
    "path": "json/common/src/test/scala/JsonCommonFixture.scala",
    "content": "package pdi.jwt\n\ntrait JsonDataEntryTrait[J] extends DataEntryBase {\n  def headerJson: J\n}\n\ntrait JsonCommonFixture[J] extends Fixture {\n  def claimJson: J\n  def headerEmptyJson: J\n  def mapData(data: DataEntryBase): JsonDataEntryTrait[J]\n\n  val dataJson = data.map(mapData)\n  val dataRSAJson = dataRSA.map(mapData)\n  val dataECDSAJson = dataECDSA.map(mapData)\n}\n"
  },
  {
    "path": "json/common/src/test/scala/JwtJsonCommonSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.util.Success\n\nabstract class JwtJsonCommonSpec[J] extends munit.FunSuite with JsonCommonFixture[J] {\n  import JwtJsonCommonSpec.JwtJsonUnderTest\n\n  protected def jwtJsonCommon(clock: Clock): JwtJsonUnderTest[J]\n\n  protected def testEncoding: Boolean = true\n\n  lazy val defaultJwt: JwtJsonUnderTest[J] = jwtJsonCommon(Clock.systemUTC)\n  lazy val afterExpirationJwt: JwtJsonUnderTest[J] = jwtJsonCommon(afterExpirationClock)\n  lazy val validTimeJwt: JwtJsonUnderTest[J] = jwtJsonCommon(validTimeClock)\n\n  def battleTestEncode(d: JsonDataEntryTrait[J], key: String, jwt: JwtJsonUnderTest[J]) = {\n    assertEquals(d.token, jwt.encode(d.headerJson, claimJson, key), s\"${d.algo.fullName} key\")\n    assertEquals(\n      d.tokenEmpty,\n      jwt.encode(claimJson),\n      s\"${d.algo.fullName} No header, no key, no algo\"\n    )\n    assertEquals(\n      d.token,\n      jwt.encode(claimJson, key, d.algo),\n      s\"${d.algo.fullName} No header, key, algo\"\n    )\n  }\n\n  if (testEncoding) {\n    test(\"should encode with no algorithm\") {\n      assertEquals(tokenEmpty, { defaultJwt.encode(headerEmptyJson, claimJson) }, \"Unsigned key\")\n    }\n\n    test(\"should encode HMAC\") {\n      dataJson.foreach { d => battleTestEncode(d, secretKey, defaultJwt) }\n    }\n\n    test(\"should encode RSA\") {\n      dataRSAJson.foreach { d => battleTestEncode(d, privateKeyRSA, defaultJwt) }\n    }\n  }\n\n  test(\"should decodeJsonAll\") {\n    dataJson.foreach { d =>\n      val success = Success((d.headerJson, claimJson, d.signature))\n      assertEquals(\n        validTimeJwt.decodeJsonAll(d.token, secretKey, JwtAlgorithm.allHmac()),\n        success,\n        d.algo.fullName\n      )\n      assertEquals(\n        validTimeJwt.decodeJsonAll(d.token, secretKeyOf(d.algo)),\n        success,\n        d.algo.fullName\n      )\n    }\n\n    dataRSAJson.foreach { d =>\n      val success = Success((d.headerJson, claimJson, d.signature))\n      assertEquals(\n        validTimeJwt.decodeJsonAll(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        success,\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should decodeJson\") {\n    val success = Success(claimJson)\n\n    dataJson.foreach { d =>\n      assertEquals(\n        validTimeJwt.decodeJson(d.token, secretKey, JwtAlgorithm.allHmac()),\n        success,\n        d.algo.fullName\n      )\n      assertEquals(validTimeJwt.decodeJson(d.token, secretKeyOf(d.algo)), success, d.algo.fullName)\n    }\n\n    dataRSAJson.foreach { d =>\n      assertEquals(\n        validTimeJwt.decodeJson(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        success,\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should decodeAll\") {\n    dataJson.foreach { d =>\n      val success = Success((d.headerClass, claimClass, d.signature))\n      assertEquals(\n        validTimeJwt.decodeAll(d.token, secretKey, JwtAlgorithm.allHmac()),\n        success,\n        d.algo.fullName\n      )\n      assertEquals(validTimeJwt.decodeAll(d.token, secretKeyOf(d.algo)), success, d.algo.fullName)\n    }\n\n    dataRSAJson.foreach { d =>\n      val success = Success((d.headerClass, claimClass, d.signature))\n      assertEquals(\n        validTimeJwt.decodeAll(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),\n        success,\n        d.algo.fullName\n      )\n    }\n  }\n\n  test(\"should fail to decodeJsonAll and decodeJson when now is after expiration date\") {\n    dataJson.foreach { d =>\n      assert(afterExpirationJwt.decodeJsonAll(d.token, secretKey, JwtAlgorithm.allHmac()).isFailure)\n\n      assert(afterExpirationJwt.decodeJson(d.token, secretKey, JwtAlgorithm.allHmac()).isFailure)\n\n      assert(afterExpirationJwt.decodeAll(d.token, secretKey, JwtAlgorithm.allHmac()).isFailure)\n    }\n\n    dataRSAJson.foreach { d =>\n      assert(\n        afterExpirationJwt.decodeJsonAll(d.token, publicKeyRSA, JwtAlgorithm.allRSA()).isFailure\n      )\n\n      assert(afterExpirationJwt.decodeJson(d.token, publicKeyRSA, JwtAlgorithm.allRSA()).isFailure)\n\n      assert(afterExpirationJwt.decodeAll(d.token, publicKeyRSA, JwtAlgorithm.allRSA()).isFailure)\n    }\n  }\n\n  test(\n    \"should success to decodeJsonAll and decodeJson when now is after expiration date with options\"\n  ) {\n    val options = JwtOptions(expiration = false)\n\n    dataJson.foreach { d =>\n      assert(\n        afterExpirationJwt\n          .decodeJsonAll(d.token, secretKey, JwtAlgorithm.allHmac(), options)\n          .isSuccess\n      )\n\n      assert(\n        afterExpirationJwt\n          .decodeJson(d.token, secretKey, JwtAlgorithm.allHmac(), options)\n          .isSuccess\n      )\n\n      assert(\n        afterExpirationJwt\n          .decodeAll(d.token, secretKey, JwtAlgorithm.allHmac(), options)\n          .isSuccess\n      )\n    }\n\n    dataRSAJson.foreach { d =>\n      assert(\n        afterExpirationJwt\n          .decodeJsonAll(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)\n          .isSuccess\n      )\n\n      assert(\n        afterExpirationJwt\n          .decodeJson(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)\n          .isSuccess\n      )\n\n      assert(\n        afterExpirationJwt\n          .decodeAll(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)\n          .isSuccess\n      )\n    }\n  }\n}\n\nobject JwtJsonCommonSpec {\n  type JwtJsonUnderTest[J] = JwtJsonCommon[J, JwtHeader, JwtClaim]\n}\n"
  },
  {
    "path": "json/json4s-common/src/main/scala/JwtJson4sCommon.scala",
    "content": "package pdi.jwt\n\nimport org.json4s.*\nimport pdi.jwt.exceptions.{\n  JwtNonNumberException,\n  JwtNonStringException,\n  JwtNonStringSetOrStringException\n}\n\ntrait JwtJson4sCommon[H, C] extends JwtJsonCommon[JObject, H, C] {\n  protected def getAlgorithm(header: JObject): Option[JwtAlgorithm] = header \\ \"alg\" match {\n    case JString(\"none\") => None\n    case JString(algo)   => Option(JwtAlgorithm.fromString(algo))\n    case JNull           => None\n    case JNothing        => None\n    case _               => throw new JwtNonStringException(\"alg\")\n  }\n\n  def readClaim(json: JValue): JwtClaim = json match {\n    case value: JObject =>\n      JwtClaim.apply(\n        issuer = extractString(value, \"iss\"),\n        subject = extractString(value, \"sub\"),\n        audience = extractStringSetOrString(value, \"aud\"),\n        expiration = extractLong(value, \"exp\"),\n        notBefore = extractLong(value, \"nbf\"),\n        issuedAt = extractLong(value, \"iat\"),\n        jwtId = extractString(value, \"jti\"),\n        content = stringify(filterClaimFields(value))\n      )\n    case _ => throw new RuntimeException(\"Expected a JObject\")\n  }\n\n  def writeClaim(claim: JwtClaim): JValue = parse(claim.toJson)\n\n  def readHeader(json: JValue): JwtHeader = json match {\n    case value: JObject =>\n      JwtHeader.apply(\n        algorithm = extractString(value, \"alg\").flatMap(JwtAlgorithm.optionFromString),\n        typ = extractString(value, \"typ\"),\n        contentType = extractString(value, \"cty\"),\n        keyId = extractString(value, \"kid\")\n      )\n    case _ => throw new RuntimeException(\"Expected a JObject\")\n  }\n\n  def writeHeader(header: JwtHeader): JValue = parse(header.toJson)\n\n  protected def extractString(json: JObject, fieldName: String): Option[String] =\n    (json \\ fieldName) match {\n      case JString(value) => Option(value)\n      case JNull          => None\n      case JNothing       => None\n      case _              => throw new JwtNonStringException(fieldName)\n    }\n\n  protected def extractStringSetOrString(json: JObject, fieldName: String): Option[Set[String]] =\n    (json \\ fieldName) match {\n      case JString(value) => Option(Set(value))\n      case JArray(_)      =>\n        try {\n          (json \\ fieldName) match {\n            case JArray(values) => Some(values.map(_.asInstanceOf[JString].s).toSet)\n            case _              => None\n          }\n        } catch {\n          case _: MappingException => throw new JwtNonStringSetOrStringException(fieldName)\n        }\n      case JNull    => None\n      case JNothing => None\n      case _        => throw new JwtNonStringSetOrStringException(fieldName)\n    }\n\n  protected def extractLong(json: JObject, fieldName: String): Option[Long] =\n    (json \\ fieldName) match {\n      case JInt(value) => Option(value.toLong)\n      case JNull       => None\n      case JNothing    => None\n      case _           => throw new JwtNonNumberException(fieldName)\n    }\n\n  protected def filterClaimFields(json: JObject): JObject = json.removeField {\n    case JField(\"iss\", _) => true\n    case JField(\"sub\", _) => true\n    case JField(\"aud\", _) => true\n    case JField(\"exp\", _) => true\n    case JField(\"nbf\", _) => true\n    case JField(\"iat\", _) => true\n    case JField(\"jti\", _) => true\n    case _                => false\n  } match {\n    case res: JObject => res\n    case _            =>\n      throw new RuntimeException(\n        \"How did we manage to go from JObject to something else by just removing fields?\"\n      )\n  }\n}\n"
  },
  {
    "path": "json/json4s-common/src/test/scala/Json4sCommonFixture.scala",
    "content": "package pdi.jwt\n\nimport org.json4s.*\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String,\n    tokenUnsigned: String,\n    tokenEmpty: String,\n    headerJson: JObject\n) extends JsonDataEntryTrait[JObject]\n\ntrait Json4sCommonFixture extends JsonCommonFixture[JObject] {\n  def parseString(value: String): JValue\n\n  val claimJson = parseString(claim) match {\n    case j: JObject => j\n    case _          => throw new RuntimeException(\"I want a JObject!\")\n  }\n\n  val headerEmptyJson = parseString(headerEmpty) match {\n    case j: JObject => j\n    case _          => throw new RuntimeException(\"I want a JObject!\")\n  }\n\n  def mapData(data: DataEntryBase): JsonDataEntry = JsonDataEntry(\n    algo = data.algo,\n    header = data.header,\n    headerClass = data.headerClass,\n    header64 = data.header64,\n    signature = data.signature,\n    token = data.token,\n    tokenUnsigned = data.tokenUnsigned,\n    tokenEmpty = data.tokenEmpty,\n    headerJson = parseString(data.header) match {\n      case j: JObject => j\n      case _          => throw new RuntimeException(\"I want a JObject!\")\n    }\n  )\n}\n"
  },
  {
    "path": "json/json4s-jackson/src/main/scala/JwtJson4sImplicits.scala",
    "content": "package pdi.jwt\n\nimport org.json4s.JValue\n\ntrait JwtJson4sImplicits {\n  implicit class RichJwtClaim(claim: JwtClaim) {\n    def toJValue(): JValue = JwtJson4s.writeClaim(claim)\n  }\n\n  implicit class RichJwtHeader(header: JwtHeader) {\n    def toJValue(): JValue = JwtJson4s.writeHeader(header)\n  }\n}\n"
  },
  {
    "path": "json/json4s-jackson/src/main/scala/JwtJson4sJackson.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.jackson.JsonMethods.*\nimport org.json4s.jackson.JsonMethods.{parse => jparse}\n\n/** Implementation of `JwtCore` using `JObject` from Json4s Jackson.\n  *\n  * To see a full list of samples, check the\n  * [[https://jwt-scala.github.io/jwt-scala/jwt-json4s.html online documentation]].\n  */\ntrait JwtJson4sParser[H, C] extends JwtJson4sCommon[H, C] with JwtJson4sImplicits {\n  protected def parse(value: String): JObject = jparse(value) match {\n    case res: JObject => res\n    case _            => throw new RuntimeException(s\"Couldn't parse [$value] to a JObject\")\n  }\n\n  protected def stringify(value: JObject): String = compact(render(value))\n}\n\nobject JwtJson4s extends JwtJson4s(Clock.systemUTC) {\n  def apply(clock: Clock): JwtJson4s = new JwtJson4s(clock)\n}\n\nclass JwtJson4s(override val clock: Clock) extends JwtJson4sParser[JwtHeader, JwtClaim] {\n  def parseHeader(header: String): JwtHeader = readHeader(parse(header))\n  def parseClaim(claim: String): JwtClaim = readClaim(parse(claim))\n}\n"
  },
  {
    "path": "json/json4s-jackson/src/test/scala/Json4sJacksonFixture.scala",
    "content": "package pdi.jwt\n\nimport org.json4s.*\nimport org.json4s.jackson.JsonMethods.*\n\ntrait Json4sJacksonFixture extends Json4sCommonFixture {\n  def parseString(value: String): JValue = parse(value)\n}\n"
  },
  {
    "path": "json/json4s-jackson/src/test/scala/Json4sJacksonSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.JsonDSL.*\n\nclass JwtJson4sJacksonSpec extends JwtJsonCommonSpec[JObject] with Json4sJacksonFixture {\n  import pdi.jwt.JwtJson4s.*\n\n  override def jwtJsonCommon(clock: Clock): JwtJson4s = JwtJson4s(clock)\n\n  test(\"JwtJson should implicitly convert to JValue\") {\n    assertEquals(\n      JwtClaim()\n        .by(\"me\")\n        .to(\"you\")\n        .about(\"something\")\n        .issuedAt(10)\n        .startsAt(10)\n        .expiresAt(15)\n        .toJValue(),\n      (\n        (\"iss\" -> \"me\") ~\n          (\"aud\" -> Option(\"you\")) ~\n          (\"sub\" -> \"something\") ~\n          (\"exp\" -> 15) ~\n          (\"nbf\" -> 10) ~\n          (\"iat\" -> 10)\n      ),\n      \"Claim\"\n    )\n\n    assertEquals(\n      JwtHeader(JwtAlgorithm.HS256).toJValue(),\n      ((\"typ\" -> \"JWT\") ~ (\"alg\" -> \"HS256\")),\n      \"Claim\"\n    )\n  }\n\n  test(\"JwtJson should implicitly convert to JValue when audience is many\") {\n    assertEquals(\n      JwtClaim(audience = Some(Set(\"you\", \"another\")))\n        .by(\"me\")\n        .about(\"something\")\n        .issuedAt(10)\n        .startsAt(10)\n        .expiresAt(15)\n        .toJValue(),\n      (\n        (\"iss\" -> \"me\") ~\n          (\"aud\" -> Set(\"you\", \"another\")) ~\n          (\"sub\" -> \"something\") ~\n          (\"exp\" -> 15) ~\n          (\"nbf\" -> 10) ~\n          (\"iat\" -> 10)\n      ),\n      \"Claim\"\n    )\n  }\n}\n"
  },
  {
    "path": "json/json4s-native/src/main/scala/JwtJson4sImplicits.scala",
    "content": "package pdi.jwt\n\nimport org.json4s.JValue\n\ntrait JwtJson4sImplicits {\n  implicit class RichJwtClaim(claim: JwtClaim) {\n    def toJValue(): JValue = JwtJson4s.writeClaim(claim)\n  }\n\n  implicit class RichJwtHeader(header: JwtHeader) {\n    def toJValue(): JValue = JwtJson4s.writeHeader(header)\n  }\n}\n"
  },
  {
    "path": "json/json4s-native/src/main/scala/JwtJson4sNative.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.native.JsonMethods.*\nimport org.json4s.native.JsonMethods.{parse => jparse}\n\n/** Implementation of `JwtCore` using `JObject` from Json4s Native.\n  *\n  * To see a full list of samples, check the\n  * [[https://jwt-scala.github.io/jwt-scala/jwt-json4s.html online documentation]].\n  */\ntrait JwtJson4sParser[H, C] extends JwtJson4sCommon[H, C] with JwtJson4sImplicits {\n  protected def parse(value: String): JObject = jparse(value) match {\n    case res: JObject => res\n    case _            => throw new RuntimeException(s\"Couldn't parse [$value] to a JObject\")\n  }\n\n  protected def stringify(value: JObject): String = compact(render(value))\n}\n\nobject JwtJson4s extends JwtJson4s(Clock.systemUTC) {\n  def apply(clock: Clock): JwtJson4s = new JwtJson4s(clock)\n}\n\nclass JwtJson4s(override val clock: Clock) extends JwtJson4sParser[JwtHeader, JwtClaim] {\n  def parseHeader(header: String): JwtHeader = readHeader(parse(header))\n  def parseClaim(claim: String): JwtClaim = readClaim(parse(claim))\n}\n"
  },
  {
    "path": "json/json4s-native/src/test/scala/Json4sNativeFixture.scala",
    "content": "package pdi.jwt\n\nimport org.json4s.*\nimport org.json4s.native.JsonMethods.*\n\ntrait Json4sNativeFixture extends Json4sCommonFixture {\n  def parseString(value: String): JValue = parse(value)\n}\n"
  },
  {
    "path": "json/json4s-native/src/test/scala/Json4sNativeSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.JsonDSL.*\n\nclass JwtJson4sNativeSpec extends JwtJsonCommonSpec[JObject] with Json4sNativeFixture {\n  import pdi.jwt.JwtJson4s.*\n\n  override def jwtJsonCommon(clock: Clock): JwtJson4s = JwtJson4s(clock)\n\n  test(\"should implicitly convert to JValue\") {\n    assertEquals(\n      JwtClaim()\n        .by(\"me\")\n        .to(\"you\")\n        .about(\"something\")\n        .issuedAt(10)\n        .startsAt(10)\n        .expiresAt(15)\n        .toJValue(),\n      (\n        (\"iss\" -> \"me\") ~\n          (\"aud\" -> Option(\"you\")) ~\n          (\"sub\" -> \"something\") ~\n          (\"exp\" -> 15) ~\n          (\"nbf\" -> 10) ~\n          (\"iat\" -> 10)\n      ),\n      \"Claim\"\n    )\n\n    assertEquals(\n      JwtHeader(JwtAlgorithm.HS256).toJValue(),\n      ((\"typ\" -> \"JWT\") ~ (\"alg\" -> \"HS256\")),\n      \"Claim\"\n    )\n  }\n\n  test(\"should implicitly convert to JValue when audience is many\") {\n    assertEquals(\n      JwtClaim(audience = Some(Set(\"you\", \"another\")))\n        .by(\"me\")\n        .about(\"something\")\n        .issuedAt(10)\n        .startsAt(10)\n        .expiresAt(15)\n        .toJValue(),\n      (\n        (\"iss\" -> \"me\") ~\n          (\"aud\" -> Set(\"you\", \"another\")) ~\n          (\"sub\" -> \"something\") ~\n          (\"exp\" -> 15) ~\n          (\"nbf\" -> 10) ~\n          (\"iat\" -> 10)\n      ),\n      \"Claim\"\n    )\n  }\n}\n"
  },
  {
    "path": "json/play-json/src/main/scala/JwtJson.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport pdi.jwt.exceptions.{JwtNonNumberException, JwtNonStringException, JwtValidationException}\nimport play.api.libs.json.*\n\n/** Implementation of `JwtCore` using `JsObject` from Play JSON.\n  *\n  * To see a full list of samples, check the\n  * [[https://jwt-scala.github.io/jwt-scala/jwt-play-json.html online documentation]].\n  */\ntrait JwtJsonParser[H, C] extends JwtJsonCommon[JsObject, H, C] with JwtJsonImplicits {\n  protected def parse(value: String): JsObject = Json.parse(value).as[JsObject]\n\n  protected def stringify(value: JsObject): String = Json.stringify(value)\n\n  protected def getAlgorithm(header: JsObject): Option[JwtAlgorithm] =\n    (header \\ \"alg\").toOption.flatMap {\n      case JsString(\"none\") => None\n      case JsString(algo)   => Option(JwtAlgorithm.fromString(algo))\n      case JsNull           => None\n      case _                => throw new JwtNonStringException(\"alg\")\n    }\n\n}\n\nobject JwtJson extends JwtJson(Clock.systemUTC) {\n  def apply(clock: Clock): JwtJson = new JwtJson(clock)\n\n  // Play Json returns a useless exception on JsResult.get. We want to give more details about what's wrong in the exception.\n  private[jwt] def jsErrorToException(error: JsError): Exception = error.errors.headOption\n    .map { case (jsPath, errors) =>\n      errors.headOption.map(_.message) match {\n        case Some(\"error.expected.string\") => new JwtNonStringException(jsPath.toString)\n        case Some(\"error.expected.number\") => new JwtNonNumberException(jsPath.toString)\n        case _                             => new JwtValidationException(s\"Failed to parse: $error\")\n      }\n    }\n    .getOrElse(new JwtValidationException(s\"Failed to parse: $error\"))\n}\n\nclass JwtJson private (override val clock: Clock) extends JwtJsonParser[JwtHeader, JwtClaim] {\n  def parseHeader(header: String): JwtHeader = jwtPlayJsonHeaderReader\n    .reads(Json.parse(header))\n    .recoverTotal { e => throw JwtJson.jsErrorToException(e) }\n\n  def parseClaim(claim: String): JwtClaim = jwtPlayJsonClaimReader\n    .reads(Json.parse(claim))\n    .recoverTotal { e => throw JwtJson.jsErrorToException(e) }\n}\n"
  },
  {
    "path": "json/play-json/src/main/scala/JwtJsonImplicits.scala",
    "content": "package pdi.jwt\n\nimport pdi.jwt.exceptions.{\n  JwtNonNumberException,\n  JwtNonStringException,\n  JwtNonStringSetOrStringException,\n  JwtNonSupportedAlgorithm\n}\nimport play.api.libs.json.*\n\ntrait JwtJsonImplicits {\n  private def extractString(json: JsObject, fieldName: String): Option[String] =\n    (json \\ fieldName).toOption.flatMap {\n      case JsString(value) => Option(value)\n      case JsNull          => None\n      case _               => throw new JwtNonStringException(fieldName)\n    }\n\n  private def extractStringSetOrString(json: JsObject, fieldName: String): Option[Set[String]] =\n    (json \\ fieldName).validateOpt[Set[String]] match {\n      case JsSuccess(set, _) => set\n      case JsError(_)        =>\n        (json \\ fieldName).validateOpt[String] match {\n          case JsSuccess(string, _) => string.map(s => Set(s))\n          case JsError(_)           => throw new JwtNonStringSetOrStringException(fieldName)\n        }\n    }\n\n  private def extractLong(json: JsObject, fieldName: String): Option[Long] =\n    (json \\ fieldName).toOption.flatMap {\n      case JsNumber(value) => Option(value.toLong)\n      case JsNull          => None\n      case _               => throw new JwtNonNumberException(fieldName)\n    }\n\n  private def keyToPath(key: String): JsPath = new JsPath(List(new KeyPathNode(key)))\n\n  implicit val jwtPlayJsonClaimReader: Reads[JwtClaim] = Reads { (json: JsValue) =>\n    json match {\n      case value: JsObject =>\n        try {\n          JsSuccess(\n            JwtClaim.apply(\n              issuer = extractString(value, \"iss\"),\n              subject = extractString(value, \"sub\"),\n              audience = extractStringSetOrString(value, \"aud\"),\n              expiration = extractLong(value, \"exp\"),\n              notBefore = extractLong(value, \"nbf\"),\n              issuedAt = extractLong(value, \"iat\"),\n              jwtId = extractString(value, \"jti\"),\n              content =\n                Json.stringify(value - \"iss\" - \"sub\" - \"aud\" - \"exp\" - \"nbf\" - \"iat\" - \"jti\")\n            )\n          )\n        } catch {\n          case e: JwtNonStringException => JsError(keyToPath(e.key), \"error.expected.string\")\n          case e: JwtNonNumberException => JsError(keyToPath(e.key), \"error.expected.number\")\n          case e: JwtNonStringSetOrStringException =>\n            JsError(keyToPath(e.key), \"error.expected.array\")\n        }\n      case _ => JsError(\"error.expected.jsobject\")\n    }\n  }\n\n  implicit val jwtPlayJsonClaimWriter: Writes[JwtClaim] = Writes { (claim: JwtClaim) =>\n    Json.parse(claim.toJson)\n  }\n\n  implicit val jwtPlayJsonHeaderReader: Reads[JwtHeader] = Reads { (json: JsValue) =>\n    json match {\n      case value: JsObject =>\n        try {\n          JsSuccess(\n            JwtHeader.apply(\n              algorithm = extractString(value, \"alg\").flatMap(JwtAlgorithm.optionFromString),\n              typ = extractString(value, \"typ\"),\n              contentType = extractString(value, \"cty\"),\n              keyId = extractString(value, \"kid\")\n            )\n          )\n        } catch {\n          case e: JwtNonStringException    => JsError(keyToPath(e.key), \"error.expected.string\")\n          case _: JwtNonSupportedAlgorithm => JsError(keyToPath(\"alg\"), \"error.expected.algorithm\")\n        }\n      case _ => JsError(\"error.expected.jsobject\")\n    }\n  }\n\n  implicit val jwtPlayJsonHeaderWriter: Writes[JwtHeader] = Writes { (header: JwtHeader) =>\n    Json.parse(header.toJson)\n  }\n\n  implicit class RichJwtClaim(claim: JwtClaim) {\n    def toJsValue(): JsValue = jwtPlayJsonClaimWriter.writes(claim)\n\n    def +[A](a: A)(implicit writes: Writes[A]): JwtClaim = {\n      val s = Json.stringify(writes.writes(a))\n      claim + s\n    }\n\n    def withContent[A](a: A)(implicit writes: Writes[A]): JwtClaim = {\n      val s = Json.stringify(writes.writes(a))\n      claim.withContent(s)\n    }\n  }\n\n  implicit class RichJwtHeader(header: JwtHeader) {\n    def toJsValue(): JsValue = jwtPlayJsonHeaderWriter.writes(header)\n  }\n}\n"
  },
  {
    "path": "json/play-json/src/test/scala/JsonFixture.scala",
    "content": "package pdi.jwt\n\nimport play.api.libs.json.JsObject\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String,\n    tokenUnsigned: String,\n    tokenEmpty: String,\n    headerJson: JsObject\n) extends JsonDataEntryTrait[JsObject]\n\ntrait JsonFixture extends JsonCommonFixture[JsObject] {\n  import pdi.jwt.JwtJson.*\n\n  val claimJson = jwtPlayJsonClaimWriter.writes(claimClass).as[JsObject]\n  val headerEmptyJson = jwtPlayJsonHeaderWriter.writes(headerClassEmpty).as[JsObject]\n\n  def mapData(data: DataEntryBase): JsonDataEntry = JsonDataEntry(\n    algo = data.algo,\n    header = data.header,\n    headerClass = data.headerClass,\n    header64 = data.header64,\n    signature = data.signature,\n    token = data.token,\n    tokenUnsigned = data.tokenUnsigned,\n    tokenEmpty = data.tokenEmpty,\n    headerJson = jwtPlayJsonHeaderWriter.writes(data.headerClass).as[JsObject]\n  )\n}\n"
  },
  {
    "path": "json/play-json/src/test/scala/JwtJsonSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.util.Failure\nimport scala.util.Success\n\nimport pdi.jwt.exceptions.JwtNonNumberException\nimport pdi.jwt.exceptions.JwtNonStringException\nimport play.api.libs.json.{JsNumber, JsObject, JsString, Json, Writes}\n\nclass JwtJsonSpec extends JwtJsonCommonSpec[JsObject] with JsonFixture {\n\n  import pdi.jwt.JwtJson.*\n\n  override def jwtJsonCommon(clock: Clock): JwtJson = JwtJson(clock)\n\n  test(\"JwtJson should implicitly convert to JsValue\") {\n    assertEquals(\n      JwtClaim()\n        .by(\"me\")\n        .to(\"you\")\n        .about(\"something\")\n        .issuedAt(10)\n        .startsAt(10)\n        .expiresAt(15)\n        .toJsValue(),\n      Json.obj(\n        (\"iss\" -> \"me\"),\n        (\"aud\" -> Some(\"you\")),\n        (\"sub\" -> \"something\"),\n        (\"exp\" -> 15),\n        (\"nbf\" -> 10),\n        (\"iat\" -> 10)\n      ),\n      \"Claim\"\n    )\n\n    assertEquals(\n      JwtHeader(JwtAlgorithm.HS256).toJsValue(),\n      Json.obj(\n        (\"typ\" -> \"JWT\"),\n        (\"alg\" -> \"HS256\")\n      ),\n      \"Claim\"\n    )\n  }\n\n  test(\"JwtJson should implicitly convert to JsValue when audience is many\") {\n    assertEquals(\n      JwtClaim(audience = Some(Set(\"you\", \"another\")))\n        .by(\"me\")\n        .about(\"something\")\n        .issuedAt(10)\n        .startsAt(10)\n        .expiresAt(15)\n        .toJsValue(),\n      Json.obj(\n        (\"iss\" -> \"me\"),\n        (\"aud\" -> Set(\"you\", \"another\")),\n        (\"sub\" -> \"something\"),\n        (\"exp\" -> 15),\n        (\"nbf\" -> 10),\n        (\"iat\" -> 10)\n      ),\n      \"Claim\"\n    )\n  }\n\n  test(\"JwtJson should decode token with spaces\") {\n    val (_, claim, _) = defaultJwt.decodeJsonAll(tokenWithSpaces).get\n    val expiration = BigDecimal(\"32086368000\")\n    assertEquals((claim \\ \"nbf\").get, JsNumber(0))\n    assertEquals((claim \\ \"exp\").get, JsNumber(expiration))\n    assertEquals((claim \\ \"foo\").get, JsString(\"bar\"))\n  }\n\n  test(\"JwtJson should fail on an invalid issuer\") {\n    val header = \"\"\"{\"alg\": \"none\"}\"\"\"\n    val claim = \"\"\"{\"iss\": 42}\"\"\"\n    val token = s\"${JwtBase64.encodeString(header)}.${JwtBase64.encodeString(claim)}.\"\n    defaultJwt.decodeJsonAll(token) match {\n      case Failure(JwtNonStringException(\"/iss\")) => ()\n      case Failure(e)                             => fail(s\"Expected JwtNonStringException, got $e\")\n      case Success(_) => fail(s\"Expected JwtNonStringException, got success\")\n    }\n  }\n\n  test(\"JwtJson should fail on an invalid expiration\") {\n    val header = \"\"\"{\"alg\": \"none\"}\"\"\"\n    val claim = \"\"\"{\"iss\": \"me\", \"exp\": \"wrong\"}\"\"\"\n    val token = s\"${JwtBase64.encodeString(header)}.${JwtBase64.encodeString(claim)}.\"\n    defaultJwt.decodeJsonAll(token) match {\n      case Failure(JwtNonNumberException(\"/exp\")) => ()\n      case Failure(e)                             => fail(s\"Expected JwtNonNumberException, got $e\")\n      case Success(_) => fail(s\"Expected JwtNonStringException, got success\")\n    }\n  }\n\n  case class ContentClass(userId: String)\n  implicit val contentClassWrites: Writes[ContentClass] = Json.writes\n\n  test(\"JwtClaim should add content with Writes\") {\n\n    val content = ContentClass(userId = \"testId\")\n    val claim = JwtClaim().expiresAt(10) + content\n\n    assertEquals(\n      claim.toJsValue(),\n      Json.obj(\n        \"exp\" -> 10,\n        \"userId\" -> \"testId\"\n      )\n    )\n  }\n\n  test(\"JwtClaim should set content with Writes\") {\n\n    val content = ContentClass(userId = \"testId\")\n    val claim = JwtClaim().expiresAt(10).withContent(content)\n\n    assertEquals(\n      claim.toJsValue(),\n      Json.obj(\n        \"exp\" -> 10,\n        \"userId\" -> \"testId\"\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "json/upickle/jvm/src/test/scala/JwtUpickleFixture.scala",
    "content": "package pdi.jwt\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String,\n    tokenUnsigned: String,\n    tokenEmpty: String,\n    headerJson: ujson.Value\n) extends JsonDataEntryTrait[ujson.Value]\n\ntrait JwtUpickleFixture extends JsonCommonFixture[ujson.Value] {\n\n  val claimJson: ujson.Value = ujson.read(claim)\n\n  val headerEmptyJson: ujson.Value = ujson.read(headerEmpty)\n\n  def mapData(data: DataEntryBase): JsonDataEntry = JsonDataEntry(\n    algo = data.algo,\n    header = data.header,\n    headerClass = data.headerClass,\n    header64 = data.header64,\n    signature = data.signature,\n    token = data.token,\n    tokenUnsigned = data.tokenUnsigned,\n    tokenEmpty = data.tokenEmpty,\n    headerJson = ujson.read(data.header)\n  )\n\n}\n"
  },
  {
    "path": "json/upickle/jvm/src/test/scala/JwtUpickleSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nclass JwtUpickleSpec extends JwtJsonCommonSpec[ujson.Value] with JwtUpickleFixture {\n  def jwtJsonCommon(clock: Clock): JwtUpickle = JwtUpickle(clock)\n}\n"
  },
  {
    "path": "json/upickle/shared/src/main/scala/JwtUpickle.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport upickle.default.*\n\n/** Implementation of `JwtCore` using `Js.Value` from uPickle.\n  *\n  * To see a full list of samples, check the\n  * [[https://jwt-scala.github.io/jwt-scala/jwt-upickle.html online documentation]].\n  */\ntrait JwtUpickleParser[H, C] extends JwtJsonCommon[ujson.Value, H, C] with JwtUpickleImplicits {\n  protected def parse(value: String): ujson.Value = ujson.read(value)\n\n  protected def stringify(value: ujson.Value): String = ujson.write(value)\n\n  protected def getAlgorithm(header: ujson.Value): Option[JwtAlgorithm] = header match {\n    case obj: ujson.Obj =>\n      val fields = obj.value.toMap\n      fields.get(\"alg\").flatMap(alg => JwtAlgorithm.optionFromString(alg.str.toString()))\n\n    case _ => None\n  }\n}\n\nobject JwtUpickle extends JwtUpickle(Clock.systemUTC) {\n  def apply(clock: Clock): JwtUpickle = new JwtUpickle(clock)\n}\n\nclass JwtUpickle(override val clock: Clock) extends JwtUpickleParser[JwtHeader, JwtClaim] {\n  def parseHeader(header: String): JwtHeader = read[JwtHeader](header)\n  def parseClaim(claim: String): JwtClaim = read[JwtClaim](claim)\n}\n"
  },
  {
    "path": "json/upickle/shared/src/main/scala/JwtUpickleImplicits.scala",
    "content": "package pdi.jwt\n\nimport pdi.jwt.exceptions.JwtNonStringSetOrStringException\nimport upickle.default.*\n\ntrait JwtUpickleImplicits {\n  implicit val jwtUpickleHeaderReadWrite: ReadWriter[JwtHeader] =\n    readwriter[ujson.Value].bimap[JwtHeader](\n      header => ujson.read(header.toJson),\n      json =>\n        json match {\n          case obj: ujson.Obj =>\n            val fieldMap = obj.value.toMap\n            JwtHeader(\n              algorithm = fieldMap\n                .get(\"alg\")\n                .map(_.str.toString())\n                .flatMap(alg => JwtAlgorithm.optionFromString(alg)),\n              typ = fieldMap.get(\"typ\").map(_.str.toString()),\n              contentType = fieldMap.get(\"cty\").map(_.str.toString()),\n              keyId = fieldMap.get(\"kid\").map(_.str.toString())\n            )\n          case _ => throw new RuntimeException(\"Expected a ujson.Obj\")\n        }\n    )\n\n  implicit val jwtUpickleClaimReadWrite: ReadWriter[JwtClaim] =\n    readwriter[ujson.Value].bimap[JwtClaim](\n      claim => ujson.read(claim.toJson),\n      json =>\n        json match {\n          case obj: ujson.Obj =>\n            val fieldMap = obj.value.toMap\n            val content = fieldMap -- Seq(\"iss\", \"sub\", \"aud\", \"exp\", \"nbf\", \"iat\", \"jti\")\n            JwtClaim(\n              content = write(content),\n              issuer = fieldMap.get(\"iss\").map(_.str.toString()),\n              subject = fieldMap.get(\"sub\").map(_.str.toString()),\n              audience = fieldMap.get(\"aud\").map {\n                case ujson.Arr(arr) => arr.map(_.str.toString()).toSet\n                case ujson.Str(s)   => Set(s.toString)\n                case _              => throw new JwtNonStringSetOrStringException(\"aud\")\n              },\n              expiration = fieldMap.get(\"exp\").map(_.num.toLong),\n              notBefore = fieldMap.get(\"nbf\").map(_.num.toLong),\n              issuedAt = fieldMap.get(\"iat\").map(_.num.toLong),\n              jwtId = fieldMap.get(\"jti\").map(_.str.toString())\n            )\n          case _ => throw new RuntimeException(\"Expected a ujson.Obj\")\n        }\n    )\n}\n"
  },
  {
    "path": "json/zio-json/src/main/scala/JwtZIOJson.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport zio.json.*\nimport zio.json.ast.*\nimport zio.json.ast.JsonCursor.*\n\ntrait JwtZIOJsonParser[H, C] extends JwtJsonCommon[Json, H, C] {\n  override protected def parse(value: String): Json =\n    value.fromJson[Json].fold(err => throw new Exception(err), Predef.identity)\n\n  override protected def stringify(value: Json): String = value.toJson\n\n  override protected def getAlgorithm(header: Json): Option[JwtAlgorithm] =\n    header\n      .get(field(\"alg\").isString)\n      .toOption\n      .map(_.value)\n      .filterNot(_ == \"none\")\n      .map(JwtAlgorithm.fromString)\n}\n\nobject JwtZIOJson extends JwtZIOJson(Clock.systemUTC) {\n  def apply(clock: Clock): JwtZIOJson = new JwtZIOJson(clock)\n\n}\n\nclass JwtZIOJson(override val clock: Clock) extends JwtZIOJsonParser[JwtHeader, JwtClaim] {\n  def parseHeader(header: String): JwtHeader = parseHeaderHelp(header)\n\n  def parseClaim(claim: String): JwtClaim = parseClaimHelp(claim)\n\n  private def parseHeaderHelp(header: String): JwtHeader = {\n    val json = parse(header)\n    JwtHeader(\n      algorithm = getAlgorithm(json),\n      typ = json.get(field(\"typ\").isString).toOption.map(_.value),\n      contentType = json.get(field(\"cty\").isString).toOption.map(_.value),\n      keyId = json.get(field(\"kid\").isString).toOption.map(_.value)\n    )\n  }\n\n  private def parseClaimHelp(claim: String): JwtClaim = {\n    val json = parse(claim)\n    val keys = Set(\"iss\", \"sub\", \"aud\", \"exp\", \"nbf\", \"iat\", \"jti\")\n    val content =\n      json\n        .as[Json.Obj]\n        .map(obj => obj.fields.filterNot { case (key, _) => keys.contains(key) })\n        .map(tuples => Json.Obj(tuples))\n        .getOrElse(Json.Obj())\n\n    val audience =\n      json\n        .get(field(\"aud\"))\n        .flatMap(_.as[Set[String]])\n        .toOption\n        .orElse(json.get(field(\"aud\")).flatMap(_.as[String]).map(s => Set(s)).toOption)\n\n    JwtClaim(\n      content = content.toJson,\n      issuer = json.get(field(\"iss\").isString).toOption.map(_.value),\n      subject = json.get(field(\"sub\").isString).toOption.map(_.value),\n      audience = audience,\n      expiration = json.get(field(\"exp\").isNumber).toOption.map(_.value.longValue()),\n      notBefore = json.get(field(\"nbf\").isNumber).toOption.map(_.value.longValue()),\n      issuedAt = json.get(field(\"iat\").isNumber).toOption.map(_.value.longValue()),\n      jwtId = json.get(field(\"jti\").isString).toOption.map(_.value)\n    )\n  }\n\n}\n"
  },
  {
    "path": "json/zio-json/src/test/scala/JwtZIOJsonSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\n\nimport zio.json.ast.Json\n\nclass JwtZIOJsonSpec extends JwtJsonCommonSpec[Json] with ZIOJsonFixture {\n  override def jwtJsonCommon(clock: Clock): JwtZIOJson = JwtZIOJson(clock)\n}\n"
  },
  {
    "path": "json/zio-json/src/test/scala/ZIOJsonFixture.scala",
    "content": "package pdi.jwt\n\nimport zio.json._\nimport zio.json.ast.Json\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    header64: String,\n    signature: String,\n    token: String,\n    tokenUnsigned: String,\n    tokenEmpty: String,\n    headerJson: Json\n) extends JsonDataEntryTrait[Json]\n\ntrait ZIOJsonFixture extends JsonCommonFixture[Json] {\n  def parseString(value: String): Json = value.fromJson[Json].toOption.get\n\n  val claimJson = parseString(claim)\n  val headerEmptyJson = parseString(headerEmpty)\n\n  def mapData(data: DataEntryBase): JsonDataEntry = JsonDataEntry(\n    algo = data.algo,\n    header = data.header,\n    headerClass = data.headerClass,\n    header64 = data.header64,\n    signature = data.signature,\n    token = data.token,\n    tokenUnsigned = data.tokenUnsigned,\n    tokenEmpty = data.tokenEmpty,\n    headerJson = parseString(data.header)\n  )\n}\n"
  },
  {
    "path": "play/src/main/scala/JwtPlayImplicits.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\nimport javax.inject.Inject\n\nimport play.api.Configuration\nimport play.api.libs.json.Json.JsValueWrapper\nimport play.api.libs.json.{JsObject, JsString, Writes}\nimport play.api.mvc.{RequestHeader, Result}\n\ntrait JwtPlayImplicits {\n  private def sanitizeHeader(header: String)(implicit conf: Configuration): String =\n    if (header.startsWith(JwtSession.TOKEN_PREFIX)) {\n      header.substring(JwtSession.TOKEN_PREFIX.length()).trim\n    } else {\n      header.trim\n    }\n\n  private def requestToJwtSession(\n      request: RequestHeader\n  )(implicit conf: Configuration, clock: Clock): JwtSession =\n    request.headers\n      .get(JwtSession.REQUEST_HEADER_NAME)\n      .map(sanitizeHeader)\n      .map(JwtSession.deserialize)\n      .getOrElse(JwtSession())\n\n  private def requestHasJwtHeader(request: RequestHeader)(implicit conf: Configuration): Boolean =\n    request.headers.get(JwtSession.REQUEST_HEADER_NAME).isDefined\n\n  /** By adding `import pdi.jwt.*`, you will implicitely add all those methods to `Result` allowing\n    * you to easily manipulate the [[JwtSession]] inside your Play application.\n    *\n    * {{{\n    * package controllers\n    *\n    * import java.time.Clock\n    * import play.api.*\n    * import play.api.mvc.*\n    * import pdi.jwt.*\n    *\n    * object Application extends Controller {\n    *   implicit val clock: Clock = Clock.systemUTC\n    *\n    *   def login = Action { implicit request =>\n    *     Ok.addingToJwtSession((\"logged\", true))\n    *   }\n    *\n    *   def logout = Action { implicit request =>\n    *     Ok.withoutJwtSession\n    *   }\n    * }\n    * }}}\n    */\n  implicit class RichResult @Inject() (result: Result)(implicit conf: Configuration, clock: Clock) {\n\n    /** Check if the header for a [[JwtSession]] is present (first from the Result then from the\n      * RequestHeader)\n      * @return\n      *   a Boolean indicating the presence of a JWT header\n      */\n    def hasJwtHeader(implicit request: RequestHeader): Boolean = {\n      result.header.headers\n        .get(JwtSession.RESPONSE_HEADER_NAME)\n        .map(_ => true)\n        .getOrElse(requestHasJwtHeader(request))\n    }\n\n    /** Retrieve the current [[JwtSession]] from the headers (first from the Result then from the\n      * RequestHeader), if none, create a new one.\n      * @return\n      *   the JwtSession inside the headers or a new one\n      */\n    def jwtSession(implicit request: RequestHeader): JwtSession = {\n      result.header.headers.get(JwtSession.RESPONSE_HEADER_NAME) match {\n        case Some(token) => JwtSession.deserialize(sanitizeHeader(token))\n        case None        => requestToJwtSession(request)\n      }\n    }\n\n    /** If the Play app has a session.maxAge config, it will extend the expiration of the\n      * [[JwtSession]] by that time, if not, it will do nothing.\n      * @return\n      *   the same Result with, eventually, a prolonged [[JwtSession]]\n      */\n    def refreshJwtSession(implicit request: RequestHeader): Result = JwtSession.MAX_AGE match {\n      case None => result\n      case _    => {\n        // Only refresh if we actually have a proper JWT session\n        if (hasJwtHeader) {\n          result.withJwtSession(jwtSession.refresh())\n        } else {\n          result\n        }\n      }\n    }\n\n    /** Override the current [[JwtSession]] with a new one */\n    def withJwtSession(session: JwtSession): Result = {\n      result.withHeaders(\n        JwtSession.RESPONSE_HEADER_NAME -> (JwtSession.TOKEN_PREFIX + session.serialize)\n      )\n    }\n\n    /** Override the current [[JwtSession]] with a new one created from a JsObject */\n    def withJwtSession(session: JsObject): Result = withJwtSession(JwtSession(session))\n\n    /** Override the current [[JwtSession]] with a new one created from a sequence of tuples */\n    def withJwtSession(fields: (String, JsValueWrapper)*): Result = withJwtSession(\n      JwtSession(fields: _*)\n    )\n\n    /** Override the current [[JwtSession]] with a new empty one */\n    def withNewJwtSession: Result = withJwtSession(JwtSession())\n\n    /** Remove the current [[JwtSession]], which means removing the associated HTTP header */\n    def withoutJwtSession: Result = result.copy(header =\n      result.header.copy(headers = result.header.headers - JwtSession.RESPONSE_HEADER_NAME)\n    )\n\n    /** Keep the current [[JwtSession]] and add some values in it, if a key is already defined, it\n      * will be overriden.\n      */\n    def addingToJwtSession(values: (String, String)*)(implicit request: RequestHeader): Result = {\n      withJwtSession(jwtSession + new JsObject(values.map(kv => kv._1 -> JsString(kv._2)).toMap))\n    }\n\n    /** Keep the current [[JwtSession]] and add some values in it, if a key is already defined, it\n      * will be overriden.\n      */\n    def addingToJwtSession[A: Writes](key: String, value: A)(implicit\n        request: RequestHeader\n    ): Result = {\n      withJwtSession(jwtSession + (key, value))\n    }\n\n    /** Remove some keys from the current [[JwtSession]] */\n    def removingFromJwtSession(keys: String*)(implicit request: RequestHeader): Result = {\n      withJwtSession(jwtSession.--(keys: _*))\n    }\n  }\n\n  /** By adding `import pdi.jwt.*`, you will implicitely add this method to `RequestHeader` allowing\n    * you to easily retrieve the [[JwtSession]] inside your Play application.\n    *\n    * {{{\n    * package controllers\n    *\n    * import play.api.*\n    * import play.api.mvc.*\n    * import pdi.jwt.*\n    *\n    * object Application extends Controller {\n    *   def index = Action { request =>\n    *     val session: JwtSession = request.jwtSession\n    *   }\n    * }\n    * }}}\n    */\n  implicit class RichRequestHeader @Inject() (request: RequestHeader)(implicit\n      conf: Configuration,\n      clock: Clock\n  ) {\n\n    /** Return the current [[JwtSession]] from the request */\n    def jwtSession: JwtSession = requestToJwtSession(request)\n\n    /** Check if the request has the JWT header defined */\n    def hasJwtHeader: Boolean = requestHasJwtHeader(request)\n  }\n}\n"
  },
  {
    "path": "play/src/main/scala/JwtSession.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\nimport javax.inject.Inject\nimport scala.concurrent.duration.Duration\n\nimport pdi.jwt.algorithms.{JwtAsymmetricAlgorithm, JwtHmacAlgorithm}\nimport play.api.Configuration\nimport play.api.libs.json.*\nimport play.api.libs.json.Json.JsValueWrapper\n\n/** Similar to the default Play Session but using JsObject instead of Map[String, String]. The data\n  * is separated into two attributes: `headerData` and `claimData`. There is also a optional\n  * signature. Most of the time, you should only care about the `claimData` which stores the claim\n  * of the token containing the custom values you eventually put in it. That's why all methods of\n  * `JwtSession` (such as add and removing values) only modifiy the `claimData`.\n  *\n  * To see a full list of samples, check the\n  * [[https://jwt-scala.github.io/jwt-scala/jwt-play-jwt-session.html online documentation]].\n  *\n  * '''Warning''' Be aware that if you override the `claimData` (using `withClaim` for example), you\n  * might override some attributes that were automatically put inside the claim such as the\n  * expiration of the token.\n  */\ncase class JwtSession @Inject() (headerData: JsObject, claimData: JsObject, signature: String)(\n    implicit\n    conf: Configuration,\n    clock: Clock\n) {\n\n  /** Merge the `value` with `claimData` */\n  def +(value: JsObject): JwtSession = this.copy(claimData = claimData.deepMerge(value))\n\n  /** Add this (key, value) to `claimData` (existing key will be overriden) */\n  def +(key: String, value: JsValueWrapper): JwtSession = this + Json.obj(key -> value)\n\n  /** Convert `value` to its JSON counterpart and add it to `claimData` */\n  def +[T](key: String, value: T)(implicit writer: Writes[T]): JwtSession =\n    this + Json.obj(key -> writer.writes(value))\n\n  /** Add a sequence of (key, value) to `claimData` */\n  def ++(fields: (String, JsValueWrapper)*): JwtSession = this + Json.obj(fields: _*)\n\n  /** Remove one key from `claimData` */\n  def -(fieldName: String): JwtSession = this.copy(claimData = claimData - fieldName)\n\n  /** Remove a sequence of keys from `claimData` */\n  def --(fieldNames: String*): JwtSession = this.copy(claimData = fieldNames.foldLeft(claimData) {\n    (data, fieldName) => (data - fieldName)\n  })\n\n  /** Retrieve the value corresponding to `fieldName` from `claimData` */\n  def get(fieldName: String): Option[JsValue] = (claimData \\ fieldName).toOption\n\n  /** After retrieving the value, try to read it as T, if no value or fails, returns None. */\n  def getAs[T](fieldName: String)(implicit reader: Reads[T]): Option[T] =\n    get(fieldName).flatMap(value => reader.reads(value).asOpt)\n\n  /** Alias of `get` */\n  def apply(fieldName: String): Option[JsValue] = get(fieldName)\n\n  def isEmpty(): Boolean = claimData.keys.isEmpty\n\n  def claim: JwtClaim = JwtSession.jwtPlayJsonClaimReader\n    .reads(claimData)\n    .recoverTotal(e => throw JwtJson.jsErrorToException(e))\n\n  def header: JwtHeader = JwtSession.jwtPlayJsonHeaderReader\n    .reads(headerData)\n    .recoverTotal(e => throw JwtJson.jsErrorToException(e))\n\n  /** Encode the session as a JSON Web Token */\n  def serialize: String = {\n    JwtSession.signingKey match {\n      // JwtJson.encode doesn't use the implicit clock, so it's safe\n      // to just default to using the JwtJson object\n      case Some(k) => JwtJson.encode(headerData, claimData, k)\n      case _       => JwtJson.encode(headerData, claimData)\n    }\n  }\n\n  /** Overrride the `claimData` */\n  def withClaim(claim: JwtClaim): JwtSession =\n    this.copy(claimData = JwtSession.asJsObject(claim)(JwtSession.jwtPlayJsonClaimWriter))\n\n  /** Override the `headerData` */\n  def withHeader(header: JwtHeader): JwtSession =\n    this.copy(headerData = JwtSession.asJsObject(header)(JwtSession.jwtPlayJsonHeaderWriter))\n\n  /** Override the `signature` (seriously, you should never need this method) */\n  def withSignature(signature: String): JwtSession = this.copy(signature = signature)\n\n  /** If your Play app config has a `session.maxAge`, it will extend the expiration by that amount\n    */\n  def refresh(): JwtSession =\n    JwtSession.MAX_AGE.map(sec => this + (\"exp\", JwtTime.nowSeconds + sec)).getOrElse(this)\n}\n\nobject JwtSession extends JwtJsonImplicits with JwtPlayImplicits {\n  def REQUEST_HEADER_NAME(implicit conf: Configuration): String =\n    conf.getOptional[String](\"play.http.session.jwtName\").getOrElse(\"Authorization\")\n\n  def RESPONSE_HEADER_NAME(implicit conf: Configuration): String =\n    conf.getOptional[String](\"play.http.session.jwtResponseName\").getOrElse(REQUEST_HEADER_NAME)\n\n  // in seconds\n  def MAX_AGE(implicit conf: Configuration): Option[Long] =\n    conf.getOptional[Duration](\"play.http.session.maxAge\").map(_.toSeconds)\n\n  def ALGORITHM(implicit conf: Configuration): JwtAlgorithm =\n    conf\n      .getOptional[String](\"play.http.session.algorithm\")\n      .map(JwtAlgorithm.fromString)\n      .getOrElse(JwtAlgorithm.HS256)\n\n  def TOKEN_PREFIX(implicit conf: Configuration): String =\n    conf.getOptional[String](\"play.http.session.tokenPrefix\").getOrElse(\"Bearer \")\n\n  private def secretKey(implicit conf: Configuration): Option[String] =\n    conf.getOptional[String](\"play.http.secret.key\")\n\n  private def privateKey(implicit conf: Configuration): Option[String] =\n    conf.getOptional[String](\"play.http.session.privateKey\")\n\n  private def publicKey(implicit conf: Configuration): Option[String] =\n    conf.getOptional[String](\"play.http.session.publicKey\")\n\n  private def signingKey(implicit conf: Configuration): Option[String] = {\n    ALGORITHM match {\n      case _: JwtAsymmetricAlgorithm => privateKey.orElse(secretKey)\n      case _: JwtHmacAlgorithm       => secretKey\n      case _                         => Option.empty\n    }\n  }\n\n  def deserialize(token: String, options: JwtOptions)(implicit\n      conf: Configuration,\n      clock: Clock\n  ): JwtSession = ((ALGORITHM, secretKey, publicKey) match {\n    case (algorithm: JwtHmacAlgorithm, Some(sk), _) =>\n      jwtJson.decodeJsonAll(token, sk, Seq(algorithm), options)\n    case (algorithm: JwtAsymmetricAlgorithm, _, Some(pk)) =>\n      jwtJson.decodeJsonAll(token, pk, Seq(algorithm), options)\n    case _ => jwtJson.decodeJsonAll(token, options)\n  }).map { tuple =>\n    JwtSession(tuple._1, tuple._2, tuple._3)\n  }.getOrElse(JwtSession())\n\n  private def jwtJson(implicit clock: Clock): JwtJsonParser[JwtHeader, JwtClaim] =\n    // In the majority of cases, the user is just using the default JwtCore.clock.\n    // If so, we can just use the existing JwtJson instead of constructing a new one.\n    if (clock eq JwtJson.clock) JwtJson else JwtJson(clock)\n\n  def deserialize(token: String)(implicit conf: Configuration, clock: Clock): JwtSession =\n    deserialize(token, JwtOptions.DEFAULT)\n\n  private def asJsObject[A](value: A)(implicit writer: Writes[A]): JsObject =\n    writer.writes(value) match {\n      case value: JsObject => value\n      case _               => Json.obj()\n    }\n\n  def defaultHeader(implicit conf: Configuration): JwtHeader =\n    signingKey.map(_ => JwtHeader(ALGORITHM)).getOrElse(JwtHeader())\n\n  def defaultClaim(implicit conf: Configuration, clock: Clock): JwtClaim = MAX_AGE match {\n    case Some(seconds) => JwtClaim().expiresIn(seconds)\n    case _             => JwtClaim()\n  }\n\n  def apply(jsClaim: JsObject)(implicit conf: Configuration, clock: Clock): JwtSession =\n    JwtSession.apply(asJsObject(defaultHeader), jsClaim)\n\n  def apply(\n      fields: (String, JsValueWrapper)*\n  )(implicit conf: Configuration, clock: Clock): JwtSession =\n    if (fields.isEmpty) {\n      JwtSession.apply(defaultHeader, JwtClaim())\n    } else {\n      JwtSession.apply(Json.obj(fields: _*))\n    }\n\n  def apply(jsHeader: JsObject, jsClaim: JsObject)(implicit\n      conf: Configuration,\n      clock: Clock\n  ): JwtSession =\n    new JwtSession(jsHeader, jsClaim, \"\").refresh()\n\n  def apply(claim: JwtClaim)(implicit conf: Configuration, clock: Clock): JwtSession =\n    JwtSession.apply(defaultHeader, claim)\n\n  def apply(header: JwtHeader, claim: JwtClaim)(implicit\n      conf: Configuration,\n      clock: Clock\n  ): JwtSession =\n    new JwtSession(asJsObject(header), asJsObject(claim), \"\").refresh()\n\n  def apply(header: JwtHeader, claim: JwtClaim, signature: String)(implicit\n      conf: Configuration,\n      clock: Clock\n  ): JwtSession =\n    new JwtSession(asJsObject(header), asJsObject(claim), signature).refresh()\n}\n"
  },
  {
    "path": "play/src/test/scala/JwtResultSpec.scala",
    "content": "package pdi.jwt\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configuration\nimport play.api.inject.guice.GuiceApplicationBuilder\nimport play.api.libs.json.*\nimport play.api.mvc.*\nimport play.api.test.*\n\nclass JwtResultSpec extends munit.FunSuite with PlayFixture {\n  import pdi.jwt.JwtSession.*\n\n  val app = new GuiceApplicationBuilder()\n    .configure(Map(\"play.http.secret.key\" -> secretKey))\n    .build()\n\n  implicit lazy val conf: Configuration = app.configuration\n  implicit lazy val materializer: Materializer = app.materializer\n  implicit lazy val Action: DefaultActionBuilder =\n    app.injector.instanceOf(classOf[DefaultActionBuilder])\n\n  val HEADER_NAME = \"Authorization\"\n\n  val session = JwtSession().withHeader(JwtHeader(JwtAlgorithm.HS256))\n\n  test(\"JwtResult must support basic scenario\") {\n    implicit val request =\n      FakeRequest().withHeaders((\"Authorization\", \"Bearer \" + session.serialize))\n    var result: Result = Results.Ok\n\n    // We can already get a JwtSession from our implicit RequestHeader\n    assertEquals(result.jwtSession.claimData, Json.obj())\n\n    // Setting a new empty JwtSession\n    result = result.withNewJwtSession\n    assertEquals(result.jwtSession.claimData, Json.obj())\n\n    // Or from an existing JwtSession\n    result = result.withJwtSession(session)\n    assertEquals(result.jwtSession.claimData, Json.obj())\n\n    // Or from a JsObject\n    result = result.withJwtSession(Json.obj((\"id\", 1), (\"key\", \"value\")))\n    assertEquals(result.jwtSession.claimData, Json.obj(\"id\" -> 1, \"key\" -> \"value\"))\n\n    // Or from (key, value)\n    result = result.withJwtSession((\"id\", 1), (\"key\", \"value\"))\n    assertEquals(result.jwtSession.claimData, Json.obj(\"id\" -> 1, \"key\" -> \"value\"))\n\n    // We can add stuff to the current session (only (String, String))\n    result = result.addingToJwtSession((\"key2\", \"value2\"), (\"key3\", \"value3\"))\n    assertEquals(\n      result.jwtSession.claimData,\n      Json.obj(\n        \"id\" -> 1,\n        \"key\" -> \"value\",\n        \"key2\" -> \"value2\",\n        \"key3\" -> \"value3\"\n      )\n    )\n\n    // Or directly classes or objects if you have the correct implicit Writes\n    result = result.addingToJwtSession(\"user\", User(1, \"Paul\"))\n    assertEquals(\n      result.jwtSession.claimData,\n      Json.obj(\n        \"id\" -> 1,\n        \"key\" -> \"value\",\n        \"key2\" -> \"value2\",\n        \"key3\" -> \"value3\",\n        \"user\" -> Json.obj(\"id\" -> 1, \"name\" -> \"Paul\")\n      )\n    )\n\n    // Removing from session\n    result = result.removingFromJwtSession(\"key2\", \"key3\")\n    assertEquals(\n      result.jwtSession.claimData,\n      Json.obj(\n        \"id\" -> 1,\n        \"key\" -> \"value\",\n        \"user\" -> Json.obj(\"id\" -> 1, \"name\" -> \"Paul\")\n      )\n    )\n\n    // Refresh the current session\n    result = result.refreshJwtSession\n    assertEquals(\n      result.jwtSession.claimData,\n      Json.obj(\n        \"id\" -> 1,\n        \"key\" -> \"value\",\n        \"user\" -> Json.obj(\"id\" -> 1, \"name\" -> \"Paul\")\n      )\n    )\n\n    // So, at the end, you can do\n    assertEquals(result.jwtSession.getAs[User](\"user\"), Some(User(1, \"Paul\")))\n  }\n}\n"
  },
  {
    "path": "play/src/test/scala/JwtSessionAsymetricSpec.scala",
    "content": "package pdi.jwt\nimport java.time.{Clock, Duration}\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configuration\nimport play.api.inject.guice.GuiceApplicationBuilder\nimport play.api.libs.json.*\nimport play.api.mvc.*\nimport play.api.test.Helpers.*\n\nclass JwtSessionAsymetricSpec extends munit.FunSuite with PlayFixture {\n\n  // Just for test, users shouldn't change the header name normally\n  def HEADER_NAME = \"Auth\"\n  def sessionTimeout = defaultMaxAge\n\n  val privateKey: String =\n    \"MIIBOAIBAAJAZbLOel7f+8jUyAxPcrwbWZjxOoEPCXOIQfrv5VeVCxGJMYnsZeCjfN9JEGyMoVXY65nD5crMOGj6oF8V5APL4wIDAQABAkAtiJx4H8iLdEUI+LINvflE6XyAZE52PdsxJ4iHl+oslPG1cHNSiE46Ol1uSWvv6GD3VPjcfi+wTPe6nWQmnZ6ZAiEAqji6dSXzWTYHwHjrBZyUwqD8LXa5mrkGAht1SZhyBK0CIQCY8lFtMM1Td7O9hplwZLGVvXAbDKNaAmvcVZUWD1aUzwIgYKLwCA3Rh3YLFJQRKRBpy8zFHbJnUJV1+cBI580p/ckCICiI6C2xJmm9qsRLHPVdqncOCt0QX2amh6GQiP+ctwyfAiAVMw0Pa3lad/Q2Wt9M7I3FdoGiGlcfMEk1NzvPvtGTjA==\"\n  val publicKey: String =\n    \"MFswDQYJKoZIhvcNAQEBBQADSgAwRwJAZbLOel7f+8jUyAxPcrwbWZjxOoEPCXOIQfrv5VeVCxGJMYnsZeCjfN9JEGyMoVXY65nD5crMOGj6oF8V5APL4wIDAQAB\"\n\n  // {\"typ\":\"JWT\",\"alg\":\"RS256\"}\n  val header = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9\"\n  val signature =\n    \"WrNldzMwkaki0eK_-S5VyZqJpCmkDtYoehh-R_Dvr6vNvUdZsJtugZReQ74zhm1ntWGIw5EP8G1vrzJ9sirLIQ\"\n\n  val app =\n    new GuiceApplicationBuilder()\n      .configure(\n        Map.apply[String, Any](\n          \"play.http.session.privateKey\" -> privateKey,\n          \"play.http.session.publicKey\" -> publicKey,\n          \"play.http.session.jwtName\" -> HEADER_NAME,\n          \"play.http.session.maxAge\" -> sessionTimeout * 1000,\n          \"play.http.session.algorithm\" -> \"RS256\",\n          \"play.http.session.tokenPrefix\" -> \"\"\n        )\n      )\n      .build()\n\n  implicit lazy val conf: Configuration = app.configuration\n  implicit lazy val materializer: Materializer = app.materializer\n  implicit lazy val Action: DefaultActionBuilder =\n    app.injector.instanceOf(classOf[DefaultActionBuilder])\n\n  val session = JwtSession()\n  val sessionCustom = JwtSession(JwtHeader(JwtAlgorithm.RS256), claimClass, signature)\n  val tokenCustom = s\"$header.$playClaim64.$signature\"\n  // Order in the Json changed for Scala 2.13 so this is correct too\n  val tokenCustom2 =\n    s\"$header.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIiwiZXhwIjoxMzAwODE5MzgwfQ.XCvpOGm7aPRy5hozuniyxFJJOMSdo5VYykpZmiGJ3d37WZAIHCrUI1TtkEIU3IbOny2fevilILBliPNgrXl3tA\"\n  // Order changed again for Scala 3 (!!!)\n  val tokenCustom3 =\n    s\"$header.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.NCDbnwlfFuF28QZte2WU6tSl_H9q7O9ujOS8c9FcPvL2kEeb2q9TFcW-v5X8Si5YzRdY78y7pBtsF3pyr15ROA\"\n\n  test(\"Init FakeApplication with the correct config\") {\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.privateKey\"),\n      Option(privateKey)\n    )\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.publicKey\"),\n      Option(\n        publicKey\n      )\n    )\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.jwtName\"),\n      Option(\n        HEADER_NAME\n      )\n    )\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.algorithm\"),\n      Option(\"RS256\")\n    )\n    assertEquals(app.configuration.getOptional[String](\"play.http.session.tokenPrefix\"), Option(\"\"))\n    assertEquals(\n      app.configuration.getOptional[Long](\"play.http.session.maxAge\"),\n      Option(sessionTimeout * 1000)\n    )\n  }\n\n  test(\"JwtSession must read default configuration\") {\n    assertEquals(JwtSession.defaultHeader, JwtHeader(JwtAlgorithm.RS256))\n    assertEquals(JwtSession.ALGORITHM, JwtAlgorithm.RS256)\n  }\n\n  test(\"JwtSession must init\") {\n    assertEquals(session.headerData, Json.obj(\"typ\" -> \"JWT\", \"alg\" -> \"RS256\"))\n    assertEquals(session.claimData, Json.obj(\"exp\" -> (validTime + sessionTimeout)))\n    assertEquals(session.signature, \"\")\n    assert(!session.isEmpty()) // There is the expiration date in the claim\n  }\n\n  test(\"JwtSession must serialize\") {\n    assert(Set(tokenCustom, tokenCustom2, tokenCustom3).contains(clue(sessionCustom.serialize)))\n  }\n\n  test(\"JwtSession must deserialize\") {\n    assertEquals(JwtSession.deserialize(tokenCustom), sessionCustom)\n  }\n\n  val sessionHeaderUser = Some(\n    header + \".eyJleHAiOjEzMDA4MTkzODAsInVzZXIiOnsiaWQiOjEsIm5hbWUiOiJQYXVsIn19.ZSTobcEIDXJfxhsIhHoe-ySBxHHKsHoYqVW5n0WHTtq3yN3X1eejfvONcaDi8Wq-EK9CM9VMeP1CIPmBX91M8g\"\n  )\n  val sessionHeaderExp = Some(\n    header + \".eyJleHAiOjEzMDA4MTk0MTF9.Vyr9qnNVAGNAqU1N_JkaiYUhVq3dgBrsjlW4gr4pdO9nIh1QeWABFi3ADKSC5Z7zubvH_WAx3X5A9SaKxp4_bg\"\n  )\n\n  test(\"RichResult must access app with no user\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must fail to login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"wrong\"))\n    assertEquals(status(result), BAD_REQUEST)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"p4ssw0rd\"))\n    assertEquals(status(result), OK)\n  }\n\n  test(\"RichResult must access app with user\") {\n    val result = get(classicAction, sessionHeaderUser)\n    val result2 = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), OK)\n    assertEquals(jwtHeader(result), sessionHeaderUser)\n    assertEquals(jwtHeader(result2), sessionHeaderUser)\n  }\n\n  test(\"RichResult must move to the future!\") {\n    this.clock = Clock.offset(this.clock, Duration.ofSeconds(sessionTimeout + 1))\n  }\n\n  test(\"RichResult must timeout session\") {\n    val result = get(classicAction, sessionHeaderUser)\n    val result2 = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), sessionHeaderExp)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must logout\") {\n    val result = get(logoutAction)\n    assertEquals(status(result), OK)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must access app with no user again\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n}\n"
  },
  {
    "path": "play/src/test/scala/JwtSessionCustomDifferentNameSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.{Clock, Duration}\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configuration\nimport play.api.inject.guice.GuiceApplicationBuilder\nimport play.api.libs.json.*\nimport play.api.mvc.*\nimport play.api.test.*\nimport play.api.test.Helpers.*\n\nclass JwtSessionCustomDifferentNameSpec extends munit.FunSuite with Injecting with PlayFixture {\n\n  // Just for test, users shouldn't change the header name normally\n  def HEADER_NAME = \"Auth\"\n  def RESPONSE_HEADER_NAME = \"Set-Auth\"\n  def sessionTimeout = defaultMaxAge\n\n  val header = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9\"\n  val signature =\n    \"3FQn0RsztnK6i8x8Vi8k6WEsvzfnKDF2yx9WPeeiC1gu6yWZAMmCvzZi05A3d9sx2GwFfkVFPXgk_erYoizFxw\"\n\n  val app =\n    new GuiceApplicationBuilder()\n      .configure(\n        Map.apply[String, Any](\n          \"play.http.secret.key\" -> secretKey,\n          \"play.http.session.jwtName\" -> HEADER_NAME,\n          \"play.http.session.jwtResponseName\" -> RESPONSE_HEADER_NAME,\n          \"play.http.session.maxAge\" -> sessionTimeout * 1000,\n          \"play.http.session.algorithm\" -> \"HS512\",\n          \"play.http.session.tokenPrefix\" -> \"\"\n        )\n      )\n      .build()\n\n  implicit lazy val conf: Configuration = app.configuration\n  implicit lazy val materializer: Materializer = app.materializer\n  implicit lazy val Action: DefaultActionBuilder =\n    app.injector.instanceOf(classOf[DefaultActionBuilder])\n\n  val session = JwtSession()\n  val sessionCustom = JwtSession(JwtHeader(JwtAlgorithm.HS512), claimClass, signature)\n  val tokenCustom = header + \".\" + playClaim64 + \".\" + signature\n  // Order in the Json changed for Scala 2.13 so this is correct too\n  val tokenCustom2 =\n    \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIiwiZXhwIjoxMzAwODE5MzgwfQ.aK_C250FUCSYbfjAfUgvAHoOfqa3EAdadSYkO0xEt1LJijGR0b89t2bl9AZJXdM4azAFj4RbzPyxSpIVczlchA\"\n  val tokenCustom3 =\n    \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.ngZsdQj8p2wvUAo8xCbJPwganGPnG5UnLkg7VrE6NgmQdV16UITjlBajZxcai_U5PjQdeN-yJtyA5kxf8O5BOQ\"\n\n  test(\"Init FakeApplication with correct config\") {\n    assertEquals(app.configuration.getOptional[String](\"play.http.secret.key\"), Option(secretKey))\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.jwtName\"),\n      Option(HEADER_NAME)\n    )\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.jwtResponseName\"),\n      Option(\n        RESPONSE_HEADER_NAME\n      )\n    )\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.algorithm\"),\n      Option(\"HS512\")\n    )\n    assertEquals(app.configuration.getOptional[String](\"play.http.session.tokenPrefix\"), Option(\"\"))\n    assertEquals(\n      app.configuration.getOptional[Long](\"play.http.session.maxAge\"),\n      Option(\n        sessionTimeout * 1000\n      )\n    )\n  }\n\n  test(\"JwtSession must read default configuration\") {\n    assertEquals(JwtSession.defaultHeader, JwtHeader(JwtAlgorithm.HS512))\n    assertEquals(JwtSession.ALGORITHM, JwtAlgorithm.HS512)\n  }\n\n  test(\"JwtSession must init\") {\n    assertEquals(session.headerData, Json.obj(\"typ\" -> \"JWT\", \"alg\" -> \"HS512\"))\n    assertEquals(session.claimData, Json.obj(\"exp\" -> (validTime + sessionTimeout)))\n    assertEquals(session.signature, \"\")\n    assert(!session.isEmpty()) // There is the expiration date in the claim\n  }\n\n  test(\"JwtSession must serialize\") {\n    assert(Set(tokenCustom, tokenCustom2, tokenCustom3).contains(clue(sessionCustom.serialize)))\n  }\n\n  test(\"JwtSession must deserialize\") {\n    assertEquals(JwtSession.deserialize(tokenCustom), sessionCustom)\n  }\n\n  val sessionHeaderUser = Some(\n    header + \".eyJleHAiOjEzMDA4MTkzODAsInVzZXIiOnsiaWQiOjEsIm5hbWUiOiJQYXVsIn19.nfhPaLvlRjXlq3o-B1FvHk0rG_ZsqMdnr9cR3GCK23iGZ4an6uxOr_FJCXX5sgtnMIx1uqQ3utgW9jyBqqFuUw\"\n  )\n  val sessionHeaderExp = Some(\n    header + \".eyJleHAiOjEzMDA4MTk0MTF9.B27yGau7FJWE_2ir6B4dqQkXh3DhgryR29nyjA-TuWNfx3H7kcRbWf2XrpMN3cCpU04Oi1cV5I0w8DVyO-h6Ig\"\n  )\n\n  test(\"RichResult must access app with no user\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must fail to login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"wrong\"))\n    assertEquals(status(result), BAD_REQUEST)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"p4ssw0rd\"))\n    assertEquals(status(result), OK)\n  }\n\n  test(\"RichResult must access app with user\") {\n    val result = get(classicAction, sessionHeaderUser)\n    val result2 = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), OK)\n    assertEquals(jwtHeader(result), sessionHeaderUser)\n    assertEquals(jwtHeader(result2), sessionHeaderUser)\n  }\n\n  test(\"RichResult must move to the future!\") {\n    this.clock = Clock.offset(this.clock, Duration.ofSeconds(sessionTimeout + 1))\n  }\n\n  test(\"RichResult must timeout session\") {\n    val result = get(classicAction, sessionHeaderUser)\n    val result2 = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), sessionHeaderExp)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must logout\") {\n    val result = get(logoutAction)\n    assertEquals(status(result), OK)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must access app with no user again\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n}\n"
  },
  {
    "path": "play/src/test/scala/JwtSessionCustomSpec.scala",
    "content": "package pdi.jwt\n\nimport java.time.{Clock, Duration}\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configuration\nimport play.api.inject.guice.GuiceApplicationBuilder\nimport play.api.libs.json.*\nimport play.api.mvc.*\nimport play.api.test.Helpers.*\n\nclass JwtSessionCustomSpec extends munit.FunSuite with PlayFixture {\n  // Just for test, users shouldn't change the header name normally\n  def HEADER_NAME = \"Auth\"\n  def sessionTimeout = defaultMaxAge\n\n  val header = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9\"\n  val signature =\n    \"3FQn0RsztnK6i8x8Vi8k6WEsvzfnKDF2yx9WPeeiC1gu6yWZAMmCvzZi05A3d9sx2GwFfkVFPXgk_erYoizFxw\"\n\n  val app =\n    new GuiceApplicationBuilder()\n      .configure(\n        Map.apply[String, Any](\n          \"play.http.secret.key\" -> secretKey,\n          \"play.http.session.jwtName\" -> HEADER_NAME,\n          \"play.http.session.maxAge\" -> sessionTimeout * 1000,\n          \"play.http.session.algorithm\" -> \"HS512\",\n          \"play.http.session.tokenPrefix\" -> \"\"\n        )\n      )\n      .build()\n\n  implicit lazy val conf: Configuration = app.configuration\n  implicit lazy val materializer: Materializer = app.materializer\n  implicit lazy val Action: DefaultActionBuilder =\n    app.injector.instanceOf(classOf[DefaultActionBuilder])\n\n  def session = JwtSession()\n  def sessionCustom = JwtSession(JwtHeader(JwtAlgorithm.HS512), claimClass, signature)\n  def tokenCustom = header + \".\" + playClaim64 + \".\" + signature\n  // Order in the Json changed for Scala 2.13 so this is correct too\n  def tokenCustom2 =\n    s\"$header.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIiwiZXhwIjoxMzAwODE5MzgwfQ.aK_C250FUCSYbfjAfUgvAHoOfqa3EAdadSYkO0xEt1LJijGR0b89t2bl9AZJXdM4azAFj4RbzPyxSpIVczlchA\"\n  def tokenCustom3 =\n    s\"$header.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.ngZsdQj8p2wvUAo8xCbJPwganGPnG5UnLkg7VrE6NgmQdV16UITjlBajZxcai_U5PjQdeN-yJtyA5kxf8O5BOQ\"\n\n  test(\"Init FakeApplication with correct config\") {\n    assertEquals(app.configuration.getOptional[String](\"play.http.secret.key\"), Option(secretKey))\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.jwtName\"),\n      Option(HEADER_NAME)\n    )\n    assertEquals(\n      app.configuration.getOptional[String](\"play.http.session.algorithm\"),\n      Option(\"HS512\")\n    )\n    assertEquals(app.configuration.getOptional[String](\"play.http.session.tokenPrefix\"), Option(\"\"))\n    assertEquals(\n      app.configuration.getOptional[Long](\"play.http.session.maxAge\"),\n      Option(sessionTimeout * 1000)\n    )\n  }\n\n  test(\"JwtSession must read default configuration\") {\n    assertEquals(JwtSession.defaultHeader, JwtHeader(JwtAlgorithm.HS512))\n    assertEquals(JwtSession.ALGORITHM, JwtAlgorithm.HS512)\n  }\n\n  test(\"JwtSession must init\") {\n    assertEquals(session.headerData, Json.obj(\"typ\" -> \"JWT\", \"alg\" -> \"HS512\"))\n    assertEquals(session.claimData, Json.obj(\"exp\" -> (validTime + sessionTimeout)))\n    assertEquals(session.signature, \"\")\n    assert(!session.isEmpty()) // There is the expiration date in the claim\n  }\n\n  test(\"JwtSession must serialize\") {\n    assert(Set(tokenCustom, tokenCustom2, tokenCustom3).contains(clue(sessionCustom.serialize)))\n  }\n\n  test(\"JwtSession must deserialize\") {\n    assertEquals(JwtSession.deserialize(tokenCustom), clue(sessionCustom))\n  }\n\n  val sessionHeaderExp = Some(\n    header + \".eyJleHAiOjEzMDA4MTk0MTF9.B27yGau7FJWE_2ir6B4dqQkXh3DhgryR29nyjA-TuWNfx3H7kcRbWf2XrpMN3cCpU04Oi1cV5I0w8DVyO-h6Ig\"\n  )\n  val sessionHeaderUser = Some(\n    header + \".eyJleHAiOjEzMDA4MTkzODAsInVzZXIiOnsiaWQiOjEsIm5hbWUiOiJQYXVsIn19.nfhPaLvlRjXlq3o-B1FvHk0rG_ZsqMdnr9cR3GCK23iGZ4an6uxOr_FJCXX5sgtnMIx1uqQ3utgW9jyBqqFuUw\"\n  )\n  val sessionHeaderUser2 = Some(\n    header + \".eyJleHAiOjEzMDA4MTk0MTEsInVzZXIiOnsiaWQiOjEsIm5hbWUiOiJQYXVsIn19.WaMPSgHoJ84DeYdYoVouGDuZc4ZnntHMR17FHdHHq4idpptKZvOt-lv1UUEt7tc6rtgzL3uo8QWRVXgooONAQA\"\n  )\n\n  test(\"RichResult must access app with no user\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must fail to login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"wrong\"))\n    assertEquals(status(result), BAD_REQUEST)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"p4ssw0rd\"))\n    assertEquals(status(result), OK)\n  }\n\n  test(\"RichResult must access app with user\") {\n    val result = get(classicAction, sessionHeaderUser)\n    val result2 = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), OK)\n    assertEquals(jwtHeader(result), sessionHeaderUser)\n    assertEquals(jwtHeader(result2), sessionHeaderUser)\n  }\n\n  test(\"RichResult must move to the future!\") {\n    this.clock = Clock.offset(this.clock, Duration.ofSeconds(sessionTimeout + 1))\n  }\n\n  test(\"RichResult must timeout session\") {\n    val result = get(classicAction, sessionHeaderUser)\n    val result2 = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), sessionHeaderExp)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must logout\") {\n    val result = get(logoutAction)\n    assertEquals(status(result), OK)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must access app with no user again\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must login again\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"p4ssw0rd\"))\n    assertEquals(status(result), OK)\n  }\n\n  test(\"RichResult must move to the future again!\") {\n    this.clock = Clock.offset(this.clock, Duration.ofSeconds(sessionTimeout + 1))\n  }\n\n  test(\"RichResult must timeout secured action again\") {\n    val result = get(securedAction, sessionHeaderUser)\n\n    assertEquals(status(result), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n  }\n}\n"
  },
  {
    "path": "play/src/test/scala/JwtSessionSpec.scala",
    "content": "package pdi.jwt\n\nimport scala.concurrent.duration.Duration\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configuration\nimport play.api.inject.guice.GuiceApplicationBuilder\nimport play.api.libs.json.*\nimport play.api.mvc.*\nimport play.api.test.Helpers.*\n\nclass JwtSessionSpec extends munit.FunSuite with PlayFixture {\n  val app =\n    new GuiceApplicationBuilder()\n      .configure(\n        Map(\n          \"play.http.secret.key\" -> secretKey,\n          \"play.http.session.maxAge\" -> null\n        )\n      )\n      .build()\n\n  implicit lazy val conf: Configuration = app.configuration\n  implicit lazy val materializer: Materializer = app.materializer\n  implicit lazy val Action: DefaultActionBuilder =\n    app.injector.instanceOf(classOf[DefaultActionBuilder])\n\n  def HEADER_NAME = \"Authorization\"\n  val session = JwtSession().withHeader(JwtHeader(JwtAlgorithm.HS256))\n  val session2 = session ++ ((\"a\", 1), (\"b\", \"c\"), (\"e\", true), (\"f\", Seq(1, 2, 3)), (\"user\", user))\n  val session3 = JwtSession(\n    JwtHeader(JwtAlgorithm.HS256),\n    claimClass,\n    \"IPSERPZc5wyxrZ4Yiq7l31wFk_qaDY5YrnfLjIC0Lmc\"\n  )\n  // This is session3 serialized (if no bug...)\n  val token =\n    \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.\" + claim64 + \".IPSERPZc5wyxrZ4Yiq7l31wFk_qaDY5YrnfLjIC0Lmc\"\n  // Order in the Json changed for Scala 2.13 so this is correct too\n  val token2 =\n    \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIiwiZXhwIjoxMzAwODE5MzgwfQ.XCvpOGm7aPRy5hozuniyxFJJOMSdo5VYykpZmiGJ3d37WZAIHCrUI1TtkEIU3IbOny2fevilILBliPNgrXl3tA\"\n\n  test(\"Init FakeApplication with the correct config\") {\n    assertEquals(app.configuration.getOptional[String](\"play.http.secret.key\"), Option(secretKey))\n    assertEquals(app.configuration.getOptional[Duration](\"play.http.session.maxAge\"), None)\n  }\n\n  test(\"JwtSession must read default configuration\") {\n    assertEquals(JwtSession.defaultHeader, JwtHeader(JwtAlgorithm.HS256))\n    assert(JwtSession.MAX_AGE.isEmpty)\n  }\n\n  test(\"JwtSession must init\") {\n    assertEquals(session.headerData, Json.obj(\"typ\" -> \"JWT\", \"alg\" -> \"HS256\"))\n    assertEquals(session.claimData, Json.obj())\n    assertEquals(session.signature, \"\")\n    assert(session.isEmpty())\n  }\n\n  test(\"JwtSession must add stuff\") {\n    assertEquals((session + Json.obj(\"a\" -> 1)).claimData, Json.obj(\"a\" -> 1))\n    assertEquals((session + (\"a\", 1) + (\"b\", \"c\")).claimData, Json.obj(\"a\" -> 1, \"b\" -> \"c\"))\n    assertEquals((session + (\"user\", user)).claimData, Json.obj(\"user\" -> userJson))\n    assertEquals((session ++ ((\"a\", 1), (\"b\", \"c\"))).claimData, Json.obj(\"a\" -> 1, \"b\" -> \"c\"))\n\n    assertEquals(\n      (session + (\"a\", 1) + (\"b\", \"c\") + (\"user\", user)).claimData,\n      Json.obj(\n        \"a\" -> 1,\n        \"b\" -> \"c\",\n        \"user\" -> userJson\n      )\n    )\n\n    val sessionBis = session + (\"a\", 1) + (\"b\", \"c\")\n    val sessionTer = sessionBis ++ ((\"d\", true), (\"e\", 42))\n    val sessionQuad = sessionTer + (\"user\", user)\n    assertEquals(\n      sessionQuad.claimData,\n      Json.obj(\n        \"a\" -> 1,\n        \"b\" -> \"c\",\n        \"d\" -> true,\n        \"e\" -> 42,\n        \"user\" -> userJson\n      )\n    )\n  }\n\n  test(\"JwtSession must remove stuff\") {\n    assertEquals((session2 - \"e\" - \"f\" - \"user\").claimData, Json.obj(\"a\" -> 1, \"b\" -> \"c\"))\n    assertEquals((session2 -- (\"e\", \"f\", \"user\")).claimData, Json.obj(\"a\" -> 1, \"b\" -> \"c\"))\n  }\n\n  test(\"JwtSession must get stuff\") {\n    assertEquals(session2(\"a\"), Option(JsNumber(1)))\n    assertEquals(session2(\"b\"), Option(JsString(\"c\")))\n    assertEquals(session2(\"e\"), Option(JsBoolean(true)))\n    assertEquals(session2(\"f\"), Option(Json.arr(1, 2, 3)))\n    assert(session2(\"nope\") match { case None => true; case _ => false })\n    assertEquals(session2.get(\"a\"), Option(JsNumber(1)))\n    assertEquals(session2.get(\"b\"), Option(JsString(\"c\")))\n    assertEquals(session2.get(\"e\"), Option(JsBoolean(true)))\n    assertEquals(session2.get(\"f\"), Option(Json.arr(1, 2, 3)))\n    assert(session2.get(\"nope\") match { case None => true; case _ => false })\n    assertEquals(session2.getAs[User](\"user\"), Option(user))\n    assertEquals(session2.getAs[User](\"nope\"), None)\n  }\n\n  test(\"JwtSession must test emptiness\") {\n    assert(session.isEmpty())\n    assert(!session2.isEmpty())\n  }\n\n  test(\"JwtSession must serialize\") {\n    assert(Set(token, token2).contains(session3.serialize))\n  }\n\n  test(\"JwtSession must deserialize\") {\n    assertEquals(JwtSession.deserialize(token)(conf, validTimeClock), session3)\n  }\n\n  val sessionHeader = Some(\n    \"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7ImlkIjoxLCJuYW1lIjoiUGF1bCJ9fQ.KBHKQarAQMse-4Conoi22XShk1ky--XXKAx4kMp6v-M\"\n  )\n\n  test(\"RichResult must access app with no user\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must fail to login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"wrong\"))\n    assertEquals(status(result), BAD_REQUEST)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must login\") {\n    val result = post(loginAction, Json.obj(\"username\" -> \"whatever\", \"password\" -> \"p4ssw0rd\"))\n    assertEquals(status(result), OK)\n    assertEquals(jwtHeader(result), sessionHeader)\n  }\n\n  test(\"RichResult must access app with user\") {\n    val result = get(classicAction, sessionHeader)\n    val result2 = get(securedAction, sessionHeader)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), OK)\n    // Wuuut? Why None? Because since there is no \"session.maxAge\", we don't need to refresh the token\n    // it's up to the client-side code to save it as long as it needs it\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n\n  test(\"RichResult must logout\") {\n    val result = get(logoutAction)\n    assertEquals(status(result), OK)\n    assertEquals(jwtHeader(result), None)\n  }\n\n  test(\"RichResult must access app with no user again\") {\n    val result = get(classicAction)\n    val result2 = get(securedAction)\n\n    assertEquals(status(result), OK)\n    assertEquals(status(result2), UNAUTHORIZED)\n    assertEquals(jwtHeader(result), None)\n    assertEquals(jwtHeader(result2), None)\n  }\n}\n"
  },
  {
    "path": "play/src/test/scala/PlayFixture.scala",
    "content": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.concurrent.Future\n\nimport org.apache.pekko.stream.Materializer\nimport org.apache.pekko.util.Timeout\nimport play.api.Configuration\nimport play.api.libs.json.*\nimport play.api.mvc.*\nimport play.api.mvc.Results.*\nimport play.api.test.*\nimport play.api.test.Helpers.*\n\ncase class User(id: Long, name: String)\n\ntrait PlayFixture extends Fixture {\n  import pdi.jwt.JwtSession.*\n\n  implicit var clock: Clock = validTimeClock\n\n  def HEADER_NAME: String\n\n  implicit val userFormat: OFormat[User] = Json.format[User]\n\n  val user = User(1, \"Paul\")\n  val userJson = Json.obj(\"id\" -> 1, \"name\" -> \"Paul\")\n\n  var defaultMaxAge = expiration - validTime\n\n  // The expiration is not added in the same way, resulting in JSON properties not in the same order,\n  // meaning a different Base64 encoding\n  val playClaim64 =\n    \"eyJpc3MiOiJqb2UiLCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiZXhwIjoxMzAwODE5MzgwfQ\"\n\n  def loginAction(implicit conf: Configuration, Action: DefaultActionBuilder): EssentialAction =\n    Action { implicit request =>\n      val body = request.body.asJson\n      val password = (body.get \\ \"password\").as[String]\n\n      password match {\n        case \"p4ssw0rd\" => Ok.withJwtSession(Json.obj(\"user\" -> userJson))\n        case _          => BadRequest\n      }\n    }\n\n  def logoutAction(implicit conf: Configuration, Action: DefaultActionBuilder): EssentialAction =\n    Action {\n      Ok.withoutJwtSession\n    }\n\n  def classicAction(implicit conf: Configuration, Action: DefaultActionBuilder): EssentialAction =\n    Action { implicit request =>\n      Ok.refreshJwtSession\n    }\n\n  def securedAction(implicit conf: Configuration, Action: DefaultActionBuilder): EssentialAction =\n    Action { implicit request =>\n      request.jwtSession.getAs[User](\"user\") match {\n        case Some(_) => Ok.refreshJwtSession\n        case _       => Unauthorized.withoutJwtSession\n      }\n    }\n\n  def get(action: EssentialAction, header: Option[String] = None)(implicit\n      conf: Configuration,\n      mat: Materializer\n  ) = {\n    val request = header match {\n      case Some(h) =>\n        FakeRequest(GET, \"/something\").withHeaders((JwtSession.REQUEST_HEADER_NAME, h))\n      case _ => FakeRequest(GET, \"/something\")\n    }\n\n    call(action, request)\n  }\n\n  def post(action: EssentialAction, body: JsObject)(implicit mat: Materializer) = {\n    call(action, FakeRequest(POST, \"/something\").withJsonBody(body))\n  }\n\n  def jwtHeader(\n      of: Future[Result]\n  )(implicit timeout: Timeout, conf: Configuration): Option[String] =\n    header(JwtSession.RESPONSE_HEADER_NAME, of)(timeout)\n}\n"
  },
  {
    "path": "project/Libs.scala",
    "content": "import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._\nimport sbt._\n\nobject Versions {\n  val munit = \"1.1.0\"\n\n  val bouncyCastle = \"1.83\"\n\n  val guice = \"4.2.3\"\n\n  val scalaJavaTime = \"2.6.0\"\n\n  val scalajsSecureRandom = \"1.0.0\"\n\n  val play = \"3.0.9\"\n\n  val playJson = \"3.0.5\"\n\n  val json4s = \"4.0.7\"\n\n  val circe = \"0.14.14\"\n\n  val upickle = \"4.4.0\"\n\n  val zioJson = \"0.7.44\"\n\n  val argonaut = \"6.3.10\"\n}\n\nobject Libs {\n  val scalaJavaTime =\n    Def.setting(\"io.github.cquiroz\" %%% \"scala-java-time\" % Versions.scalaJavaTime)\n  val scalajsSecureRandom = Def.setting(\n    (\"org.scala-js\" %%% \"scalajs-java-securerandom\" % Versions.scalajsSecureRandom).cross(\n      CrossVersion.for3Use2_13\n    )\n  )\n\n  val play = \"org.playframework\" %% \"play\" % Versions.play\n  val playJson = \"org.playframework\" %% \"play-json\" % Versions.playJson\n  val playTest = \"org.playframework\" %% \"play-test\" % Versions.play % Test\n  val playTestProvided = \"org.playframework\" %% \"play-test\" % Versions.play\n  val guice = \"com.google.inject\" % \"guice\" % Versions.guice % \"test\"\n\n  val json4sCore = \"org.json4s\" %% \"json4s-core\" % Versions.json4s\n  val json4sNative = \"org.json4s\" %% \"json4s-native-core\" % Versions.json4s\n  val json4sJackson = \"org.json4s\" %% \"json4s-jackson-core\" % Versions.json4s\n\n  val circeCore = Def.setting(\"io.circe\" %%% \"circe-core\" % Versions.circe)\n  val circeGeneric = Def.setting(\"io.circe\" %%% \"circe-generic\" % Versions.circe)\n  val circeJawn = Def.setting(\"io.circe\" %%% \"circe-jawn\" % Versions.circe)\n  val circeParse = Def.setting(\"io.circe\" %%% \"circe-parser\" % Versions.circe)\n\n  val upickle = Def.setting(\"com.lihaoyi\" %%% \"upickle\" % Versions.upickle)\n\n  val zioJson = \"dev.zio\" %% \"zio-json\" % Versions.zioJson\n\n  val argonaut = \"io.argonaut\" %% \"argonaut\" % Versions.argonaut\n\n  val bouncyCastle = \"org.bouncycastle\" % \"bcpkix-jdk18on\" % Versions.bouncyCastle % Test\n  val bouncyCastleTut = \"org.bouncycastle\" % \"bcpkix-jdk18on\" % Versions.bouncyCastle\n\n  val munit = Def.setting(\"org.scalameta\" %%% \"munit\" % Versions.munit % Test)\n  val munitScalacheck =\n    Def.setting(\"org.scalameta\" %%% \"munit-scalacheck\" % Versions.munit % Test)\n}\n"
  },
  {
    "path": "project/build.properties",
    "content": "sbt.version=1.12.9\n"
  },
  {
    "path": "project/plugins.sbt",
    "content": "// Documentation\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-site-paradox\" % \"1.7.0\")\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-unidoc\" % \"0.6.1\")\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-paradox-material-theme\" % \"0.7.0\")\n\naddSbtPlugin(\n  (\"com.github.sbt\" % \"sbt-ghpages\" % \"0.9.0\")\n    // sbt-ghpages depends on sbt-site 1.4.1, which pulls Scala XML 1.x\n    .exclude(\"org.scala-lang.modules\", \"scala-xml_2.12\")\n)\n\n// Publishing\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-pgp\" % \"2.3.1\")\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-ci-release\" % \"1.11.2\")\n\n// Linting\n\naddSbtPlugin(\"org.scalameta\" % \"sbt-scalafmt\" % \"2.5.6\")\n\naddSbtPlugin(\"org.typelevel\" % \"sbt-tpolecat\" % \"0.5.3\")\n\naddSbtPlugin(\"com.typesafe\" % \"sbt-mima-plugin\" % \"1.1.5\")\n\n// Scala JS / Scala Native\n\naddSbtPlugin(\"org.scala-js\" % \"sbt-scalajs\" % \"1.19.0\")\n\naddSbtPlugin(\"org.scala-native\" % \"sbt-scala-native\" % \"0.5.11\")\n\naddSbtPlugin(\"org.portable-scala\" % \"sbt-scalajs-crossproject\" % \"1.3.2\")\n\naddSbtPlugin(\"org.portable-scala\" % \"sbt-scala-native-crossproject\" % \"1.3.2\")\n"
  },
  {
    "path": "scripts/bump.sh",
    "content": "#!/bin/bash\n\nOLDVERSION=$1\nVERSION=$2\n\necho \"Stating publish process for JWT Scala from $OLDVERSION to $VERSION ...\"\n\necho \"Upgrading version in files\"\nsed -i.tmp \"s/$OLDVERSION/$VERSION/g\" ./README.md\nsed -i.tmp \"s/$OLDVERSION/$VERSION/g\" ./docs/src/main/mdoc/_install.md\n\nrm ./README.md.tmp\nrm ./docs/src/main/mdoc/_install.md.tmp\n\n# The end\nexit 0;\n"
  },
  {
    "path": "scripts/clean.sh",
    "content": "find . -name \"*.tmp\" -exec rm -rf {} \\;\n"
  },
  {
    "path": "scripts/pu.sh",
    "content": "#!/bin/bash\n\nVERSION=$1\n\necho \"Pushing to GitHub\"\ngit add .\ngit commit -m \"Release v$VERSION\"\ngit tag -a v$VERSION -m \"Release v$VERSION\"\ngit push origin main\necho \"Don't forget to create the release on GitHub <-----------------------------------------\"\n\nexit 0;\n"
  }
]