Skip to content

Commit

Permalink
Merge pull request #234 from cactacea/#41_2
Browse files Browse the repository at this point in the history
Support email Authentication and password reset. (#41)
  • Loading branch information
takeshishimada authored Jul 20, 2019
2 parents 0d8cc8a + 754ebae commit c1794e9
Show file tree
Hide file tree
Showing 42 changed files with 951 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class OAuthHandler @Inject()(

def findUser(username: String, password: String): Future[Option[OAuthUser]] = {
accountsDAO.find(username, password)
.map(_.map({ a => OAuthUser(a.id, 0, new Date(), a.signedOutAt.getOrElse(0L)) }))
.map(_.map({ a => OAuthUser(a.id, 0, new Date(), 0L) }))
}

def findClientUser(clientId: String, clientSecret: String, scope: Option[String]): Future[Option[OAuthUser]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package io.github.cactacea.addons.oauth
import com.google.inject.{Inject, Singleton}
import com.twitter.finagle.oauth2._
import com.twitter.util.Future
import io.github.cactacea.backend.core.domain.models.Account
import io.github.cactacea.backend.core.infrastructure.dao.{AccountsDAO, AuthDAO}
import io.github.cactacea.backend.core.infrastructure.models.{Accounts, Clients}
import io.github.cactacea.backend.core.infrastructure.models.Clients

@Singleton
class OAuthService @Inject()(
Expand Down Expand Up @@ -47,7 +48,7 @@ class OAuthService @Inject()(
}
}

def signIn(username: String, password: String): Future[Option[Accounts]] = {
def signIn(username: String, password: String): Future[Option[Account]] = {
accountsDAO.find(username, password)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.cactacea.backend.auth.enums;

public enum TokenType {
signUp((byte)0),
signIn((byte)1),
resetPassword((byte)2);

private byte value;

private TokenType(byte value) {
this.value = value;
}

static public TokenType forName(byte value) {
for (TokenType e : values()) {
if (e.value == value) {
return e;
}
}
throw new IllegalArgumentException();
}

public byte toValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.cactacea.backend.auth.application.components.interfaces

import com.twitter.util.Future

trait MailService {

def send(address: String, toText: String, subjectText: String, bodyText: Option[String], bodyHtml: Option[String]): Future[Unit]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.cactacea.backend.auth.application.components.modules

import com.twitter.inject.TwitterModule
import io.github.cactacea.backend.auth.application.components.interfaces.MailService
import io.github.cactacea.backend.auth.application.components.services.DefaultMailService

object DefaultMailModule extends TwitterModule {

override def configure(): Unit = {
bindSingleton[MailService].to[DefaultMailService]
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.cactacea.backend.auth.application.components.services

import com.twitter.util.Future
import io.github.cactacea.backend.auth.application.components.interfaces.MailService
import io.github.cactacea.filhouette.api.Logger

class DefaultMailService extends MailService with Logger {

def send(addressText: String, toText: String, subjectText: String, bodyText: Option[String], bodyHtml: Option[String]): Future[Unit] = {
info(bodyText.getOrElse(subjectText))
Future.Done
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.github.cactacea.backend.auth.application.services

import java.util.Locale

import com.google.inject.{Inject, Singleton}
import com.twitter.finagle.http.{Request, Response}
import com.twitter.finatra.http.response.ResponseBuilder
import com.twitter.util.Future
import io.github.cactacea.backend.auth.domain.repositories.{TokensRepository, UsersRepository}
import io.github.cactacea.backend.auth.enums.TokenType
import io.github.cactacea.backend.auth.infrastructure.mailer.Mailer
import io.github.cactacea.backend.auth.utils.providers.EmailsProvider
import io.github.cactacea.backend.core.application.components.interfaces.ListenerService
import io.github.cactacea.backend.core.application.components.services.DatabaseService
import io.github.cactacea.backend.core.domain.repositories.{AccountsRepository, AuthenticationsRepository}
import io.github.cactacea.backend.core.infrastructure.identifiers.SessionId
import io.github.cactacea.backend.core.infrastructure.validators.AccountsValidator
import io.github.cactacea.filhouette.api.LoginInfo
import io.github.cactacea.filhouette.api.repositories.AuthInfoRepository
import io.github.cactacea.filhouette.api.util.{Credentials, PasswordHasherRegistry}
import io.github.cactacea.filhouette.impl.authenticators.JWTAuthenticatorService
import io.github.cactacea.filhouette.impl.providers.CredentialsProvider

@Singleton
class AuthenticationService @Inject()(
db: DatabaseService,
response: ResponseBuilder,
accountsValidator: AccountsValidator,
accountsRepository: AccountsRepository,
authenticationsRepository: AuthenticationsRepository,
authInfoRepository: AuthInfoRepository,
tokensRepository: TokensRepository,
credentialsProvider: CredentialsProvider,
passwordHasherRegistry: PasswordHasherRegistry,
authenticatorService: JWTAuthenticatorService,
mailer: Mailer,
listenerService: ListenerService
) {

import db._

def signUp(accountName: String, password: String)(implicit request: Request): Future[Response] = {
val l = LoginInfo(CredentialsProvider.ID, accountName)
transaction {
for {
_ <- authInfoRepository.add(l, passwordHasherRegistry.current.hash(password))
a <- accountsRepository.create(accountName)
_ <- authenticationsRepository.link(l.providerId, l.providerKey, a.id.toSessionId)
_ <- authenticationsRepository.confirm(l.providerId, l.providerKey)
s <- authenticatorService.create(l)
c <- authenticatorService.init(s)
r <- authenticatorService.embed(c, response.ok)
_ <- listenerService.signedUp(a)
} yield (r)
}

}

def signIn(accountName: String, password: String)(implicit request: Request): Future[Response] = {
transaction {
for {
l <- credentialsProvider.authenticate(Credentials(accountName, password))
s <- authenticatorService.create(l)
c <- authenticatorService.init(s)
r <- authenticatorService.embed(c, response.ok)
} yield (r)
}
}

def changeAccountName(accountName: String, sessionId: SessionId): Future[Unit] = {
db.transaction {
for {
_ <- authenticationsRepository.updateAccountName(CredentialsProvider.ID, accountName, sessionId)
_ <- listenerService.accountNameUpdated(accountName, sessionId)
} yield (())
}
}

def changePassword(password: String, sessionId: SessionId): Future[Unit] = {
for {
a <- accountsValidator.find(sessionId)
_ <- db.transaction(authInfoRepository.update(LoginInfo(CredentialsProvider.ID, a.accountName), passwordHasherRegistry.current.hash(password)))
} yield (())
}

def recoverPassword(email: String, locale: Locale): Future[Unit] = {
transaction {
authenticationsRepository.find(EmailsProvider.ID, email).flatMap(_ match {
case Some(_) =>
for {
t <- tokensRepository.issue(EmailsProvider.ID, email, TokenType.resetPassword)
_ <- mailer.forgotPassword(email, t, locale)
} yield (())
case None =>
Future.Unit
})
}
}

def resetPassword(token: String, password: String): Future[Unit] = {
transaction {
for {
l <- tokensRepository.verify(token, TokenType.resetPassword)
a <- accountsRepository.find(l.providerId, l.providerKey)
_ <- authInfoRepository.update(LoginInfo(CredentialsProvider.ID, a.accountName), passwordHasherRegistry.current.hash(password))
} yield (())
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package io.github.cactacea.backend.auth.application.services

import java.util.Locale

import com.google.inject.{Inject, Singleton}
import com.twitter.finagle.http.{Request, Response}
import com.twitter.finatra.http.response.ResponseBuilder
import com.twitter.util.Future
import io.github.cactacea.backend.auth.domain.repositories.TokensRepository
import io.github.cactacea.backend.auth.enums.TokenType
import io.github.cactacea.backend.auth.infrastructure.mailer.Mailer
import io.github.cactacea.backend.auth.utils.providers.EmailsProvider
import io.github.cactacea.backend.core.application.components.services.DatabaseService
import io.github.cactacea.backend.core.domain.repositories.{AccountsRepository, AuthenticationsRepository}
import io.github.cactacea.backend.core.infrastructure.identifiers.SessionId
import io.github.cactacea.backend.core.infrastructure.validators.AccountsValidator
import io.github.cactacea.filhouette.api.LoginInfo
import io.github.cactacea.filhouette.api.repositories.AuthInfoRepository
import io.github.cactacea.filhouette.api.util.{Credentials, PasswordHasherRegistry}
import io.github.cactacea.filhouette.impl.authenticators.JWTAuthenticatorService
import io.github.cactacea.filhouette.impl.providers.CredentialsProvider

@Singleton
class EmailAuthenticationService @Inject()(
db: DatabaseService,
response: ResponseBuilder,
accountsValidator: AccountsValidator,
accountsRepository: AccountsRepository,
authenticationsRepository: AuthenticationsRepository,
authInfoRepository: AuthInfoRepository,
tokensRepository: TokensRepository,
emailsProvider: EmailsProvider,
mailer: Mailer,
passwordHasherRegistry: PasswordHasherRegistry,
authenticatorService: JWTAuthenticatorService
) {

import db._

def register(email: String, locale: Locale): Future[Unit] = {
transaction {
for {
t <- tokensRepository.issue(EmailsProvider.ID, email, TokenType.signUp)
_ <- mailer.welcome(email, t, locale)
} yield (())
}
}

def register(email: String, password: String, locale: Locale): Future[Unit] = {
val l = LoginInfo(EmailsProvider.ID, email)
transaction {
for {
_ <- authInfoRepository.add(l, passwordHasherRegistry.current.hash(password))
t <- tokensRepository.issue(EmailsProvider.ID, email, TokenType.signUp)
_ <- mailer.welcome(email, t, locale)
} yield (())
}
}

def verify(token: String): Future[Unit] = {
transaction {
for {
l <- tokensRepository.verify(token, TokenType.signUp)
_ <- authenticationsRepository.confirm(l.providerId, l.providerKey)
} yield (())
}
}

def reject(token: String): Future[Unit] = {
transaction {
for {
l <- tokensRepository.verify(token, TokenType.signUp)
_ <- authInfoRepository.remove(l)
} yield (())
}
}

def signUp(accountName: String, token: String)(implicit request: Request): Future[Response] = {
transaction {
for {
l <- tokensRepository.verify(token, TokenType.signUp)
_ <- authenticationsRepository.findAccountId(l.providerId, l.providerKey)
a <- accountsRepository.create(accountName)
_ <- authenticationsRepository.link(l.providerId, l.providerKey, a.id.toSessionId)
s <- authenticatorService.create(l)
c <- authenticatorService.init(s)
r <- authenticatorService.embed(c, response.ok)
} yield (r)
}
}

def signUp(accountName: String, password: String, token: String)(implicit request: Request): Future[Response] = {
transaction {
for {
l <- tokensRepository.verify(token, TokenType.signUp)
_ <- authInfoRepository.add(l, passwordHasherRegistry.current.hash(password))
a <- accountsRepository.create(accountName)
_ <- authenticationsRepository.link(l.providerId, l.providerKey, a.id.toSessionId)
s <- authenticatorService.create(l)
c <- authenticatorService.init(s)
r <- authenticatorService.embed(c, response.ok)
} yield (r)
}
}

def signIn(email: String, password: String)(implicit request: Request): Future[Response] = {
transaction {
for {
l <- emailsProvider.authenticate(Credentials(email, password))
s <- authenticatorService.create(l)
c <- authenticatorService.init(s)
r <- authenticatorService.embed(c, response.ok)
} yield (r)
}
}

def changePassword(password: String, sessionId: SessionId): Future[Unit] = {
for {
a <- accountsValidator.find(sessionId)
_ <- db.transaction(authInfoRepository.update(LoginInfo(EmailsProvider.ID, a.accountName), passwordHasherRegistry.current.hash(password)))
} yield (())
}

def recoverPassword(email: String, locale: Locale): Future[Unit] = {
transaction {
authenticationsRepository.find(EmailsProvider.ID, email).flatMap(_ match {
case Some(_) =>
for {
t <- tokensRepository.issue(CredentialsProvider.ID, email, TokenType.resetPassword)
_ <- mailer.forgotPassword(email, t, locale)
} yield (())
case None =>
Future.Unit
})
}
}

def resetPassword(token: String, password: String): Future[Unit] = {
transaction {
for {
l <- tokensRepository.verify(token, TokenType.resetPassword)
_ <- authenticationsRepository.exist(l.providerId, l.providerKey)
_ <- authInfoRepository.update(l, passwordHasherRegistry.current.hash(password))
} yield (())
}
}


}
Loading

0 comments on commit c1794e9

Please sign in to comment.