mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Initial impl for totp
This commit is contained in:
@ -76,6 +76,7 @@ object RestServer {
|
||||
"organization" -> OrganizationRoutes(restApp.backend, token),
|
||||
"person" -> PersonRoutes(restApp.backend, token),
|
||||
"source" -> SourceRoutes(restApp.backend, token),
|
||||
"user/otp" -> TotpRoutes(restApp.backend, cfg, token),
|
||||
"user" -> UserRoutes(restApp.backend, token),
|
||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||
@ -109,6 +110,7 @@ object RestServer {
|
||||
def adminRoutes[F[_]: Async](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
|
||||
Router(
|
||||
"fts" -> FullTextIndexRoutes.admin(cfg, restApp.backend),
|
||||
"user/otp" -> TotpRoutes.admin(restApp.backend),
|
||||
"user" -> UserRoutes.admin(restApp.backend),
|
||||
"info" -> InfoRoutes.admin(cfg),
|
||||
"attachments" -> AttachmentRoutes.admin(restApp.backend)
|
||||
|
@ -82,7 +82,8 @@ object LoginRoutes {
|
||||
true,
|
||||
"Login successful",
|
||||
Some(cd.asString),
|
||||
cfg.auth.sessionValid.millis
|
||||
cfg.auth.sessionValid.millis,
|
||||
token.requireSecondFactor
|
||||
)
|
||||
).map(cd.addCookie(getBaseUrl(cfg, req)))
|
||||
.map(resp =>
|
||||
@ -93,7 +94,7 @@ object LoginRoutes {
|
||||
|
||||
} yield resp
|
||||
case _ =>
|
||||
Ok(AuthResult("", account, false, "Login failed.", None, 0L))
|
||||
Ok(AuthResult("", account, false, "Login failed.", None, 0L, false))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020 Docspell Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.backend.ops.OTotp
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.Config
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.totp.OnetimePassword
|
||||
|
||||
import org.http4s.HttpRoutes
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object TotpRoutes {
|
||||
def apply[F[_]: Async](
|
||||
backend: BackendApp[F],
|
||||
cfg: Config,
|
||||
user: AuthToken
|
||||
): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root / "state" =>
|
||||
for {
|
||||
result <- backend.totp.state(user.account)
|
||||
resp <- Ok(
|
||||
result.fold(en => OtpState(true, en.created.some), _ => OtpState(false, None))
|
||||
)
|
||||
} yield resp
|
||||
case POST -> Root / "init" =>
|
||||
for {
|
||||
result <- backend.totp.initialize(user.account)
|
||||
resp <- result match {
|
||||
case OTotp.InitResult.AlreadyExists =>
|
||||
UnprocessableEntity(BasicResult(false, "A totp setup already exists!"))
|
||||
case OTotp.InitResult.NotFound =>
|
||||
NotFound(BasicResult(false, "User not found"))
|
||||
case OTotp.InitResult.Failed(ex) =>
|
||||
InternalServerError(BasicResult(false, ex.getMessage))
|
||||
case s @ OTotp.InitResult.Success(_, key) =>
|
||||
val issuer = cfg.appName
|
||||
val uri = s.authenticatorUrl(issuer)
|
||||
Ok(OtpResult(uri, key.data.toBase32, "totp", issuer))
|
||||
}
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root / "confirm" =>
|
||||
for {
|
||||
data <- req.as[OtpConfirm]
|
||||
result <- backend.totp.confirmInit(user.account, OnetimePassword(data.otp.pass))
|
||||
resp <- result match {
|
||||
case OTotp.ConfirmResult.Success =>
|
||||
Ok(BasicResult(true, "TOTP setup successful."))
|
||||
case OTotp.ConfirmResult.Failed =>
|
||||
Ok(BasicResult(false, "TOTP setup failed!"))
|
||||
}
|
||||
} yield resp
|
||||
|
||||
case POST -> Root / "disable" =>
|
||||
for {
|
||||
result <- backend.totp.disable(user.account)
|
||||
resp <- Ok(Conversions.basicResult(result, "TOTP setup disabled."))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
def admin[F[_]: Async](backend: BackendApp[F]): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of { case req @ POST -> Root / "resetOTP" =>
|
||||
for {
|
||||
data <- req.as[ResetPassword]
|
||||
result <- backend.totp.disable(data.account)
|
||||
resp <- Ok(Conversions.basicResult(result, "TOTP setup disabled."))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
}
|
@ -39,6 +39,10 @@
|
||||
<script type="application/javascript">
|
||||
var storedAccount = localStorage.getItem('account');
|
||||
var account = storedAccount ? JSON.parse(storedAccount) : null;
|
||||
if (account && !account.hasOwnProperty("requireSecondFactor")) {
|
||||
// this is required for transitioning; elm fails to parse the account
|
||||
account["requireSecondFactor"] = false;
|
||||
}
|
||||
var elmFlags = {
|
||||
"account": account,
|
||||
"config": {{{flagsJson}}}
|
||||
|
Reference in New Issue
Block a user