diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index c6c8b36..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-root = true
-
-[*]
-indent_style = space
-indent_size = 2
-end_of_line = lf
-charset = utf-8
-trim_trailing_whitespace = true
-insert_final_newline = true
diff --git a/.gitignore b/.gitignore
index 92f86aa..3e27ec5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,16 @@
-.idea
-
+# Build
target
project/target
+
+# IntelliJ
+.idea
+
+# VS Code
+.vscode
+
+# Metals
+.bloop
+.bsp
+.metals
+metals.sbt
+project/project
diff --git a/.scalafix.conf b/.scalafix.conf
new file mode 100644
index 0000000..57bf7db
--- /dev/null
+++ b/.scalafix.conf
@@ -0,0 +1,17 @@
+// https://github.com/liancheng/scalafix-organize-imports
+OrganizeImports {
+ blankLines = Auto
+ coalesceToWildcardImportThreshold = 5
+ expandRelative = false
+ groupExplicitlyImportedImplicitsSeparately = false
+ groupedImports = Merge
+ groups = [
+ "*"
+ "java."
+ "scala."
+ ]
+ importSelectorsOrder = Ascii
+ importsOrder = Ascii
+ preset = DEFAULT
+ removeUnused = true
+}
diff --git a/.scalafmt.conf b/.scalafmt.conf
new file mode 100644
index 0000000..4d9c342
--- /dev/null
+++ b/.scalafmt.conf
@@ -0,0 +1,8 @@
+version = "3.0.0-RC2"
+
+runner.dialect = scala3
+
+newlines.topLevelStatements = [before]
+newlines.afterCurlyLambdaParams = preserve
+
+docstrings.wrap = "no"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bf41fc..ff2d96e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,15 @@
# Changelog
-## Version 1.0.0 (2017-11-12)
+All notable changes to this project will be documented in this file.
-### New features
-- Encode and decode JWT token
-- suppot hashing algorithms:
-- - HS256
-- - HS384
-- - HS512
-- - RS256
-- - RS384
-- - RS512
-- - ES256
-- - ES384
-- - ES512
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [1.1.0]
+
+### ✨ Feature
+
+* Add CHANGELOG to root directory
+
+## [1.0.0]
+
+* Encode and decode JWT tokens support for following hashing algorithms (HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384 and ES512)
diff --git a/README.md b/README.md
index 725036b..2fff878 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,19 @@
# spray-jwt
+
JWT library to use with spray-json and akka-http.
## Install
+
Add spray-jwt as dependency to your `build.sbt`:
-```sbtshell
+```sbt
libraryDependencies ++= Seq(
"com.github.janjaali" %% "spray-jwt" % "1.0.0"
)
```
To encode a JsValue to a JWT token:
+
```scala
import org.janjaali.sprayjwt.Jwt
import org.janjaali.sprayjwt.algorithms.HS256
@@ -20,6 +23,7 @@ val jwtOpt = Jwt.encode(payload, "super_fancy_secret", HS256)
```
And vice versa to decode JWT token as a JsValue:
+
```scala
import org.janjaali.sprayjwt.Jwt
import org.janjaali.sprayjwt.algorithms.HS256
@@ -29,17 +33,11 @@ val jsValueOpt = Jwt.decode(token, "super_fancy_secret")
```
## Supported algorithms
-- HS256
-- HS384
-- HS512
-
-- RS256
-- RS384
-- RS512
-## Source Code Style
-Check style via [scalastyle](http://www.scalastyle.org/):
+* HS256
+* HS384
+* HS512
-```sbtshell
-sbt scalastyle
-```
+* RS256
+* RS384
+* RS512
diff --git a/build.sbt b/build.sbt
index c5ca7cb..55754ef 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,70 +1,60 @@
-import scala.sys.process._
-
-name := "spray-jwt"
-
-organization := "com.github.janjaali"
-
-version := "1.0.0"
-
-licenses := Seq("MIT License" -> url("https://opensource.org/licenses/MIT"))
-
-homepage := Some(url("https://github.com/janjaali/spray-jwt"))
-
-scmInfo := Some(
- ScmInfo(
- url("https://github.com/janjaali/spray-jwt"),
- "scm:git@github.com/janjaali/spray-jwt.git"
- )
-)
-
-developers := List(
- Developer(
- id = "ghashange",
- name = "ghashange",
- email = "",
- url = url("https://github.com/janjaali")
+ThisBuild / scalaVersion := "3.0.0-RC3"
+
+ThisBuild / versionScheme := Some("early-semver")
+
+lazy val sprayJwt = (project in file("spray-jwt"))
+ .settings(
+ name := "spray-jwt",
+ organization := "com.github.janjaali",
+ version := "1.0.0",
+
+ licenses := Seq(
+ "MIT License" -> url("https://opensource.org/licenses/MIT")
+ ),
+ homepage := Some(url("https://github.com/janjaali/spray-jwt")),
+ scmInfo := Some(
+ ScmInfo(
+ browseUrl = url("https://github.com/janjaali/spray-jwt"),
+ connection = "scm:git@github.com/janjaali/spray-jwt.git"
+ )
+ ),
+ developers := List(
+ Developer(
+ id = "janjaali",
+ name = "janjaali",
+ email = "",
+ url = url("https://github.com/janjaali")
+ )
+ ),
+
+ publishMavenStyle := true,
+ publishTo := {
+ val nexus = "https://oss.sonatype.org/"
+ if (isSnapshot.value) {
+ Some("snapshots" at nexus + "content/repositories/snapshots")
+ } else {
+ Some("releases" at nexus + "service/local/staging/deploy/maven2")
+ }
+ },
+
+ libraryDependencies ++= Seq(
+ // JSON
+ ("io.spray" %% "spray-json" % "1.3.6").cross(CrossVersion.for3Use2_13),
+ // Encryption
+ "org.bouncycastle" % "bcpkix-jdk15on" % "1.58",
+ // Test
+ "org.scalatest" %% "scalatest" % "3.2.8" % Test,
+ // Property based tests
+ "org.scalacheck" %% "scalacheck" % "1.15.3" % Test,
+ "org.scalatestplus" %% "scalacheck-1-15" % "3.2.8.0" % Test
+ )
)
-)
-
-scalaVersion := "2.12.3"
-
-publishMavenStyle := true
-publishArtifact in Test := false
-
-publishTo := {
- val nexus = "https://oss.sonatype.org/"
- if (isSnapshot.value) {
- Some("snapshots" at nexus + "content/repositories/snapshots")
- } else {
- Some("releases" at nexus + "service/local/staging/deploy/maven2")
+lazy val sprayJsonSupport = (project in file("spray-json-support"))
+ .dependsOn(sprayJwt % "test->test;compile->compile")
+ .settings {
+ libraryDependencies ++= Seq(
+ // Supported JSON library
+ ("io.spray" %% "spray-json" % "1.3.6").cross(CrossVersion.for3Use2_13)
+ )
}
-}
-
-val testDependencies = Seq(
- "org.scalatest" %% "scalatest" % "3.0.4" % "test"
-)
-
-val dependencies = Seq(
- "io.spray" %% "spray-json" % "1.3.3",
- "org.bouncycastle" % "bcpkix-jdk15on" % "1.58"
-)
-
-libraryDependencies ++= dependencies
-libraryDependencies ++= testDependencies
-
-lazy val scalastyleTest = taskKey[Unit]("scalastyleTest")
-scalastyleTest := (scalastyle in Test).toTask("").value
-
-(scalastyle in Compile) := ((scalastyle in Compile) dependsOn scalastyleTest).toTask("").value
-
-lazy val installGitHook = taskKey[Unit]("Installs git hooks")
-installGitHook := {
- if (sys.props("os.name").contains("Windows")) {
- "cmd /c copy scripts\\pre-commit-hook.sh .git\\hooks\\pre-commit" !
- } else {
- "cp scripts/pre-commit-hook.sh .git/hooks/pre-commit" !
- }
-}
-
-(compile in Compile) := ((compile in Compile) dependsOn installGitHook).value
diff --git a/project/build.properties b/project/build.properties
index 017bb86..7bb94aa 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version = 1.0.2
+sbt.version = 1.5.1
diff --git a/project/plugins.sbt b/project/plugins.sbt
deleted file mode 100644
index b7728a3..0000000
--- a/project/plugins.sbt
+++ /dev/null
@@ -1,3 +0,0 @@
-addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")
-
-addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0")
diff --git a/scalastyle-config.xml b/scalastyle-config.xml
deleted file mode 100644
index 66d8a97..0000000
--- a/scalastyle-config.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-
+ * val origin = List(("a", 1), ("b", 2), ("c", 3), ("b", 4), ("a", 5)) + * val result = uniqueElements(origin) { elem => elem._1 } + * + * result // => List((c,3), (b,4), (a,5)) + *+ * + * @param collection + * @param predicate + * @return sequence containing at most one element that matches the predicate + */ + def uniqueElements[T, U](collection: Seq[T])(predicate: T => U): Seq[T] = { + collection.foldRight(List.empty[T]) { case (elem, collection) => + if (collection.map(predicate).contains(predicate(elem))) { + collection + } else { + elem :: collection + } + } + } +} diff --git a/src/test/resources/org/janjaali/sprayjwt/test.rsa.private.key b/spray-jwt/src/test/resources/org/janjaali/sprayjwt/test.rsa.private.key similarity index 100% rename from src/test/resources/org/janjaali/sprayjwt/test.rsa.private.key rename to spray-jwt/src/test/resources/org/janjaali/sprayjwt/test.rsa.private.key diff --git a/src/test/resources/org/janjaali/sprayjwt/test.rsa.public.key b/spray-jwt/src/test/resources/org/janjaali/sprayjwt/test.rsa.public.key similarity index 100% rename from src/test/resources/org/janjaali/sprayjwt/test.rsa.public.key rename to spray-jwt/src/test/resources/org/janjaali/sprayjwt/test.rsa.public.key diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/algorithms/JsonSupportSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/algorithms/JsonSupportSpec.scala new file mode 100644 index 0000000..c6f9c84 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/algorithms/JsonSupportSpec.scala @@ -0,0 +1,118 @@ +package org.janjaali.sprayjwt.algorithms + +import org.janjaali.sprayjwt.encoder.{Base64UrlDecoder, Base64UrlEncoder} +import org.janjaali.sprayjwt.json.CommonJsonWriters.Implicits._ +import org.janjaali.sprayjwt.json.{ + JsonStringDeserializer, + JsonStringSerializer, + JsonValue +} +import org.janjaali.sprayjwt.jws.{Header, JoseHeader, JwsPayload, JwsSignature} +import org.janjaali.sprayjwt.jwt.{Claim, JwtClaimsSet, NumericDate} +import org.janjaali.sprayjwt.tests.ScalaTestSpec + +trait JsonSupportSpec extends ScalaTestSpec: + + protected given jsonStringSerializer: JsonStringSerializer + + protected given jsonStringDeserializer: JsonStringDeserializer + + protected def verifySignWithHmacAlgorithms(): Unit = + verifySignWithHmac256Algorithm() + verifySignWithHmac384Algorithm() + verifySignWithHmac512Algorithm() + + protected def verifyValidationWithHmacAlgorithms(): Unit = + verifyValidationWithHmac256Algorithm() + verifyValidationWithHmac384Algorithm() + verifyValidationWithHmac512Algorithm() + + private def verifySignWithHmac256Algorithm(): Unit = + verifySignWithAlgorithm( + algorithm = Algorithms.Hs256, + expectedSignature = JwsSignature( + "jUzTJEnlFeTXDUPp9vJMwoalvXJ55IZ6DaBExN08UtA" + ) + ) + + private def verifySignWithHmac384Algorithm(): Unit = + verifySignWithAlgorithm( + algorithm = Algorithms.Hs384, + expectedSignature = JwsSignature( + "tz6NV8IfhPNqEnfUgeu0TJowwvWsjcmFCiRC_F-7bTOQeUle8jomj151nYHx1-IQ" + ) + ) + + private def verifySignWithHmac512Algorithm(): Unit = + verifySignWithAlgorithm( + algorithm = Algorithms.Hs512, + expectedSignature = JwsSignature( + "dOf7rSkv-y62jQDwAuzNdNKX2jfYK2HREBYqlB0rLnlERIlWkQ4BkVbbVyGi47br1Os4FllE4yjuz_FVjabK5w" + ) + ) + + private def verifySignWithAlgorithm( + algorithm: Algorithm, + expectedSignature: JwsSignature + ): Unit = + s"Verify serializer with algorithm '${algorithm}'." in { + val joseHeader = JoseHeader( + Seq( + Header.Type(Header.Type.Value.Jwt), + // TODO: That's weird, how can we set the algorithm and then just + // sign with another one? + Header.Algorithm(algorithm) + ) + ) + + val jwsPayload = JwsPayload( + JwtClaimsSet( + Seq( + // TODO: Maybe add an iss type? + Claim.Private(name = "iss", value = "joe"), + Claim.ExpirationTime(NumericDate(1300819380L)), + Claim.Private(name = "http://example.com/is_root", value = true) + ) + ) + ) + + val secret = Secret("secret value") + + algorithm.sign(joseHeader, jwsPayload, secret) shouldBe expectedSignature + } + + private def verifyValidationWithHmac256Algorithm(): Unit = + verifyValidationWithHmacAlgorithm( + algorithmName = "Hmac256", + data = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.eMS0OOcs-0Eo5x5vDepYOcITeG4NtPrtE8seTsT1RT0" + }, + secret = Secret("secret value") + ) + + private def verifyValidationWithHmac384Algorithm(): Unit = + verifyValidationWithHmacAlgorithm( + algorithmName = "Hmac384", + data = { + "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tXvOFupOdp6ISki7ysqn_gO9LHk2n35d0f_E26d9FYfhLLHNudoWU2HXD_6Tnm7X" + }, + secret = Secret("secret value") + ) + + private def verifyValidationWithHmac512Algorithm(): Unit = + verifyValidationWithHmacAlgorithm( + algorithmName = "Hmac512", + data = { + "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.mV13yTaWA-6cQWcQEmUOh2qaTnpF5hQpNNkIMY6RIlbyQQQ-MbE9zjQ19dTQpQ2hxEql5ObbmDmWzqx13ka6Iw", + }, + secret = Secret("secret value") + ) + + private def verifyValidationWithHmacAlgorithm( + algorithmName: String, + data: String, + secret: Secret + ): Unit = + s"Verify deserializer with '$algorithmName'." in { + Algorithms.validate(data, secret) shouldBe true + } diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/algorithms/ScalaCheckGenerators.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/algorithms/ScalaCheckGenerators.scala new file mode 100644 index 0000000..cb5e96e --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/algorithms/ScalaCheckGenerators.scala @@ -0,0 +1,17 @@ +package org.janjaali.sprayjwt.algorithms + +import org.scalacheck.Gen + +object ScalaCheckGenerators { + + def algorithmGen: Gen[Algorithm] = { + Gen.oneOf( + Algorithms.Hs256, + Algorithms.Hs384, + Algorithms.Hs512, + Algorithms.Rs256, + Algorithms.Rs384, + Algorithms.Rs512 + ) + } +} diff --git a/src/test/scala/org/janjaali/sprayjwt/encoder/Base64DecoderSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/Base64DecoderSpec.scala similarity index 57% rename from src/test/scala/org/janjaali/sprayjwt/encoder/Base64DecoderSpec.scala rename to spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/Base64DecoderSpec.scala index 0f7d229..6609daa 100644 --- a/src/test/scala/org/janjaali/sprayjwt/encoder/Base64DecoderSpec.scala +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/Base64DecoderSpec.scala @@ -1,12 +1,12 @@ package org.janjaali.sprayjwt.encoder -import org.scalatest.FunSpec +import org.scalatest.funspec.AnyFunSpec -class Base64DecoderSpec extends FunSpec { +class Base64DecoderSpec extends AnyFunSpec { describe("Base64Decoder") { it("decodes text as Base64 decoded byte-array") { - val decodedByteArray = Base64Decoder.decode("ZGFuY2U=") + val decodedByteArray = Base64UrlDecoder.decode("ZGFuY2U=") assert(decodedByteArray sameElements "dance".getBytes) } } diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/Base64UrlEncoderSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/Base64UrlEncoderSpec.scala new file mode 100644 index 0000000..fb1df8d --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/Base64UrlEncoderSpec.scala @@ -0,0 +1,37 @@ +package org.janjaali.sprayjwt.encoder + +import org.janjaali.sprayjwt.tests.ScalaTestSpec + +final class Base64UrlUrlEncoderSpec extends ScalaTestSpec { + + "Base64UrlEncoder" - { + + val sut = Base64UrlEncoder + + "encodes text as Base64 URL encoded String." in { + + val text = "{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}" + + sut.encode(text) shouldBe "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + } + + "encodes text as Base64 URL encoded String without padding." in { + + val text = { + "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}" + } + + sut.encode(text) shouldBe { + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly" + + "9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + } + } + + "encoded byte array as Base64 URL encoded String." in { + + val text = "{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}".getBytes("UTF-8") + + sut.encode(text) shouldBe "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + } + } +} diff --git a/src/test/scala/org/janjaali/sprayjwt/encoder/ByteEncoderSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/ByteEncoderSpec.scala similarity index 75% rename from src/test/scala/org/janjaali/sprayjwt/encoder/ByteEncoderSpec.scala rename to spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/ByteEncoderSpec.scala index dadd364..e2e7015 100644 --- a/src/test/scala/org/janjaali/sprayjwt/encoder/ByteEncoderSpec.scala +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/encoder/ByteEncoderSpec.scala @@ -1,8 +1,8 @@ package org.janjaali.sprayjwt.encoder -import org.scalatest.FunSpec +import org.scalatest.funspec.AnyFunSpec -class ByteEncoderSpec extends FunSpec { +class ByteEncoderSpec extends AnyFunSpec { describe("ByteEncoder") { it("encodes text as byte array") { diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/headers/JwtHeaderJsonProtocolSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/headers/JwtHeaderJsonProtocolSpec.scala new file mode 100644 index 0000000..dc0afe1 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/headers/JwtHeaderJsonProtocolSpec.scala @@ -0,0 +1,21 @@ +package org.janjaali.sprayjwt.headers + +import org.scalatest.funspec.AnyFunSpec +import spray.json._ +import org.janjaali.sprayjwt.algorithms.Algorithms + +class JwtHeaderJsonProtocolSpec extends AnyFunSpec { + + describe("JwtHeaderJsonProtocol trait") { + it("converts JWT-Header to JsValue") { + val jwtHeaderJson = JwtHeader(Algorithms.Hs256).toJson + assert( + jwtHeaderJson == JsObject( + "typ" -> JsString("JWT"), + "alg" -> JsString("HS256") + ) + ) + } + } + +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/json/CommonJsonWritersSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/json/CommonJsonWritersSpec.scala new file mode 100644 index 0000000..b3543de --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/json/CommonJsonWritersSpec.scala @@ -0,0 +1,48 @@ +package org.janjaali.sprayjwt.json + +import org.janjaali.sprayjwt.tests.ScalaTestSpec + +class CommonJsonWritersSpec extends ScalaTestSpec { + + private val sut = CommonJsonWriters + + "Int JsonWriter" - { + + "should write Int values as JsonNumber." in { + + sut.intJsonWriter.write(2) shouldBe JsonNumber(2) + } + } + + "Long JsonWriter" - { + + "should write Long values as JsonNumber." in { + + sut.longJsonWriter.write(41L) shouldBe JsonNumber(41L) + } + } + + "String JsonWriter" - { + + "should write String values as JsonString." in { + + sut.stringJsonWriter.write("string") shouldBe JsonString("string") + } + } + + "Flat value JSON writer" - { + + "should write flat JSON values for products with at least an arbitrary of 1." in { + + import CommonJsonWriters.Implicits.stringJsonWriter + + case class Prod(value: String) + + val prodExample = Prod("dance") + + sut.flatValueJsonWriter[Prod, String].write(prodExample) shouldBe { + JsonString("dance") + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/HeaderSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/HeaderSpec.scala new file mode 100644 index 0000000..605f7aa --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/HeaderSpec.scala @@ -0,0 +1,153 @@ +package org.janjaali.sprayjwt.jws + +import org.janjaali.sprayjwt.algorithms +import org.janjaali.sprayjwt.algorithms.Algorithms +import org.janjaali.sprayjwt.json.CommonJsonWriters.Implicits.jsonValueJsonWriter +import org.janjaali.sprayjwt.json.{JsonBoolean, JsonString, JsonValue} +import org.janjaali.sprayjwt.tests.ScalaCheckGeneratorsSampler._ +import org.janjaali.sprayjwt.tests.ScalaTestSpec +import org.scalacheck.Gen +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks + +class HeaderSpec extends ScalaTestSpec with ScalaCheckDrivenPropertyChecks { + + "Algorithm header" - { + + "when serialized as JSON value" - { + + "should result in a JSON string." in { + + forAll(ScalaCheckGenerators.algorithmHeaderGen) { header => + header.valueAsJson shouldBe a[JsonString] + } + } + } + } + + "Type header" - { + + "when serialized as JSON value" - { + + "should result in a JSON string." in { + + forAll(ScalaCheckGenerators.typeHeaderGen) { header => + header.valueAsJson shouldBe a[JsonString] + } + } + } + } + + "Header" - { + + "when constructed from a header name and value" - { + + "when header name is 'alg'" - { + + val headerName = "alg" + + "when value matches an 'RS256'" - { + + behave like createAlgorithmHeader( + value = JsonString("RS256"), + expectedHeader = Header.Algorithm(Algorithms.Rs256) + ) + } + + "when value matches an 'RS384'" - { + + behave like createAlgorithmHeader( + value = JsonString("RS384"), + expectedHeader = Header.Algorithm(Algorithms.Rs384) + ) + } + + "when value matches an 'RS512'" - { + + behave like createAlgorithmHeader( + value = JsonString("RS512"), + expectedHeader = Header.Algorithm(Algorithms.Rs512) + ) + } + + "when value matches an 'HS256'" - { + + behave like createAlgorithmHeader( + value = JsonString("HS256"), + expectedHeader = Header.Algorithm(Algorithms.Hs256) + ) + } + + "when value matches an 'HS384'" - { + + behave like createAlgorithmHeader( + value = JsonString("HS384"), + expectedHeader = Header.Algorithm(Algorithms.Hs384) + ) + } + + "when value matches an 'HS512'" - { + + behave like createAlgorithmHeader( + value = JsonString("HS512"), + expectedHeader = Header.Algorithm(Algorithms.Hs512) + ) + } + + def createAlgorithmHeader( + value: JsonString, + expectedHeader: Header.Algorithm + ): Unit = { + + s"should create $expectedHeader." in { + Header(headerName, value) shouldBe expectedHeader + } + } + + "when value does not match an algorithm" - { + + "should create a private header." in { + + Header(headerName, JsonBoolean(false)) shouldBe { + Header.Private[JsonValue](headerName, JsonBoolean(false)) + } + } + } + } + + "when header name is 'typ'" - { + + val headerName = "typ" + + "when value matches a type value" - { + + "should create a type header." in { + + Header(headerName, JsonString("JWT")) shouldBe { + Header.Type(Header.Type.Value.Jwt) + } + } + } + + "when value does not match a type value" - { + + "should create a private header." in { + + Header(headerName, JsonBoolean(false)) shouldBe { + Header.Private[JsonValue](headerName, JsonBoolean(false)) + } + } + } + } + + "when header name does not match 'alg' nor 'typ'" - { + + "should create a private header." in { + + Header("arbitrary", JsonBoolean(false)) shouldBe { + Header.Private[JsonValue]("arbitrary", JsonBoolean(false)) + } + } + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/JoseHeaderSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/JoseHeaderSpec.scala new file mode 100644 index 0000000..4d5c255 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/JoseHeaderSpec.scala @@ -0,0 +1,140 @@ +package org.janjaali.sprayjwt.jws + +import org.janjaali.sprayjwt.algorithms.Algorithm +import org.janjaali.sprayjwt.json.{JsonNumber, JsonObject} +import org.janjaali.sprayjwt.tests.{ScalaCheckGeneratorsSampler, ScalaTestSpec} +import org.scalacheck.Gen +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks +import org.janjaali.sprayjwt.json.JsonValue + +class JoseHeaderSpec extends ScalaTestSpec with ScalaCheckDrivenPropertyChecks { + + "Jose Header" - { + + "when constructed from a sequence of headers" - { + + "should not contain headers with the same name." in { + + forAll(ScalaCheckGenerators.headersGen) { headers => + val headerNames = JoseHeader(headers).headers.map(_.name) + + headerNames.toSet.size shouldBe headerNames.size + } + } + + "should add headers with distinct names." in { + + forAll(ScalaCheckGenerators.headersGen) { headers => + val distinctNamedHeaders = { + headers.groupBy(_.name).values.map(_.head).toSeq + } + + val joseHeader = JoseHeader(distinctNamedHeaders) + + joseHeader.headers should contain theSameElementsAs { + distinctNamedHeaders + } + } + } + + "with headers with the same name" - { + + "should add the later ones." in { + + import org.janjaali.sprayjwt.json.CommonJsonWriters.Implicits._ + + val joseHeaders = JoseHeader( + List(Header.Private("name", 1), Header.Private("name", 3)) + ) + + joseHeaders.headers should contain only Header.Private("name", 3) + } + } + } + + "when constructed from a JSON object" - { + + "should not contain headers with the same name." in { + + forAll(ScalaCheckGenerators.headersGen) { headers => + + val json = { + JsonObject( + headers.map { header => + header.name -> header.valueAsJson + }.toMap + ) + } + + val headerNames = JoseHeader(json).headers.map(_.name) + + headerNames.toSet.size shouldBe headerNames.size + } + } + + "should add headers with distinct names." in { + + forAll(ScalaCheckGenerators.headersGen) { headers => + val distinctNamedHeaders = { + headers.groupBy(_.name).values.map(_.head).toSeq + } + + val json = { + JsonObject( + distinctNamedHeaders.map { header => + header.name -> header.valueAsJson + }.toMap + ) + } + + val joseHeader = JoseHeader(json) + + joseHeader.headers.map(_.name) should contain theSameElementsAs { + distinctNamedHeaders.map(_.name) + } + } + } + + "with headers with the same name" - { + + "should add the later ones." in { + + import org.janjaali.sprayjwt.json.CommonJsonWriters.Implicits._ + + val joseHeaders = JoseHeader( + JsonObject( + Map( + "name" -> JsonNumber(1), + "name" -> JsonNumber(3) + ) + ) + ) + + joseHeaders.headers should contain only { + Header.Private[JsonValue]( + "name", + JsonNumber(3) + ) // TODO: JsonNumber(3) should be 3 - grant implicit to deserialize such JSON values in private headers + } + } + } + } + + "when serialized as JSON" - { + + "should result in a JSON object." in { + + forAll(ScalaCheckGenerators.joseHeader) { joseHeader => + + val jsonObject = joseHeader.asJson + + val expectedMembers = joseHeader.headers.map { header => + header.name -> header.valueAsJson + } + + jsonObject.members should contain theSameElementsAs expectedMembers + } + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/JwsPayloadSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/JwsPayloadSpec.scala new file mode 100644 index 0000000..ca892d0 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/JwsPayloadSpec.scala @@ -0,0 +1,26 @@ +package org.janjaali.sprayjwt.jws + +import org.janjaali.sprayjwt.tests.ScalaTestSpec +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks + +class JwsPayloadSpec extends ScalaTestSpec with ScalaCheckDrivenPropertyChecks { + + "JWS payload" - { + + "should serialize claims as JSON object." in { + + forAll(ScalaCheckGenerators.jwsPayloadGen) { jwsPayload => + val jwsPayloadAsJsonObject = jwsPayload.asJson + + val expectedJsonObjectMembers = { + + jwsPayload.claimsSet.claims.map { claim => + claim.name -> claim.valueAsJson + }.toMap + } + + jwsPayloadAsJsonObject.members should contain theSameElementsAs expectedJsonObjectMembers + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/ScalaCheckGenerators.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/ScalaCheckGenerators.scala new file mode 100644 index 0000000..8c10222 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jws/ScalaCheckGenerators.scala @@ -0,0 +1,54 @@ +package org.janjaali.sprayjwt.jws + +import org.janjaali.sprayjwt.{algorithms, jwt} +import org.scalacheck.{Arbitrary, Gen} +import org.janjaali.sprayjwt.json.JsonWriter + +object ScalaCheckGenerators { + + def algorithmHeaderGen: Gen[Header.Algorithm] = { + algorithms.ScalaCheckGenerators.algorithmGen.map(Header.Algorithm.apply) + } + + def typeHeaderGen: Gen[Header.Type] = { + + import Header.Type + + Type(Type.Value.Jwt) + } + + def privateHeaderGen[T: JsonWriter: Arbitrary]: Gen[ + Header.Private[T] + ] = { + + val valueGen = implicitly[Arbitrary[T]].arbitrary + + for { + name <- Gen.alphaStr + value <- valueGen + } yield Header.Private(name, value) + } + + def headerGen: Gen[Header] = { + + import org.janjaali.sprayjwt.json.CommonJsonWriters.Implicits._ + + Gen.oneOf( + algorithmHeaderGen, + typeHeaderGen, + privateHeaderGen[String] + ) + } + + def headersGen: Gen[List[Header]] = { + Gen.listOf(headerGen) + } + + def joseHeader: Gen[JoseHeader] = { + headersGen.map(JoseHeader.apply) + } + + def jwsPayloadGen: Gen[JwsPayload] = { + jwt.ScalaCheckGenerators.claimsSetGen.map(JwsPayload.apply) + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ClaimSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ClaimSpec.scala new file mode 100644 index 0000000..3c9c56a --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ClaimSpec.scala @@ -0,0 +1,18 @@ +package org.janjaali.sprayjwt.jwt + +import org.janjaali.sprayjwt.json.JsonNumber +import org.janjaali.sprayjwt.tests.ScalaTestSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class ClaimSpec extends ScalaTestSpec with ScalaCheckPropertyChecks { + + "Expiration time claim" - { + + "value should be JSON serialized as a JSON number." in { + + forAll(ScalaCheckGenerators.expirationTimeClaimGen) { claim => + claim.valueAsJson shouldBe JsonNumber(claim.value.secondsSinceEpoch) + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ClaimsSetSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ClaimsSetSpec.scala new file mode 100644 index 0000000..caabc0c --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ClaimsSetSpec.scala @@ -0,0 +1,57 @@ +package org.janjaali.sprayjwt.jwt + +import org.janjaali.sprayjwt.tests.ScalaTestSpec +import org.janjaali.sprayjwt.json.CommonJsonWriters.Implicits._ +import org.scalacheck.Gen +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks + +class ClaimsSetSpec extends ScalaTestSpec with ScalaCheckDrivenPropertyChecks { + + "Claim Set" - { + + "when constructed" - { + + val sut = JwtClaimsSet.apply _ + + "should not contain claims with the same name." in { + + forAll(ScalaCheckGenerators.claimsGen) { claims => + val headerNames = sut(claims).claims.map(_.name) + + headerNames.toSet.size shouldBe headerNames.size + } + } + + "should add all claims with uniqueNames." in { + + forAll(ScalaCheckGenerators.claimsGen) { claims => + val distinctNamedClaims = { + claims.groupBy(_.name).values.map(_.head).toSeq + } + + val claimsSet = sut(distinctNamedClaims) + + claimsSet.claims should contain theSameElementsAs { + distinctNamedClaims + } + } + } + + "with claims with the same name" - { + + "should add the later ones." in { + val claimsSet = sut( + List( + Claim.Private("name", 1), + Claim.Private("name", 4) + ) + ) + + claimsSet.claims should contain only { + Claim.Private("name", 4) + } + } + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/NumericDateSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/NumericDateSpec.scala new file mode 100644 index 0000000..5b12d7e --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/NumericDateSpec.scala @@ -0,0 +1,25 @@ +package org.janjaali.sprayjwt.jwt + +import org.janjaali.sprayjwt.json.JsonNumber +import org.janjaali.sprayjwt.tests.ScalaTestSpec +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks + +class NumericDateSpec extends ScalaTestSpec with ScalaCheckDrivenPropertyChecks { + + "Numeric date" - { + + "JSON writer" - { + + val sut = NumericDate.jsonWriter + + "should writer numeric dates as JSON number." in { + + forAll(ScalaCheckGenerators.numericDateGen) { numericDate => + sut.write(numericDate) shouldBe { + JsonNumber(numericDate.secondsSinceEpoch) + } + } + } + } + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ScalaCheckGenerators.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ScalaCheckGenerators.scala new file mode 100644 index 0000000..d25e433 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/jwt/ScalaCheckGenerators.scala @@ -0,0 +1,65 @@ +package org.janjaali.sprayjwt.jwt + +import org.janjaali.sprayjwt.json.CommonJsonWriters +import org.scalacheck.Gen + +import java.time.Instant + +object ScalaCheckGenerators { + + /** Generator for numeric dates containing epoch seconds in the time frame of: + * [now - 100 years, now + 100 years]. + * + * @return generator for numeric date + */ + def numericDateGen: Gen[NumericDate] = { + + import scala.concurrent.duration._ + + val aHundredYearsInSeconds = (365.days * 10).toSeconds + + val now = Instant.now() + + Gen + .chooseNum( + now.minusSeconds(aHundredYearsInSeconds).getEpochSecond(), + now.plusSeconds(aHundredYearsInSeconds).getEpochSecond() + ) + .map(NumericDate.apply) + } + + /** Generator for an expiration time claim that contains a numeric date time + * in the time interval of: [now - 100 years, now + 100 years]. + * + * @return generator for expiration time + */ + def expirationTimeClaimGen: Gen[Claim.ExpirationTime] = { + numericDateGen.map(Claim.ExpirationTime.apply) + } + + def privateClaimGen: Gen[Claim.Private[_]] = { + + import CommonJsonWriters.Implicits.stringJsonWriter + + for { + name <- Gen.alphaStr + value <- Gen.alphaStr + } yield Claim.Private(name, value) + } + + def claimGen: Gen[Claim] = { + + Gen.oneOf( + expirationTimeClaimGen, + privateClaimGen + ) + } + + def claimsGen: Gen[List[Claim]] = { + Gen.listOf(claimGen) + } + + def claimsSetGen: Gen[JwtClaimsSet] = { + claimsGen.map(JwtClaimsSet.apply) + } +} diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/tests/ScalaCheckGeneratorsSampler.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/tests/ScalaCheckGeneratorsSampler.scala new file mode 100644 index 0000000..2b69c23 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/tests/ScalaCheckGeneratorsSampler.scala @@ -0,0 +1,23 @@ +package org.janjaali.sprayjwt.tests + +import org.scalacheck.Gen + +/** Provides sampler based on ScalaCheck generators and enables to do the + * following: + * + *
+ * val gen: Gen[T] = ??? + * val t: T = gen.get + *+ */ +trait ScalaCheckGeneratorsSampler { + + implicit class Sampler[T](gen: Gen[T]) { + + def get: T = { + gen.sample.get + } + } +} + +object ScalaCheckGeneratorsSampler extends ScalaCheckGeneratorsSampler diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/tests/ScalaTestSpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/tests/ScalaTestSpec.scala new file mode 100644 index 0000000..16d8dc1 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/tests/ScalaTestSpec.scala @@ -0,0 +1,6 @@ +package org.janjaali.sprayjwt.tests + +import org.scalatest.matchers.should +import org.scalatest.freespec.AnyFreeSpec + +trait ScalaTestSpec extends AnyFreeSpec with should.Matchers diff --git a/spray-jwt/src/test/scala/org/janjaali/sprayjwt/util/CollectionsFactorySpec.scala b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/util/CollectionsFactorySpec.scala new file mode 100644 index 0000000..2fb1330 --- /dev/null +++ b/spray-jwt/src/test/scala/org/janjaali/sprayjwt/util/CollectionsFactorySpec.scala @@ -0,0 +1,38 @@ +package org.janjaali.sprayjwt.util + +import org.janjaali.sprayjwt.tests.ScalaTestSpec + +class CollectionsFactorySpec extends ScalaTestSpec { + + "CollectionsFactory" - { + + val sut = CollectionsFactory + + "should create collections containing at most one element for a given predicate" - { + + "when empty should be empty" in { + + sut.uniqueElements(Nil)(_ => true) shouldBe Nil + } + + "when predicate matches all should contain last matching element." in { + + sut.uniqueElements(List(1, 2, 3))(_ < 10) shouldBe List(3) + } + + "when predicate matches none should contain last none matching element." in { + + sut.uniqueElements(List(1, 2, 3))(_ > 10) shouldBe List(3) + } + + "when predicate matches multiple elements should contain last matching element." in { + + val pairs = List(("a", 1), ("b", 2), ("c", 3), ("b", 4), ("a", 5)) + + sut.uniqueElements(pairs)(elem => elem._1) shouldBe { + List(("c", 3), ("b", 4), ("a", 5)) + } + } + } + } +} diff --git a/src/main/scala/org/janjaali/sprayjwt/Jwt.scala b/src/main/scala/org/janjaali/sprayjwt/Jwt.scala deleted file mode 100644 index 8555b1f..0000000 --- a/src/main/scala/org/janjaali/sprayjwt/Jwt.scala +++ /dev/null @@ -1,172 +0,0 @@ -package org.janjaali.sprayjwt - -import java.security.Security - -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.janjaali.sprayjwt.algorithms.HashingAlgorithm -import org.janjaali.sprayjwt.encoder.{Base64Decoder, Base64Encoder} -import org.janjaali.sprayjwt.exceptions.{InvalidJwtException, InvalidSignatureException} -import org.janjaali.sprayjwt.headers.{JwtHeader, JwtHeaderJsonWriter} -import spray.json._ - -import scala.util.{Success, Try} - -/** - * JWT Encoder/Decoder. - */ -object Jwt { - - addBouncyCastleProvider() - - /** - * Encodes payload as JWT. - * - * @param payload the payload to encode as JWT's payload - * @param secret the secret which is used to sign JWT - * @param algorithm the hashing algorithm used for encoding - * @return encoded JWT - */ - def encode(payload: String, secret: String, algorithm: HashingAlgorithm): Try[String] = { - Try(encode(payload.parseJson, secret, algorithm, None)) - } - - /** - * Encodes payload as JWT. - * - * @param payload the payload to encode as JWT's payload - * @param secret the secret which is used to sign JWT - * @param algorithm the hashing algorithm used for encoding - * @param jwtClaims reserved JWT claims to add to payload (specified claims will overwrite equal named claims in - * payload) - * @return encoded JWT - */ - def encode(payload: String, secret: String, algorithm: HashingAlgorithm, jwtClaims: JwtClaims): Try[String] = { - Try(encode(payload.parseJson, secret, algorithm, Some(jwtClaims))) - } - - /** - * Encodes payload as JWT. - * - * @param payload the payload to encode as JWT's payload - * @param secret the secret which is used to sign JWT - * @param algorithm the hashing algorithm used for encoding - * @return encoded JWT - */ - def encode(payload: JsValue, secret: String, algorithm: HashingAlgorithm): Try[String] = { - Try(encode(payload, secret, algorithm, None)) - } - - /** - * Encodes payload as JWT. - * - * @param payload the payload to encode as JWT's payload - * @param secret the secret which is used to sign JWT - * @param algorithm the hashing algorithm used for encoding - * @param jwtClaims reserved JWT claims to add to payload (specified claims will overwrite equal named claims in - * payload) - * @return encoded JWT - */ - def encode(payload: JsValue, secret: String, algorithm: HashingAlgorithm, jwtClaims: JwtClaims): Try[String] = { - Try(encode(payload, secret, algorithm, Some(jwtClaims))) - } - - /** - * Decodes JWT token as JsValue. - * - * @param token the JWT token to decode - * @param secret the secret to use to validate signature of JWT - * @return JsValue decoded JWT - */ - def decode(token: String, secret: String): Try[JsValue] = { - decodeAsString(token, secret).map(_.parseJson) - } - - /** - * Decodes JWT token as JsValue. - * - * @param token the JWT token to decode - * @param secret the secret to use to validate signature of JWT - * @return JsValue decoded JWT - */ - def decodeAsString(token: String, secret: String): Try[String] = { - val splitToken = token.split("\\.") - if (splitToken.length != 3) { - throw new InvalidJwtException("JWT must have form header.payload.signature") - } - - val header = splitToken(0) - val payload = splitToken(1) - val data = s"$header.$payload" - - val signature = splitToken(2) - - val algorithm = getAlgorithmFromHeader(header) - - if (!algorithm.validate(data, signature, secret)) { - throw new InvalidSignatureException() - } - - val payloadDecoded = Base64Decoder.decodeAsString(payload) - Success(payloadDecoded) - } - - private def getAlgorithmFromHeader(header: String): HashingAlgorithm = { - val headerDecoded = Base64Decoder.decodeAsString(header) - val jwtHeader = headerDecoded.parseJson.convertTo[JwtHeader] - jwtHeader.algorithm - } - - private def encode(payload: JsValue, secret: String, algorithm: HashingAlgorithm, jwtClaims: Option[JwtClaims]): String = { - val fields = payload.asJsObject.fields - val reversedClaims = jwtClaims.map(getReversedClaims).getOrElse(Map.empty) - - val payloadWithReservedClaims = JsObject(fields ++ reversedClaims) - - val encodedHeader = getEncodedHeader(algorithm) - val encodedPayload = Base64Encoder.encode(payloadWithReservedClaims.toString) - - val encodedData = s"$encodedHeader.$encodedPayload" - - val signature = algorithm.sign(encodedData, secret) - s"$encodedData.$signature" - } - - private def addBouncyCastleProvider(): Unit = { - if (Security.getProvider("BC") == null) { - Security.addProvider(new BouncyCastleProvider) - } - } - - private def getEncodedHeader(algorithm: HashingAlgorithm): String = { - val header = JwtHeader(algorithm).toJson.toString - Base64Encoder.encode(header) - } - - private def getReversedClaims(jwtClaims: JwtClaims): Map[String, JsValue] = { - Seq( - "iss" -> jwtClaims.iss, - "sub" -> jwtClaims.sub, - "aud" -> jwtClaims.aud, - "exp" -> jwtClaims.exp, - "nbf" -> jwtClaims.nbf, - "isa" -> jwtClaims.isa, - "iat" -> jwtClaims.iat, - "jti" -> jwtClaims.jti - ).filter(_._2.nonEmpty) - .map(entry => entry._1 -> entry._2.get) - .map { - case (name, value: String) => name -> JsString(value) - case (name, value: Long) => name -> JsNumber(value) - case (name, values: Set[_]) => - if (values.size == 1) { - name -> JsString(values.head.asInstanceOf[String]) - } else { - name -> JsArray(values.map(v => JsString(v.asInstanceOf[String])).toVector) - } - - case (name, _) => throw new SerializationException(s"Cannot serialize reserved claim: $name") - } - .toMap - } - -} diff --git a/src/main/scala/org/janjaali/sprayjwt/JwtClaims.scala b/src/main/scala/org/janjaali/sprayjwt/JwtClaims.scala deleted file mode 100644 index f1a46e6..0000000 --- a/src/main/scala/org/janjaali/sprayjwt/JwtClaims.scala +++ /dev/null @@ -1,12 +0,0 @@ -package org.janjaali.sprayjwt - -case class JwtClaims( - iss: Option[String] = None, - sub: Option[String] = None, - aud: Option[Set[String]] = None, - exp: Option[Long] = None, - nbf: Option[Long] = None, - isa: Option[Long] = None, - iat: Option[Long] = None, - jti: Option[String] = None -) diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/HS256.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/HS256.scala deleted file mode 100644 index 911a727..0000000 --- a/src/main/scala/org/janjaali/sprayjwt/algorithms/HS256.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.janjaali.sprayjwt.algorithms - -/** - * Represents HS256 hashing algorithm. - */ -case object HS256 extends HmacAlgorithm("HS256") { - - override val cryptoAlgName = "HMACSHA256" - -} diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/HS384.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/HS384.scala deleted file mode 100644 index 687307e..0000000 --- a/src/main/scala/org/janjaali/sprayjwt/algorithms/HS384.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.janjaali.sprayjwt.algorithms - -/** - * Represents HS384 hashing algorithm. - */ -case object HS384 extends HmacAlgorithm("HS384") { - - override val cryptoAlgName = "HMACSHA384" - -} diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/HS512.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/HS512.scala deleted file mode 100644 index c6d1ac0..0000000 --- a/src/main/scala/org/janjaali/sprayjwt/algorithms/HS512.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.janjaali.sprayjwt.algorithms - -/** - * Represents HS512 hashing algorithm. - */ -case object HS512 extends HmacAlgorithm("HS512") { - - override val cryptoAlgName = "HMACSHA512" - -} diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/HashingAlgorithm.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/HashingAlgorithm.scala deleted file mode 100644 index 8a3fa8f..0000000 --- a/src/main/scala/org/janjaali/sprayjwt/algorithms/HashingAlgorithm.scala +++ /dev/null @@ -1,42 +0,0 @@ -package org.janjaali.sprayjwt.algorithms - -/** - * Companion object to map Strings as hashing algorithms. - */ -object HashingAlgorithm { - def apply(name: String): Option[HashingAlgorithm] = name match { - case "HS256" => Some(HS256) - case "HS384" => Some(HS384) - case "HS512" => Some(HS512) - case "RS256" => Some(RS256) - case "RS384" => Some(RS384) - case "RS512" => Some(RS512) - case _ => None - } -} - -/** - * Represents hashing algorithms used for JWT's. - * - * @param name the name of the hashing algorithm - */ -private[sprayjwt] abstract class HashingAlgorithm(val name: String) { - /** - * Signs data. - * - * @param data the data to sign - * @param secret the secret to use for signing the data - * @return signed data - */ - def sign(data: String, secret: String): String - - /** - * Validates signature. - * - * @param data the data to validate signature for - * @param signature the signature to validate - * @param secret the secret to use for validation - * @return
true
if signature is valid, otherwise returns false
- */
- def validate(data: String, signature: String, secret: String): Boolean
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/HmacAlgorithm.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/HmacAlgorithm.scala
deleted file mode 100644
index bcf9b35..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/algorithms/HmacAlgorithm.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.janjaali.sprayjwt.algorithms
-
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
-
-import org.janjaali.sprayjwt.encoder.{Base64Encoder, ByteEncoder}
-
-/**
- * Represents Hmac hashing algorithms.
- *
- * @param name the name of the hashing algorithm
- */
-private[sprayjwt] abstract class HmacAlgorithm(override val name: String) extends HashingAlgorithm(name) {
-
- private val provider = "SunJCE"
-
- /**
- * Hashing algorithm name used by SunJCE/BouncyCastle.
- */
- protected val cryptoAlgName: String
-
- override def sign(data: String, secret: String): String = {
- val secretAsByteArray = ByteEncoder.getBytes(secret)
- val secretKey = new SecretKeySpec(secretAsByteArray, cryptoAlgName)
-
- val dataAsByteArray = ByteEncoder.getBytes(data)
-
- val mac = Mac.getInstance(cryptoAlgName, provider)
- mac.init(secretKey)
- val signAsByteArray = mac.doFinal(dataAsByteArray)
- Base64Encoder.encode(signAsByteArray)
- }
-
- override def validate(data: String, signature: String, secret: String): Boolean = {
- sign(data, secret) == signature
- }
-
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/RS256.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/RS256.scala
deleted file mode 100644
index de74f48..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/algorithms/RS256.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.janjaali.sprayjwt.algorithms
-
-/**
- * Represents RS256 hashing algorithm.
- */
-case object RS256 extends RsaAlgorithm("RS256") {
-
- override protected val cryptoAlgName = "SHA256withRSA"
-
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/RS384.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/RS384.scala
deleted file mode 100644
index b66919f..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/algorithms/RS384.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.janjaali.sprayjwt.algorithms
-
-/**
- * Represents RS384 hashing algorithm.
- */
-case object RS384 extends RsaAlgorithm("RS384") {
-
- override protected val cryptoAlgName = "SHA384withRSA"
-
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/RS512.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/RS512.scala
deleted file mode 100644
index 5182fbf..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/algorithms/RS512.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.janjaali.sprayjwt.algorithms
-
-/**
- * Represents RS512 hashing algorithm.
- */
-case object RS512 extends RsaAlgorithm("RS512") {
-
- override protected val cryptoAlgName = "SHA512withRSA"
-
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/algorithms/RsaAlgorithm.scala b/src/main/scala/org/janjaali/sprayjwt/algorithms/RsaAlgorithm.scala
deleted file mode 100644
index ed73711..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/algorithms/RsaAlgorithm.scala
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.janjaali.sprayjwt.algorithms
-
-import java.io.{IOException, StringReader}
-import java.security.{PrivateKey, PublicKey, Signature}
-
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
-import org.bouncycastle.openssl.{PEMKeyPair, PEMParser}
-import org.janjaali.sprayjwt.encoder.{Base64Decoder, Base64Encoder, ByteEncoder}
-
-/**
- * Represents RSA hashing algorithms.
- *
- * @param name the name of the hashing algorithm
- */
-private[sprayjwt] abstract class RsaAlgorithm(override val name: String) extends HashingAlgorithm(name) {
-
- private val provider = "BC"
-
- /**
- * Hashing algorithm name used by SunJCE/BouncyCastle.
- */
- protected val cryptoAlgName: String
-
- override def sign(data: String, secret: String): String = {
- val key = getPrivateKey(secret)
-
- val dataByteArray = ByteEncoder.getBytes(data)
-
- val signature = Signature.getInstance(cryptoAlgName, provider)
- signature.initSign(key)
- signature.update(dataByteArray)
- val signatureByteArray = signature.sign
- Base64Encoder.encode(signatureByteArray)
- }
-
- override def validate(data: String, signature: String, secret: String): Boolean = {
- val key = getPublicKey(secret)
-
- val dataByteArray = ByteEncoder.getBytes(data)
-
- val rsaSignature = Signature.getInstance(cryptoAlgName, provider)
- rsaSignature.initVerify(key)
- rsaSignature.update(dataByteArray)
- rsaSignature.verify(Base64Decoder.decode(signature))
- }
-
- private def getPublicKey(str: String): PublicKey = {
- val pemParser = new PEMParser(new StringReader(str))
- val keyPair = pemParser.readObject()
-
- Option(keyPair) match {
- case Some(publicKeyInfo: SubjectPublicKeyInfo) =>
- val converter = new JcaPEMKeyConverter
- converter.getPublicKey(publicKeyInfo)
- case _ => throw new IOException(s"Invalid key for $cryptoAlgName")
- }
- }
-
- private def getPrivateKey(str: String): PrivateKey = {
- val pemParser = new PEMParser(new StringReader(str))
- val keyPair = pemParser.readObject()
-
- Option(keyPair) match {
- case Some(keyPair: PEMKeyPair) =>
- val converter = new JcaPEMKeyConverter
- converter.getKeyPair(keyPair).getPrivate
- case _ => throw new IOException(s"Invalid key for $cryptoAlgName")
- }
- }
-
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/encoder/Base64Decoder.scala b/src/main/scala/org/janjaali/sprayjwt/encoder/Base64Decoder.scala
deleted file mode 100644
index 85c111c..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/encoder/Base64Decoder.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.janjaali.sprayjwt.encoder
-
-import java.util.Base64
-
-/**
- * Base64Decoder utility class.
- */
-private[sprayjwt] object Base64Decoder {
-
- private lazy val base64Decoder: Base64.Decoder = Base64.getDecoder
-
- /**
- * Decodes Base64 encoded text as ByteArray.
- *
- * @param text the text to decode as ByteArray
- * @return Base64 decoded ByteArray
- */
- def decode(text: String): Array[Byte] = {
- base64Decoder.decode(text)
- }
-
- /**
- * Decodes Base64 decoded text as String.
- *
- * @param text the text to decode as String
- * @return Base64 decoded String
- */
- def decodeAsString(text: String): String = {
- new String(decode(text))
- }
-
-}
diff --git a/src/main/scala/org/janjaali/sprayjwt/encoder/Base64Encoder.scala b/src/main/scala/org/janjaali/sprayjwt/encoder/Base64Encoder.scala
deleted file mode 100644
index b32c805..0000000
--- a/src/main/scala/org/janjaali/sprayjwt/encoder/Base64Encoder.scala
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.janjaali.sprayjwt.encoder
-
-import java.util.Base64
-
-/**
- * Base64Encoder utility class.
- */
-private[sprayjwt] object Base64Encoder {
-
- private lazy val base64Encoder: Base64.Encoder = Base64.getEncoder
-
- /**
- * Encodes text to a Base64 encoded String.
- *
- * @param text the text to encode
- * @return Base64 encoded String
- */
- def encode(text: String): String = {
- val textAsByteArray = ByteEncoder.getBytes(text)
- encode(textAsByteArray)
- }
-
- /**
- * Encodes a ByteArray as String.
- *
- * @param byteArray the ByteArray to encode as String
- * @return String encoded ByteArray
- */
- def encode(byteArray: Array[Byte]): String = {
- base64Encoder.encodeToString(byteArray).replaceAll("=", "")
- }
-
-}
diff --git a/src/test/scala/org/janjaali/sprayjwt/JwtSpec.scala b/src/test/scala/org/janjaali/sprayjwt/JwtSpec.scala
deleted file mode 100644
index 3d4616e..0000000
--- a/src/test/scala/org/janjaali/sprayjwt/JwtSpec.scala
+++ /dev/null
@@ -1,277 +0,0 @@
-package org.janjaali.sprayjwt
-
-import org.janjaali.sprayjwt.algorithms._
-import org.scalatest.FunSpec
-import spray.json.{JsBoolean, JsObject, JsString}
-
-class JwtSpec extends FunSpec {
-
- describe("Jwt encodes the header to JSON") {
- describe("HS256") {
- val secret = "secret"
-
- it("encodes as JWT") {
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, HS256).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("encodes as JWT with iss") {
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, HS256, JwtClaims(iss = Some("issuer"))).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imlzc3VlciJ9.MabLKH7FNuQXlshZe6m054If8TLP5DvwYccl0ejlUVA"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("encodes JsValue as JWT") {
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jsValue = JsObject(
- "sub" -> JsString("1234567890"),
- "name" -> JsString("John Doe"),
- "admin" -> JsBoolean(true)
- )
-
- val jwt = Jwt.encode(jsValue, secret, HS256).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("encodes JsValue as JWT with iss") {
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jsValue = JsObject(
- "sub" -> JsString("1234567890"),
- "name" -> JsString("John Doe"),
- "admin" -> JsBoolean(true)
- )
-
- val jwt = Jwt.encode(jsValue, secret, HS256, JwtClaims(iss = Some("issuer"))).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imlzc3VlciJ9.MabLKH7FNuQXlshZe6m054If8TLP5DvwYccl0ejlUVA"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("encodes as JWT with all reserved claims") {
- // scalastyle:off
- val payload =
- """{"sub":"1234567890","name":"John Doe","admin":true}"""
-
- val jwt = Jwt.encode(payload, secret, HS256, JwtClaims(
- iss = Some("issuer"),
- sub = Some("subject"),
- aud = Some(Set("audience")),
- isa = Some(500),
- exp = Some(1000),
- nbf = Some(2000),
- iat = Some(3000),
- jti = Some("jwtId")
- )).get
-
- val expectedJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjIwMDAsImFkbWluIjp0cnVlLCJuYW1lIjoiSm9obiBEb2UiLCJqdGkiOiJqd3RJZCIsImV4cCI6MTAwMCwiaXNhIjo1MDAsImlhdCI6MzAwMCwic3ViIjoic3ViamVjdCIsImF1ZCI6ImF1ZGllbmNlIiwiaXNzIjoiaXNzdWVyIn0.2zS7vqKCLPKOlre6LYMMR/dTp41Q9jV5KiEyE9I6JLw"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("decodes JWT as String") {
- // scalastyle:off
- val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
- // scalastyle:on
-
- val decodedPayload = Jwt.decodeAsString(token, secret).get
- val expected = """{"sub":"1234567890","name":"John Doe","admin":true}"""
-
- assert(decodedPayload == expected)
- }
-
- it("decodes JWT as JsValue") {
- // scalastyle:off
- val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
- // scalastyle:on
-
- val json = Jwt.decode(token, secret).get
-
- val expected = JsObject(
- "sub" -> JsString("1234567890"),
- "name" -> JsString("John Doe"),
- "admin" -> JsBoolean(true)
- )
- assert(json == expected)
- }
-
- it("decodes JWT as JsValue with iss claim") {
- // scalastyle:off
- val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imlzc3VlciJ9.MabLKH7FNuQXlshZe6m054If8TLP5DvwYccl0ejlUVA"
- // scalastyle:on
-
- val json = Jwt.decode(token, secret).get
-
- val expected = JsObject(
- "sub" -> JsString("1234567890"),
- "name" -> JsString("John Doe"),
- "admin" -> JsBoolean(true),
- "iss" -> JsString("issuer")
- )
- assert(json == expected)
- }
- }
-
- describe("HS384") {
- val secret = "secret"
-
- it("encodes as JWT") {
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, HS384).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.DtVnCyiYCsCbg8gUP+579IC2GJ7P3CtFw6nfTTPw+0lZUzqgWAo9QIQElyxOpoRm"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("decodes JWT as String") {
- // scalastyle:off
- val token = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.DtVnCyiYCsCbg8gUP+579IC2GJ7P3CtFw6nfTTPw+0lZUzqgWAo9QIQElyxOpoRm"
- // scalastyle:on
-
- val decodedPayload = Jwt.decodeAsString(token, secret).get
-
- val expected = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- assert(decodedPayload == expected)
- }
- }
-
- describe("HS512") {
- val secret = "secret"
-
- it("encodes as JWT") {
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, HS512).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.YI0rUGDq5XdRw8vW2sDLRNFMN8Waol03iSFH8I4iLzuYK7FKHaQYWzPt0BJFGrAmKJ6SjY0mJIMZqNQJFVpkuw"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("decodes JWT as String") {
- // scalastyle:off
- val token = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.DtVnCyiYCsCbg8gUP+579IC2GJ7P3CtFw6nfTTPw+0lZUzqgWAo9QIQElyxOpoRm"
- // scalastyle:on
-
- val decodedPayload = Jwt.decodeAsString(token, secret).get
-
- val expected = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- assert(decodedPayload == expected)
- }
- }
-
- describe("RS256") {
- it("encodes as JWT") {
- val source = scala.io.Source.fromURL(getClass.getResource("test.rsa.private.key"))
- val secret = try source.mkString finally source.close
-
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, RS256).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.CqkpMs2+ttnOcZMjCk3ca2Fw0d3yGBsS5X+eEtuPhYV77ApgRAidZqZvsC1Cs5hqhX6ZTuer0UnCAQ5n4gvyLoaMiMiGqtm+UeHiUKQSeThtqf4M5ylMERi971gZV5ffXPeAHUZUPN8IiMof2BjUwOk4cN7WVfz5i80zcXAkbBUcra2uPlvVpHXGrIVI3CPpBYs4Hn3towNHX9bpWnqfvogy5TXzMEVHAF8H/TgGDwmCMuIGmi4xdlVviXTXrF/znPNNowTuI8aaXenJRYaDkI0VyN6MChmsA8aDOMMSlikDrgGzdxQSGJrBSrvrjnuJMK9raJ7dr/1U+5Rghtms+Q"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("decodes JWT as String") {
- val source = scala.io.Source.fromURL(getClass.getResource("test.rsa.public.key"))
- val public = try source.mkString finally source.close
-
- // scalastyle:off
- val token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.CqkpMs2+ttnOcZMjCk3ca2Fw0d3yGBsS5X+eEtuPhYV77ApgRAidZqZvsC1Cs5hqhX6ZTuer0UnCAQ5n4gvyLoaMiMiGqtm+UeHiUKQSeThtqf4M5ylMERi971gZV5ffXPeAHUZUPN8IiMof2BjUwOk4cN7WVfz5i80zcXAkbBUcra2uPlvVpHXGrIVI3CPpBYs4Hn3towNHX9bpWnqfvogy5TXzMEVHAF8H/TgGDwmCMuIGmi4xdlVviXTXrF/znPNNowTuI8aaXenJRYaDkI0VyN6MChmsA8aDOMMSlikDrgGzdxQSGJrBSrvrjnuJMK9raJ7dr/1U+5Rghtms+Q"
- // scalastyle:on
-
- val decoded = Jwt.decodeAsString(token, public).get
-
- val expected = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- assert(decoded == expected)
- }
- }
-
- describe("RS384") {
- it("encodes as JWT") {
- val source = scala.io.Source.fromURL(getClass.getResource("test.rsa.private.key"))
- val secret = try source.mkString finally source.close
-
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, RS384).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.oCcq/xI4jcjxMB5zRSp6F7bfQjT2KhdH4fJkN3E24wa6ltE2UufgXQ4/wJjOalJ4h0RbnEUwlMzdD3hJgNRU7BfD6r5GzVo/RLTLTkyTD+KsHXYiS4qHYOZ1otyoPFV/QzQcovoOXT+kmsVH/S6mpVzN1Qh1OUgu+2D9swH+6rZi0YctrKv3dXou+GSVt1l5xfyA7R4KB8HwONTwdyEbTSM/aJWP+Ob80kDNAEs9xkx/2KzY1iGfdh0FwIU2OKdc+b0CNlVQrbYwLX55Yk5CBPJY6UNlXwSmCFwlbZvjChkwE5MH4ICDLWN7j2llm6PX38RE2xRCguqId/iA8vwj8g"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("decodes JWT as String") {
- val source = scala.io.Source.fromURL(getClass.getResource("test.rsa.public.key"))
- val public = try source.mkString finally source.close
-
- // scalastyle:off
- val token = "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.oCcq/xI4jcjxMB5zRSp6F7bfQjT2KhdH4fJkN3E24wa6ltE2UufgXQ4/wJjOalJ4h0RbnEUwlMzdD3hJgNRU7BfD6r5GzVo/RLTLTkyTD+KsHXYiS4qHYOZ1otyoPFV/QzQcovoOXT+kmsVH/S6mpVzN1Qh1OUgu+2D9swH+6rZi0YctrKv3dXou+GSVt1l5xfyA7R4KB8HwONTwdyEbTSM/aJWP+Ob80kDNAEs9xkx/2KzY1iGfdh0FwIU2OKdc+b0CNlVQrbYwLX55Yk5CBPJY6UNlXwSmCFwlbZvjChkwE5MH4ICDLWN7j2llm6PX38RE2xRCguqId/iA8vwj8g"
- // scalastyle:on
-
- val decoded = Jwt.decodeAsString(token, public).get
-
- val expected = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- assert(decoded == expected)
- }
- }
-
- describe("RS512") {
- it("encodes as JWT") {
- val source = scala.io.Source.fromURL(getClass.getResource("test.rsa.private.key"))
- val secret = try source.mkString finally source.close
-
- val payload = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- val jwt = Jwt.encode(payload, secret, RS512).get
-
- // scalastyle:off
- val expectedJwt = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.JuhmKgWohwJS9lLhBo5ealB+RA5CX6VgzYExvUKKL/v5oqEO1bZ91Ayi949jvB5tTgvMOX3njfKpl8tyazfqoHjsXzRvHX/NdUGx1rWhWZ826Zdpgm32fO15Jv1xHbxWbFaqp0zwyLUKPo756lmg1+8IeTBdDvhC7XSlBc9cUDe4x3anltjeUseZllS2PZgQn0pxYXK5KVbAsIasDthprmaJheLBgO+CInCpDiVukUC2WfCGz9tr9IhKwNgLPkcue4uVRubOgV8By68SMZgVdxZXP70siV/sMOqrILyWk7Zi0fSm/JC4QP4fZSenfxwl8FEr4Rs+FWL/clk3fnMYQA"
- // scalastyle:on
-
- assert(jwt == expectedJwt)
- }
-
- it("decodes JWT as String") {
- val source = scala.io.Source.fromURL(getClass.getResource("test.rsa.public.key"))
- val public = try source.mkString finally source.close
-
- // scalastyle:off
- val token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.JuhmKgWohwJS9lLhBo5ealB+RA5CX6VgzYExvUKKL/v5oqEO1bZ91Ayi949jvB5tTgvMOX3njfKpl8tyazfqoHjsXzRvHX/NdUGx1rWhWZ826Zdpgm32fO15Jv1xHbxWbFaqp0zwyLUKPo756lmg1+8IeTBdDvhC7XSlBc9cUDe4x3anltjeUseZllS2PZgQn0pxYXK5KVbAsIasDthprmaJheLBgO+CInCpDiVukUC2WfCGz9tr9IhKwNgLPkcue4uVRubOgV8By68SMZgVdxZXP70siV/sMOqrILyWk7Zi0fSm/JC4QP4fZSenfxwl8FEr4Rs+FWL/clk3fnMYQA"
- // scalastyle:on
-
- val decoded = Jwt.decodeAsString(token, public).get
-
- val expected = """{"sub":"1234567890","name":"John Doe","admin":true}"""
- assert(decoded == expected)
- }
- }
- }
-
-}
diff --git a/src/test/scala/org/janjaali/sprayjwt/algorithms/HashingAlgorithmSpec.scala b/src/test/scala/org/janjaali/sprayjwt/algorithms/HashingAlgorithmSpec.scala
deleted file mode 100644
index a38a690..0000000
--- a/src/test/scala/org/janjaali/sprayjwt/algorithms/HashingAlgorithmSpec.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.janjaali.sprayjwt.algorithms
-
-import org.scalatest.FunSpec
-
-class HashingAlgorithmSpec extends FunSpec {
-
- describe("HashingAlgorithm") {
- describe("maps Strings to HashingAlgorithm") {
- it("maps HS256") {
- HashingAlgorithm("HS256") match {
- case Some(alg) => assert(alg == HS256)
- case _ => fail
- }
- }
-
- it("maps HS384") {
- HashingAlgorithm("HS384") match {
- case Some(alg) => assert(alg == HS384)
- case _ => fail
- }
- }
-
- it("maps HS512") {
- HashingAlgorithm("HS512") match {
- case Some(alg) => assert(alg == HS512)
- case _ => fail
- }
- }
- }
- }
-
-}
diff --git a/src/test/scala/org/janjaali/sprayjwt/encoder/Base64EncoderSpec.scala b/src/test/scala/org/janjaali/sprayjwt/encoder/Base64EncoderSpec.scala
deleted file mode 100644
index b59d30b..0000000
--- a/src/test/scala/org/janjaali/sprayjwt/encoder/Base64EncoderSpec.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.janjaali.sprayjwt.encoder
-
-import org.scalatest.FunSpec
-
-class Base64EncoderSpec extends FunSpec {
-
- describe("Base64Encoder") {
- it("encodes text as Base64 encoded text") {
- val encodedString = Base64Encoder.encode("""{"alg":"HS256","typ":"JWT"}""")
- assert(encodedString == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
- }
-
- it("encodes byteArray as Base64 encoded text") {
- val byteArray = """{"alg":"HS256","typ":"JWT"}""".getBytes("UTF-8")
- val encodedString = Base64Encoder.encode(byteArray)
- assert(encodedString == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
- }
- }
-
-}
diff --git a/src/test/scala/org/janjaali/sprayjwt/headers/JwtHeaderJsonProtocolSpec.scala b/src/test/scala/org/janjaali/sprayjwt/headers/JwtHeaderJsonProtocolSpec.scala
deleted file mode 100644
index 6c25539..0000000
--- a/src/test/scala/org/janjaali/sprayjwt/headers/JwtHeaderJsonProtocolSpec.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.janjaali.sprayjwt.headers
-
-import org.janjaali.sprayjwt.algorithms.HS256
-import org.scalatest.FunSpec
-import spray.json._
-
-class JwtHeaderJsonProtocolSpec extends FunSpec {
-
- describe("JwtHeaderJsonProtocol trait") {
- it("converts JWT-Header to JsValue") {
- val jwtHeaderJson = JwtHeader(HS256).toJson
- assert(jwtHeaderJson == JsObject(
- "typ" -> JsString("JWT"),
- "alg" -> JsString("HS256")
- ))
- }
- }
-
-}