Skip to content

Commit

Permalink
Password resets: Move to generated DAO
Browse files Browse the repository at this point in the history
  • Loading branch information
mbryzek committed Aug 2, 2024
1 parent f5ecde4 commit 561e2db
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 372 deletions.
6 changes: 0 additions & 6 deletions TODO
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
~/code/apicollective/apibuilder [versions_dao] > git grep DbHelpers
api/app/db/DbHelpers.scala:case class DbHelpers(
api/app/db/PasswordResetRequestsDao.scala: private val dbHelpers = DbHelpers(db, "password_resets")
api/app/db/generators/ServicesDao.scala: private val dbHelpers = DbHelpers(db, "generators.services")
~/code/apicollective/apibuilder [versions_dao] > git grep "val parser" api/app/db
api/app/db/OriginalsDao.scala: private val parser: anorm.RowParser[InternalOriginal] = {
api/app/db/generators/ServicesDao.scala: private val parser: RowParser[GeneratorService] = {

"org.webjars" % "bootstrap" % "3.4.1",
webjars
Expand Down
8 changes: 4 additions & 4 deletions api/app/controllers/PasswordResetRequests.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package controllers

import db.{InternalUsersDao, PasswordResetRequestsDao}
import db.{InternalUsersDao, InternalPasswordResetsDao}
import io.apibuilder.api.v0.models.PasswordResetRequest
import io.apibuilder.api.v0.models.json.*
import lib.Validation
Expand All @@ -12,9 +12,9 @@ import javax.inject.{Inject, Singleton}

@Singleton
class PasswordResetRequests @Inject() (
val apiBuilderControllerComponents: ApiBuilderControllerComponents,
passwordResetRequestsDao: PasswordResetRequestsDao,
usersDao: InternalUsersDao,
val apiBuilderControllerComponents: ApiBuilderControllerComponents,
passwordResetRequestsDao: InternalPasswordResetsDao,
usersDao: InternalUsersDao,
) extends ApiBuilderController {

def post(): Action[JsValue] = Anonymous(parse.json) { request =>
Expand Down
4 changes: 2 additions & 2 deletions api/app/controllers/PasswordResets.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package controllers

import cats.data.Validated.{Invalid, Valid}
import db.{InternalUserPasswordsDao, InternalUsersDao, PasswordResetRequestsDao}
import db.{InternalUserPasswordsDao, InternalUsersDao, InternalPasswordResetsDao}
import io.apibuilder.api.v0.models.PasswordReset
import io.apibuilder.api.v0.models.json.*
import lib.Validation
Expand All @@ -14,7 +14,7 @@ import javax.inject.{Inject, Singleton}
@Singleton
class PasswordResets @Inject() (
val apiBuilderControllerComponents: ApiBuilderControllerComponents,
passwordResetRequestsDao: PasswordResetRequestsDao,
passwordResetRequestsDao: InternalPasswordResetsDao,
sessionHelper: SessionHelper,
usersDao: InternalUsersDao,
userPasswordsDao: InternalUserPasswordsDao,
Expand Down
71 changes: 0 additions & 71 deletions api/app/db/DbHelpers.scala

This file was deleted.

104 changes: 104 additions & 0 deletions api/app/db/InternalPasswordResetsDao.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package db

import anorm.JodaParameterMetaData.*
import anorm.*
import db.generated.PasswordResetsDao
import cats.implicits.*
import cats.data.ValidatedNec
import io.apibuilder.api.v0.models.{Error, User}
import io.apibuilder.task.v0.models.EmailDataPasswordResetRequestCreated
import io.flow.postgresql.{Query, OrderBy}
import lib.{TokenGenerator, Validation}
import org.joda.time.DateTime
import play.api.db.*
import processor.EmailProcessorQueue

import java.util.UUID
import javax.inject.{Inject, Singleton}

case class InternalPasswordReset(db: generated.PasswordReset) {
val guid: UUID = db.guid
val userGuid: UUID = db.userGuid
val token: String = db.token
val expiresAt: DateTime = db.expiresAt
}

class InternalPasswordResetsDao @Inject()(
dao: PasswordResetsDao,
emailQueue: EmailProcessorQueue,
userPasswordsDao: InternalUserPasswordsDao,
usersDao: InternalUsersDao
) {

private val TokenLength = 80
private val HoursUntilTokenExpires = 72

def create(createdBy: Option[InternalUser], user: InternalUser): InternalPasswordReset = {
val guid = dao.db.withTransaction { implicit c =>
val guid = dao.insert(c, createdBy.getOrElse(user).guid, generated.PasswordResetForm(
userGuid = user.guid,
token = TokenGenerator.generate(TokenLength),
expiresAt = DateTime.now.plusHours(HoursUntilTokenExpires),
))
emailQueue.queueWithConnection(c, EmailDataPasswordResetRequestCreated(guid))
guid
}

findByGuid(guid).getOrElse {
sys.error("Failed to create password reset")
}
}

private def isExpired(pr: InternalPasswordReset): Boolean = {
pr.expiresAt.isBeforeNow
}

def resetPassword(user: Option[InternalUser], pr: InternalPasswordReset, newPassword: String): ValidatedNec[Error, InternalUser] = {
if (isExpired(pr)) {
Validation.singleError("Password reset is expired").invalidNec
} else {
usersDao.findByGuid(pr.userGuid).toValidNec(Validation.singleError("User not found")).andThen { prUser =>
val updatingUser = user.getOrElse(prUser)
userPasswordsDao.create(updatingUser, prUser.guid, newPassword).map { _ =>
softDelete(updatingUser, pr)
prUser
}
}
}
}

def softDelete(deletedBy: InternalUser, pr: InternalPasswordReset): Unit = {
dao.delete(deletedBy.guid, pr.db)
}

def findByGuid(guid: UUID): Option[InternalPasswordReset] = {
findAll(guid = Some(guid), limit = Some(1)).headOption
}

def findByToken(token: String): Option[InternalPasswordReset] = {
findAll(token = Some(token), limit = Some(1)).headOption
}

def findAll(
guid: Option[UUID] = None,
userGuid: Option[UUID] = None,
token: Option[String] = None,
isExpired: Option[Boolean] = None,
isDeleted: Option[Boolean] = Some(false),
limit: Option[Long],
offset: Long = 0
): Seq[InternalPasswordReset] = {
dao.findAll(
guid = guid,
token = token,
userGuid = userGuid,
limit = limit,
offset = offset,
orderBy = Some(OrderBy("created_at"))
) { q =>
q.and(isDeleted.map(Filters.isDeleted("password_resets", _)))
.and(isExpired.map(Filters.isExpired("password_resets", _)))
}.map(InternalPasswordReset(_))
}

}
140 changes: 0 additions & 140 deletions api/app/db/PasswordResetRequestsDao.scala

This file was deleted.

2 changes: 1 addition & 1 deletion api/app/processor/EmailProcessor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class EmailProcessor @Inject()(
membershipRequestsDao: db.InternalMembershipRequestsDao,
membershipRequestsModel: MembershipRequestsModel,
organizationsDao: InternalOrganizationsDao,
passwordResetRequestsDao: db.PasswordResetRequestsDao,
passwordResetRequestsDao: db.InternalPasswordResetsDao,
usersDao: InternalUsersDao,
orgModel: OrganizationsModel,
) extends TaskProcessorWithData[EmailData](args, TaskType.Email) {
Expand Down
Loading

0 comments on commit 561e2db

Please sign in to comment.