Skip to content

Commit

Permalink
allow mods to disable 2fa
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasf committed May 6, 2018
1 parent 313a3f1 commit 33fde6d
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 36 deletions.
17 changes: 8 additions & 9 deletions app/controllers/Account.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,14 @@ object Account extends LilaController {
}

def twoFactor = Auth { implicit ctx => me =>
Env.user.authenticator.hasTotp(me.id) flatMap {
case false => Env.security.forms.setupTwoFactor(me) map { form =>
html.account.setupTwoFactor(me, form)
}
case true => Env.security.forms.disableTwoFactor(me) map { form =>
if (me.totpSecret.isDefined)
Env.security.forms.disableTwoFactor(me) map { form =>
html.account.disableTwoFactor(me, form)
}
}
else
Env.security.forms.setupTwoFactor(me) map { form =>
html.account.setupTwoFactor(me, form)
}
}

def setupTwoFactor = AuthBody { implicit ctx => me =>
Expand All @@ -171,7 +171,7 @@ object Account extends LilaController {
FormFuResult(form) { err =>
fuccess(html.account.setupTwoFactor(me, err))
} { data =>
Env.user.authenticator.setTotpSecret(me.id, TotpSecret(data.secret)) inject
UserRepo.setupTwoFactor(me.id, TotpSecret(data.secret)) inject
Redirect(routes.Account.twoFactor)
}
}
Expand All @@ -183,8 +183,7 @@ object Account extends LilaController {
FormFuResult(form) { err =>
fuccess(html.account.disableTwoFactor(me, err))
} { _ =>
Env.user.authenticator.unsetTotpSecret(me.id) inject
Redirect(routes.Account.twoFactor)
UserRepo.disableTwoFactor(me.id) inject Redirect(routes.Account.twoFactor)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/Mod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ object Mod extends LilaController {
}
}

def disableTwoFactor(username: String) = Secure(_.DisableTwoFactor) { implicit ctx => me =>
modApi.disableTwoFactor(me.id, username) >> User.modZoneOrRedirect(username, me)
}

def closeAccount(username: String) = Secure(_.CloseAccount) { implicit ctx => me =>
modApi.closeAccount(me.id, username).flatMap {
_.?? { user =>
Expand Down
5 changes: 5 additions & 0 deletions app/views/user/mod.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<input class="button@when(u.ipBan, " active")" type="submit" value="IP ban @spy.ips.size" />
</form>
}
@if(u.totpSecret.isDefined && isGranted(_.DisableTwoFactor)) {
<form method="post" action="@routes.Mod.disableTwoFactor(u.username)" data-hint="Disables two-factor authentication for this account." class="hint--bottom-left xhr">
<input class="button confirm" type="submit" value="Disable 2FA" />
</form>
}
@if(u.enabled) {
@if(isGranted(_.CloseAccount)) {
<form method="post" action="@routes.Mod.closeAccount(u.username)" data-hint="Disables this account." class="hint--bottom-left xhr">
Expand Down
1 change: 1 addition & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ POST /mod/:username/troll/:v controllers.Mod.troll(username: String, v
POST /mod/:username/ban/:v controllers.Mod.ban(username: String, v: Boolean)
POST /mod/:username/delete-pms-and-chats controllers.Mod.deletePmsAndChats(username: String)
POST /mod/:username/warn controllers.Mod.warn(username: String, subject: String)
POST /mod/:username/disable-2fa controllers.Mod.disableTwoFactor(username: String)
POST /mod/:username/close controllers.Mod.closeAccount(username: String)
POST /mod/:username/reopen controllers.Mod.reopenAccount(username: String)
POST /mod/:username/title controllers.Mod.setTitle(username: String)
Expand Down
4 changes: 4 additions & 0 deletions modules/mod/src/main/ModApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ final class ModApi(
_ <- ipBan ?? setBan(mod, sus, true)
} yield logApi.garbageCollect(mod, sus)

def disableTwoFactor(mod: String, username: String): Funit = withUser(username) { user =>
(UserRepo disableTwoFactor user.id) >> logApi.disableTwoFactor(mod, user.id)
}

def closeAccount(mod: String, username: String): Fu[Option[User]] = withUser(username) { user =>
user.enabled ?? {
logApi.closeAccount(mod, user.id) inject user.some
Expand Down
2 changes: 2 additions & 0 deletions modules/mod/src/main/Modlog.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ case class Modlog(
case Modlog.ban => "ban user"
case Modlog.ipban => "ban IPs"
case Modlog.ipunban => "unban IPs"
case Modlog.disableTwoFactor => "disable 2fa"
case Modlog.closeAccount => "close account"
case Modlog.selfCloseAccount => "self close account"
case Modlog.reopenAccount => "reopen account"
Expand Down Expand Up @@ -82,6 +83,7 @@ object Modlog {
val permissions = "permissions"
val ban = "ban"
val ipban = "ipban"
val disableTwoFactor = "disableTwoFactor"
val closeAccount = "closeAccount"
val selfCloseAccount = "selfCloseAccount"
val reopenAccount = "reopenAccount"
Expand Down
4 changes: 4 additions & 0 deletions modules/mod/src/main/ModlogApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ final class ModlogApi(coll: Coll) {
Modlog.make(mod, sus, if (sus.user.ipBan) Modlog.ipban else Modlog.ipunban)
}

def disableTwoFactor(mod: String, user: String) = add {
Modlog(mod, user.some, Modlog.disableTwoFactor)
}

def closeAccount(mod: String, user: String) = add {
Modlog(mod, user.some, Modlog.closeAccount)
}
Expand Down
13 changes: 6 additions & 7 deletions modules/security/src/main/DataForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,12 @@ final class DataForm(
))
}

def disableTwoFactor(u: User) = for {
candidate <- authenticator loginCandidate u
totpSecret <- authenticator totpSecret u.id
} yield Form(tuple(
"passwd" -> nonEmptyText.verifying("incorrectPassword", p => candidate.check(ClearPassword(p))),
"token" -> nonEmptyText.verifying("invalidAuthenticationToken", t => totpSecret.map(_.verify(t)).getOrElse(false))
))
def disableTwoFactor(u: User) = authenticator loginCandidate u map { candidate =>
Form(tuple(
"passwd" -> nonEmptyText.verifying("incorrectPassword", p => candidate.check(ClearPassword(p))),
"token" -> nonEmptyText.verifying("invalidAuthenticationToken", t => u.totpSecret.map(_.verify(t)).getOrElse(false))
))
}

def fixEmail(old: EmailAddress) = Form(
single("email" -> acceptableUniqueEmail(none).verifying(emailValidator differentConstraint old.some))
Expand Down
5 changes: 3 additions & 2 deletions modules/security/src/main/Permission.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object Permission {
case object MarkEngine extends Permission("ROLE_ADJUST_CHEATER", List(UserSpy))
case object MarkBooster extends Permission("ROLE_ADJUST_BOOSTER", List(UserSpy))
case object IpBan extends Permission("ROLE_IP_BAN", List(UserSpy))
case object DisableTwoFactor extends Permission("ROLE_DISABLE_2FA")
case object CloseAccount extends Permission("ROLE_CLOSE_ACCOUNT", List(UserSpy))
case object ReopenAccount extends Permission("ROLE_REOPEN_ACCOUNT", List(UserSpy))
case object SetTitle extends Permission("ROLE_SET_TITLE", List(UserSpy))
Expand Down Expand Up @@ -64,7 +65,7 @@ object Permission {
ChatTimeout, MarkTroll, SetTitle, SetEmail, ModerateQa, StreamConfig,
MessageAnyone, CloseTeam, TerminateTournament, ManageTournament, ManageEvent,
PracticeConfig, RemoveRanking, ReportBan, Beta, DisapproveCoachReview,
Relay, Streamers
Relay, Streamers, DisableTwoFactor
))

case object SuperAdmin extends Permission("ROLE_SUPER_ADMIN", List(
Expand All @@ -76,7 +77,7 @@ object Permission {
UserSpy, MarkEngine, MarkBooster, IpBan, ModerateQa, StreamConfig, PracticeConfig,
Beta, MessageAnyone, UserSearch, CloseTeam, TerminateTournament, ManageTournament, ManageEvent,
PublicMod, Developer, Coach, ModNote, RemoveRanking, ReportBan,
Relay, Cli, Settings, Streamers
Relay, Cli, Settings, Streamers, DisableTwoFactor
)

lazy private val all: List[Permission] = SuperAdmin :: allButSuperAdmin
Expand Down
18 changes: 0 additions & 18 deletions modules/user/src/main/Authenticator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,6 @@ final class Authenticator(
$set(F.bpass -> passEnc(p).bytes) ++ $unset(F.salt, F.sha512)
).void

def setTotpSecret(id: User.ID, totp: TotpSecret): Funit =
userRepo.coll.update(
$id(id) ++ (F.totpSecret $exists false), // never overwrite existing secret
$set(F.totpSecret -> totp.secret)
).void

def unsetTotpSecret(id: User.ID): Funit =
userRepo.coll.update(
$id(id),
$unset(F.totpSecret)
).void

def totpSecret(id: User.ID): Fu[Option[TotpSecret]] =
userRepo.coll.primitiveOne[Array[Byte]]($id(id), F.totpSecret).map(_ map TotpSecret.apply)

def hasTotp(id: User.ID): Fu[Boolean] =
userRepo.coll.exists($id(id) ++ $doc(F.totpSecret $exists true))

private def authWithBenefits(auth: AuthData)(p: ClearPassword): Boolean = {
val res = compare(auth, p)
if (res && auth.salt.isDefined)
Expand Down
8 changes: 8 additions & 0 deletions modules/user/src/main/UserRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ object UserRepo {

def setRoles(id: ID, roles: List[String]) = coll.updateField($id(id), "roles", roles)

def disableTwoFactor(id: ID) = coll.update($id(id), $unset(F.totpSecret))

def setupTwoFactor(id: ID, totp: TotpSecret): Funit =
coll.update(
$id(id) ++ (F.totpSecret $exists false), // never overwrite existing secret
$set(F.totpSecret -> totp.secret)
).void

def enable(id: ID) = coll.updateField($id(id), F.enabled, true)

def disable(user: User, keepEmail: Boolean) = coll.update(
Expand Down

0 comments on commit 33fde6d

Please sign in to comment.