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:
@ -0,0 +1,7 @@
|
||||
CREATE TABLE "totp" (
|
||||
"user_id" varchar(254) not null primary key,
|
||||
"enabled" boolean not null,
|
||||
"secret" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
FOREIGN KEY ("user_id") REFERENCES "user_"("uid") ON DELETE CASCADE
|
||||
);
|
@ -42,6 +42,7 @@ object AddResult {
|
||||
def withMsg(msg: String): EntityExists =
|
||||
EntityExists(msg)
|
||||
}
|
||||
def entityExists(msg: String): AddResult = EntityExists(msg)
|
||||
|
||||
case class Failure(ex: Throwable) extends AddResult {
|
||||
def toEither = Left(ex)
|
||||
|
@ -11,6 +11,7 @@ import java.time.{Instant, LocalDate}
|
||||
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.all._
|
||||
import docspell.totp.Key
|
||||
|
||||
import com.github.eikek.calev.CalEvent
|
||||
import doobie._
|
||||
@ -125,6 +126,9 @@ trait DoobieMeta extends EmilDoobieMeta {
|
||||
|
||||
implicit val metaJsonString: Meta[Json] =
|
||||
Meta[String].timap(DoobieMeta.parseJsonUnsafe)(_.noSpaces)
|
||||
|
||||
implicit val metaKey: Meta[Key] =
|
||||
Meta[String].timap(Key.unsafeFromString)(_.asString)
|
||||
}
|
||||
|
||||
object DoobieMeta extends DoobieMeta {
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2020 Docspell Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.{NonEmptyList => Nel}
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
import docspell.totp.{Key, Mac}
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final case class RTotp(
|
||||
userId: Ident,
|
||||
enabled: Boolean,
|
||||
secret: Key,
|
||||
created: Timestamp
|
||||
) {}
|
||||
|
||||
object RTotp {
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "totp"
|
||||
|
||||
val userId = Column[Ident]("user_id", this)
|
||||
val enabled = Column[Boolean]("enabled", this)
|
||||
val secret = Column[Key]("secret", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all = Nel.of(userId, enabled, secret, created)
|
||||
}
|
||||
val T = Table(None)
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
def generate[F[_]: Sync](userId: Ident, mac: Mac): F[RTotp] =
|
||||
for {
|
||||
now <- Timestamp.current[F]
|
||||
key <- Key.generate[F](mac)
|
||||
} yield RTotp(userId, false, key, now)
|
||||
|
||||
def insert(r: RTotp): ConnectionIO[Int] =
|
||||
DML.insert(T, T.all, sql"${r.userId},${r.enabled},${r.secret},${r.created}")
|
||||
|
||||
def updateDisabled(r: RTotp): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
T.enabled === false && T.userId === r.userId,
|
||||
DML.set(
|
||||
T.secret.setTo(r.secret),
|
||||
T.created.setTo(r.created)
|
||||
)
|
||||
)
|
||||
|
||||
def setEnabled(account: AccountId, enabled: Boolean): ConnectionIO[Int] =
|
||||
for {
|
||||
userId <- RUser.findIdByAccount(account)
|
||||
n <- userId match {
|
||||
case Some(id) =>
|
||||
DML.update(T, T.userId === id, DML.set(T.enabled.setTo(enabled)))
|
||||
case None =>
|
||||
0.pure[ConnectionIO]
|
||||
}
|
||||
} yield n
|
||||
|
||||
def findEnabledByLogin(
|
||||
accountId: AccountId,
|
||||
enabled: Boolean
|
||||
): ConnectionIO[Option[RTotp]] = {
|
||||
val t = RTotp.as("t")
|
||||
val u = RUser.as("u")
|
||||
Select(
|
||||
select(t.all),
|
||||
from(t).innerJoin(u, t.userId === u.uid),
|
||||
u.login === accountId.user && u.cid === accountId.collective && t.enabled === enabled
|
||||
).build.query[RTotp].option
|
||||
}
|
||||
|
||||
def existsByLogin(accountId: AccountId): ConnectionIO[Boolean] = {
|
||||
val t = RTotp.as("t")
|
||||
val u = RUser.as("u")
|
||||
Select(
|
||||
select(count(t.userId)),
|
||||
from(t).innerJoin(u, t.userId === u.uid),
|
||||
u.login === accountId.user && u.cid === accountId.collective
|
||||
).build
|
||||
.query[Int]
|
||||
.unique
|
||||
.map(_ > 0)
|
||||
}
|
||||
|
||||
}
|
@ -55,6 +55,8 @@ object RUser {
|
||||
)
|
||||
}
|
||||
|
||||
val T = Table(None)
|
||||
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
@ -105,6 +107,15 @@ object RUser {
|
||||
sql.query[RUser].to[Vector]
|
||||
}
|
||||
|
||||
def findIdByAccount(accountId: AccountId): ConnectionIO[Option[Ident]] =
|
||||
run(
|
||||
select(T.uid),
|
||||
from(T),
|
||||
T.login === accountId.user && T.cid === accountId.collective
|
||||
)
|
||||
.query[Ident]
|
||||
.option
|
||||
|
||||
def updateLogin(accountId: AccountId): ConnectionIO[Int] = {
|
||||
val t = Table(None)
|
||||
def stmt(now: Timestamp) =
|
||||
|
Reference in New Issue
Block a user