Skip to content

Commit

Permalink
upgrade account and notification dependencies - init SoftPayAccount s…
Browse files Browse the repository at this point in the history
…pecifications - fix SoftPayAccount routes
  • Loading branch information
fupelaqu committed Jan 19, 2025
1 parent 07814c5 commit 5db74a9
Show file tree
Hide file tree
Showing 29 changed files with 764 additions and 142 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ jobs:
# verbose: true
- name: Run tests
run: STRIPE_CLIENT_ID=${{secrets.STRIPE_CLIENT_ID}} STRIPE_API_KEY=${{secrets.STRIPE_API_KEY}} sbt clean test
env:
STRIPE_CLIENT_ID: ${{secrets.STRIPE_CLIENT_ID}}
STRIPE_API_KEY: ${{secrets.STRIPE_API_KEY}}
# Optional: This step uploads information to the GitHub dependency graph and unblocking Dependabot alerts for the repository
# - name: Upload dependency graph
# uses: scalacenter/sbt-dependency-submission@ab086b50c947c9774b70f39fc7f6e20ca2706c91
Expand Down
2 changes: 1 addition & 1 deletion api/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ organization := "app.softnetwork.payment"
name := "softpay-api"

libraryDependencies ++= Seq(
"app.softnetwork.notification" %% "notification-api" % Versions.notification
"app.softnetwork.scheduler" %% "scheduler-api" % Versions.scheduler
)
7 changes: 7 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ ThisBuild / javacOptions ++= Seq("-source", "1.8", "-target", "1.8", "-Xlint")

ThisBuild / resolvers ++= Seq(
"Softnetwork Server" at "https://softnetwork.jfrog.io/artifactory/releases/",
"Softnetwork snapshots" at "https://softnetwork.jfrog.io/artifactory/snapshots/",
"Maven Central Server" at "https://repo1.maven.org/maven2",
"Typesafe Server" at "https://repo.typesafe.com/typesafe/releases"
)

ThisBuild / libraryDependencySchemes ++= Seq(
"app.softnetwork.notification" %% "notification-common" % VersionScheme.Always,
"app.softnetwork.notification" %% "notification-core" % VersionScheme.Always,
"app.softnetwork.notification" %% "notification-testkit" % VersionScheme.Always
)

ThisBuild / versionScheme := Some("early-semver")

val scalatest = Seq(
Expand Down
5 changes: 4 additions & 1 deletion core/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Compile / PB.protoSources := Seq(sourceDirectory.value / ".." / ".." / "client/s

libraryDependencies ++= Seq(
"app.softnetwork.persistence" %% "persistence-kv" % Versions.genericPersistence,
"app.softnetwork.account" %% "account-core" % Versions.account,
"app.softnetwork.account" %% "account-core" % Versions.account excludeAll(
ExclusionRule(organization = "app.softnetwork.notification")
),
"app.softnetwork.notification" %% "notification-common" % Versions.notification,
"app.softnetwork.session" %% "session-core" % Versions.genericPersistence
)
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,13 @@ trait SoftPayAccountBehavior extends AccountBehavior[SoftPayAccount, BasicAccoun
}

case _ =>
Effect.none.thenRun { _ =>
AccountMessages.ClientNotFound ~> replyTo
}
super.handleCommand(
entityId,
state,
RefreshAccessToken(refreshToken),
replyTo,
timers
)
}

case Some(account) if !account.status.isActive =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ trait SoftPayAccountServiceEndpoints[SD <: SessionData with SessionDataDecorator
: List[ServerEndpoint[AkkaStreams with capabilities.WebSockets, Future]] =
List(
signUp,
basic,
login,
signIn,
activate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import app.softnetwork.account.message.{
AccessTokenGenerated,
AccessTokenRefreshed,
AccountErrorMessage,
GenerateAccessToken,
Tokens
}
import app.softnetwork.account.service.OAuthService
Expand All @@ -32,73 +33,89 @@ trait SoftPayOAuthService[SD <: SessionData with SessionDataDecorator[SD]]

override val route: Route = {
pathPrefix(AccountSettings.OAuthPath) {
concat(token ~ me :: (signin ++ backup).toList: _*)
concat(token ~ client :: (signin ++ callback).toList: _*)
}
}

override lazy val token: Route =
path("token") {
post {
formField("grant_type") {
case "client_credentials" =>
formField("credentials") { credentials =>
val httpCredentials = BasicHttpCredentials(credentials)
val clientId = httpCredentials.username
val clientSecret = httpCredentials.password
run(clientId, GenerateClientToken(clientId, clientSecret)) completeWith {
case r: AccessTokenGenerated =>
complete(
StatusCodes.OK,
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
complete(
StatusCodes.BadRequest,
Map(
"error" -> "access_denied",
"error_description" -> error.message
)
)
case _ => complete(StatusCodes.BadRequest)
}
}
case "refresh_token" =>
formField("refresh_token") { refreshToken =>
run(refreshToken, RefreshClientToken(refreshToken)) completeWith {
case r: AccessTokenRefreshed =>
complete(
StatusCodes.OK,
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
complete(
StatusCodes.BadRequest,
Map(
"error" -> "access_denied",
"error_description" -> error.message
)
)
case _ => complete(StatusCodes.BadRequest)
}
}
case _ => complete(StatusCodes.BadRequest)
handlGrantType
}
}
}

override lazy val me: Route = path("me") {
protected def handlGrantType(grantType: String): Route = {
grantType match {
case "client_credentials" =>
handleClientCredentialsGrantType
case "refresh_token" =>
handleRefreshTokenGrantType
case _ => complete(StatusCodes.BadRequest)
}
}

private def handleRefreshTokenGrantType: Route = {
formField("refresh_token") { refreshToken =>
run(refreshToken, RefreshClientToken(refreshToken)) completeWith {
case r: AccessTokenRefreshed =>
complete(
StatusCodes.OK,
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
complete(
StatusCodes.BadRequest,
Map(
"error" -> "access_denied",
"error_description" -> error.message
)
)
case _ => complete(StatusCodes.BadRequest)
}
}
}

private def handleClientCredentialsGrantType: Route = {
formField("credentials") { credentials =>
val httpCredentials = BasicHttpCredentials(credentials)
val clientId = httpCredentials.username
val clientSecret = httpCredentials.password
run(clientId, GenerateClientToken(clientId, clientSecret)) completeWith {
case r: AccessTokenGenerated =>
complete(
StatusCodes.OK,
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
complete(
StatusCodes.BadRequest,
Map(
"error" -> "access_denied",
"error_description" -> error.message
)
)
case _ => complete(StatusCodes.BadRequest)
}
}
}

val pmClient: String = "me"

lazy val client: Route = path(pmClient) {
get {
handleRejections(
RejectionHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import app.softnetwork.account.message.{
AccessTokenRefreshed,
AccountErrorMessage,
BearerAuthenticationFailed,
GenerateAccessToken,
Tokens
}
import app.softnetwork.account.service.OAuthServiceEndpoints
Expand Down Expand Up @@ -49,8 +50,8 @@ trait SoftPayOAuthServiceEndpoints[SD <: SessionData with SessionDataDecorator[S
: List[ServerEndpoint[AkkaStreams with capabilities.WebSockets, Future]] =
List(
token,
me
) ++ services.map(signin) ++ services.map(backup)
client
) ++ services.map(signin) ++ services.map(callback)

override val token: ServerEndpoint[Any with AkkaStreams, Future] =
endpoint.post
Expand Down Expand Up @@ -78,54 +79,74 @@ trait SoftPayOAuthServiceEndpoints[SD <: SessionData with SessionDataDecorator[S
)
)
.out(jsonBody[Tokens])
.serverLogic {
case tokenRequest: ClientCredentials =>
import tokenRequest._
val httpCredentials = BasicHttpCredentials(credentials)
val clientId = httpCredentials.username
val clientSecret = httpCredentials.password
run(clientId, GenerateClientToken(clientId, clientSecret)) map {
case r: AccessTokenGenerated =>
Right(
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
Left(ApiErrors.BadRequest(error.message))
case _ => Left(ApiErrors.BadRequest("Unknown"))
}
case tokenRequest: RefreshToken =>
import tokenRequest._
run(refreshToken, RefreshClientToken(refreshToken)) map {
case r: AccessTokenRefreshed =>
Right(
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
Left(ApiErrors.BadRequest(error.message))
case _ => Left(ApiErrors.BadRequest("Unknown"))
}
case tokenRequest: UnsupportedGrantType =>
Future.successful(
Left(ApiErrors.BadRequest(s"Unknown grant_type ${tokenRequest.grantType}"))
)
.serverLogic { case tokenRequest: ClientTokenRequest =>
handleGrantType(tokenRequest)
}

override val me: ServerEndpoint[Any with AkkaStreams, Future] =
protected def handleGrantType(
tokenRequest: ClientTokenRequest
): Future[Either[ApiErrors.BadRequest, Tokens]] = {
tokenRequest match {
case tokenRequest: ClientCredentials =>
handleClientCredentialsGrantType(tokenRequest)
case tokenRequest: RefreshToken =>
handleRefreshTokenGrantType(tokenRequest)
case tokenRequest: UnsupportedGrantType =>
Future.successful(
Left(ApiErrors.BadRequest(s"Unknown grant_type ${tokenRequest.grantType}"))
)
}
}

private def handleRefreshTokenGrantType(
tokenRequest: RefreshToken
): Future[Either[ApiErrors.BadRequest, Tokens]] = {
run(tokenRequest.refreshToken, RefreshClientToken(tokenRequest.refreshToken)) map {
case r: AccessTokenRefreshed =>
Right(
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
Left(ApiErrors.BadRequest(error.message))
case _ => Left(ApiErrors.BadRequest("Unknown"))
}
}

private def handleClientCredentialsGrantType(
tokenRequest: ClientCredentials
): Future[Either[ApiErrors.BadRequest, Tokens]] = {
val httpCredentials = BasicHttpCredentials(tokenRequest.credentials)
val clientId = httpCredentials.username
val clientSecret = httpCredentials.password
run(clientId, GenerateClientToken(clientId, clientSecret)) map {
case r: AccessTokenGenerated =>
Right(
Tokens(
r.accessToken.token,
r.accessToken.tokenType.toLowerCase(),
r.accessToken.expiresIn,
r.accessToken.refreshToken,
r.accessToken.refreshExpiresIn
)
)
case error: AccountErrorMessage =>
Left(ApiErrors.BadRequest(error.message))
case _ => Left(ApiErrors.BadRequest("Unknown"))
}
}

val pmClient: String = "me"

lazy val client: ServerEndpoint[Any with AkkaStreams, Future] =
endpoint.get
.in(AccountSettings.OAuthPath / "me")
.description("OAuth2 me endpoint")
.in(AccountSettings.OAuthPath / pmClient)
.description("OAuth2 client endpoint")
.securityIn(auth.bearer[String](WWWAuthenticateChallenge.bearer(AccountSettings.Realm)))
.errorOut(ApiErrors.oneOfApiErrors)
.out(setCookieOpt(clientCookieName))
Expand Down Expand Up @@ -162,6 +183,21 @@ sealed trait ClientTokenRequest {
def asMap(): Map[String, String]
}

case class AuthorizationCodeRequest(
grant_type: String,
code: String,
redirect_uri: Option[String],
client_id: String
) extends ClientTokenRequest {
override def asMap(): Map[String, String] =
Map(
"grant_type" -> grant_type,
"code" -> code,
"redirect_uri" -> redirect_uri.getOrElse(""),
"client_id" -> client_id
)
}

case class ClientCredentials(credentials: String, scope: Option[String] = None)
extends ClientTokenRequest {
override def asMap(): Map[String, String] =
Expand Down Expand Up @@ -190,6 +226,13 @@ case class UnsupportedGrantType(grantType: String) extends ClientTokenRequest {
object ClientTokenRequest {
def decode(form: Map[String, String]): ClientTokenRequest = {
form.getOrElse("grant_type", "") match {
case "authorization_code" =>
AuthorizationCodeRequest(
"authorization_code",
form.getOrElse("code", ""),
form.get("redirect_uri"),
form.getOrElse("client_id", "")
)
case "client_credentials" =>
ClientCredentials(
form("credentials"),
Expand Down
Loading

0 comments on commit 5db74a9

Please sign in to comment.