Initial impl of search route

This commit is contained in:
eikek 2021-10-04 09:46:08 +02:00
parent f4596db63d
commit a286556116
4 changed files with 102 additions and 15 deletions

View File

@ -12,7 +12,7 @@ import cats.implicits._
import docspell.backend.PasswordCrypt
import docspell.backend.auth.ShareToken
import docspell.backend.ops.OShare.VerifyResult
import docspell.backend.ops.OShare.{ShareQuery, VerifyResult}
import docspell.common._
import docspell.query.ItemQuery
import docspell.store.Store
@ -36,26 +36,37 @@ trait OShare[F[_]] {
removePassword: Boolean
): F[OShare.ChangeResult]
// ---
/** Verifies the given id and password and returns a authorization token on success. */
def verify(key: ByteVector)(id: Ident, password: Option[Password]): F[VerifyResult]
/** Verifies the authorization token. */
def verifyToken(key: ByteVector)(token: String): F[VerifyResult]
def findShareQuery(id: Ident): OptionT[F, ShareQuery]
}
object OShare {
final case class ShareQuery(id: Ident, cid: Ident, query: ItemQuery)
sealed trait VerifyResult {
def toEither: Either[String, ShareToken] =
this match {
case VerifyResult.Success(token) => Right(token)
case VerifyResult.Success(token, _) =>
Right(token)
case _ => Left("Authentication failed.")
}
}
object VerifyResult {
case class Success(token: ShareToken) extends VerifyResult
case class Success(token: ShareToken, shareName: Option[String]) extends VerifyResult
case object NotFound extends VerifyResult
case object PasswordMismatch extends VerifyResult
case object InvalidToken extends VerifyResult
def success(token: ShareToken): VerifyResult = Success(token)
def success(token: ShareToken): VerifyResult = Success(token, None)
def success(token: ShareToken, name: Option[String]): VerifyResult =
Success(token, name)
def notFound: VerifyResult = NotFound
def passwordMismatch: VerifyResult = PasswordMismatch
def invalidToken: VerifyResult = InvalidToken
@ -158,8 +169,8 @@ object OShare {
val token = ShareToken.create(id, shareKey)
pwCheck match {
case Some(true) => token.map(VerifyResult.success)
case None => token.map(VerifyResult.success)
case Some(true) => token.map(t => VerifyResult.success(t, share.name))
case None => token.map(t => VerifyResult.success(t, share.name))
case Some(false) => VerifyResult.passwordMismatch.pure[F]
}
}
@ -186,5 +197,11 @@ object OShare {
logger.debug(s"Invalid session token: $err") *>
VerifyResult.invalidToken.pure[F]
}
def findShareQuery(id: Ident): OptionT[F, ShareQuery] =
RShare
.findCurrentActive(id)
.mapK(store.transform)
.map(share => ShareQuery(share.id, share.cid, share.query))
}
}

View File

@ -1558,6 +1558,30 @@ paths:
schema:
$ref: "#/components/schemas/BasicResult"
/share/search:
post:
operationId: "share-search"
tags: [Share]
summary: Performs a search in a share.
description: |
Allows to run a search query in the shared documents. The
input data structure is the same as with a standard query. The
`searchMode` parameter is ignored here.
security:
- shareTokenHeader: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ItemQuery"
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/ItemLightList"
/admin/user/resetPassword:
post:
operationId: "admin-user-reset-password"
@ -4248,6 +4272,11 @@ components:
type: boolean
message:
type: string
name:
type: string
description: |
The name of the share if it exists. Only valid to use when
`success` is `true`.
ShareData:
description: |
@ -6475,6 +6504,10 @@ components:
type: apiKey
in: header
name: Docspell-Admin-Secret
shareTokenHeader:
type: apiKey
in: header
name: Docspell-Share-Auth
parameters:
id:
name: id

View File

@ -81,16 +81,16 @@ object ShareRoutes {
res <- backend.share
.verify(cfg.auth.serverSecret)(secret.shareId, secret.password)
resp <- res match {
case VerifyResult.Success(token) =>
case VerifyResult.Success(token, name) =>
val cd = ShareCookieData(token)
Ok(ShareVerifyResult(true, token.asString, false, "Success"))
Ok(ShareVerifyResult(true, token.asString, false, "Success", name))
.map(cd.addCookie(ClientRequestInfo.getBaseUrl(cfg, req)))
case VerifyResult.PasswordMismatch =>
Ok(ShareVerifyResult(false, "", true, "Failed"))
Ok(ShareVerifyResult(false, "", true, "Failed", None))
case VerifyResult.NotFound =>
Ok(ShareVerifyResult(false, "", false, "Failed"))
Ok(ShareVerifyResult(false, "", false, "Failed", None))
case VerifyResult.InvalidToken =>
Ok(ShareVerifyResult(false, "", false, "Failed"))
Ok(ShareVerifyResult(false, "", false, "Failed", None))
}
} yield resp
}

View File

@ -7,13 +7,20 @@
package docspell.restserver.routes
import cats.effect._
import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.ShareToken
import docspell.common.Logger
import docspell.backend.ops.OSimpleSearch
import docspell.common._
import docspell.restapi.model.ItemQuery
import docspell.restserver.Config
import docspell.store.qb.Batch
import docspell.store.queries.Query
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.dsl.Http4sDsl
object ShareSearchRoutes {
@ -23,7 +30,37 @@ object ShareSearchRoutes {
token: ShareToken
): HttpRoutes[F] = {
val logger = Logger.log4s[F](org.log4s.getLogger)
logger.trace(s"$backend $cfg $token")
???
val dsl = new Http4sDsl[F] {}
import dsl._
HttpRoutes.of { case req @ POST -> Root =>
backend.share
.findShareQuery(token.id)
.semiflatMap { share =>
for {
userQuery <- req.as[ItemQuery]
batch = Batch(
userQuery.offset.getOrElse(0),
userQuery.limit.getOrElse(cfg.maxItemPageSize)
).restrictLimitTo(
cfg.maxItemPageSize
)
itemQuery = ItemQueryString(userQuery.query)
settings = OSimpleSearch.Settings(
batch,
cfg.fullTextSearch.enabled,
userQuery.withDetails.getOrElse(false),
cfg.maxNoteLength,
searchMode = SearchMode.Normal
)
account = AccountId(share.cid, Ident.unsafe(""))
fixQuery = Query.Fix(account, Some(share.query.expr), None)
_ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}")
resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery)
} yield resp
}
.getOrElseF(NotFound())
}
}
}