mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Authenticate with external accounts using OIDC
After successful authentication at the provider, an account is automatically created at docspell and the user is logged in.
This commit is contained in:
@ -23,6 +23,8 @@ import scodec.bits.ByteVector
|
||||
|
||||
trait Login[F[_]] {
|
||||
|
||||
def loginExternal(config: Config)(accountId: AccountId): F[Result]
|
||||
|
||||
def loginSession(config: Config)(sessionKey: String): F[Result]
|
||||
|
||||
def loginUserPass(config: Config)(up: UserPass): F[Result]
|
||||
@ -93,6 +95,16 @@ object Login {
|
||||
|
||||
private val logF = Logger.log4s(logger)
|
||||
|
||||
def loginExternal(config: Config)(accountId: AccountId): F[Result] =
|
||||
for {
|
||||
data <- store.transact(QLogin.findUser(accountId))
|
||||
_ <- logF.trace(s"Account lookup: $data")
|
||||
res <-
|
||||
if (data.exists(checkNoPassword(_, Set(AccountSource.OpenId))))
|
||||
doLogin(config, accountId, false)
|
||||
else Result.invalidAuth.pure[F]
|
||||
} yield res
|
||||
|
||||
def loginSession(config: Config)(sessionKey: String): F[Result] =
|
||||
AuthToken.fromString(sessionKey) match {
|
||||
case Right(at) =>
|
||||
@ -110,24 +122,11 @@ object Login {
|
||||
def loginUserPass(config: Config)(up: UserPass): F[Result] =
|
||||
AccountId.parse(up.user) match {
|
||||
case Right(acc) =>
|
||||
val okResult =
|
||||
for {
|
||||
require2FA <- store.transact(RTotp.isEnabled(acc))
|
||||
_ <-
|
||||
if (require2FA) ().pure[F]
|
||||
else store.transact(RUser.updateLogin(acc))
|
||||
token <- AuthToken.user(acc, require2FA, config.serverSecret)
|
||||
rem <- OptionT
|
||||
.whenF(!require2FA && up.rememberMe && config.rememberMe.enabled)(
|
||||
insertRememberToken(store, acc, config)
|
||||
)
|
||||
.value
|
||||
} yield Result.ok(token, rem)
|
||||
for {
|
||||
data <- store.transact(QLogin.findUser(acc))
|
||||
_ <- Sync[F].delay(logger.trace(s"Account lookup: $data"))
|
||||
res <-
|
||||
if (data.exists(check(up.pass))) okResult
|
||||
if (data.exists(check(up.pass))) doLogin(config, acc, up.rememberMe)
|
||||
else Result.invalidAuth.pure[F]
|
||||
} yield res
|
||||
case Left(_) =>
|
||||
@ -247,6 +246,24 @@ object Login {
|
||||
0.pure[F]
|
||||
}
|
||||
|
||||
private def doLogin(
|
||||
config: Config,
|
||||
acc: AccountId,
|
||||
rememberMe: Boolean
|
||||
): F[Result] =
|
||||
for {
|
||||
require2FA <- store.transact(RTotp.isEnabled(acc))
|
||||
_ <-
|
||||
if (require2FA) ().pure[F]
|
||||
else store.transact(RUser.updateLogin(acc))
|
||||
token <- AuthToken.user(acc, require2FA, config.serverSecret)
|
||||
rem <- OptionT
|
||||
.whenF(!require2FA && rememberMe && config.rememberMe.enabled)(
|
||||
insertRememberToken(store, acc, config)
|
||||
)
|
||||
.value
|
||||
} yield Result.ok(token, rem)
|
||||
|
||||
private def insertRememberToken(
|
||||
store: Store[F],
|
||||
acc: AccountId,
|
||||
|
@ -9,6 +9,7 @@ package docspell.backend.ops
|
||||
import cats.effect.{Async, Resource}
|
||||
import cats.implicits._
|
||||
import fs2.Stream
|
||||
|
||||
import docspell.backend.JobFactory
|
||||
import docspell.backend.PasswordCrypt
|
||||
import docspell.backend.ops.OCollective._
|
||||
@ -19,6 +20,7 @@ import docspell.store.queue.JobQueue
|
||||
import docspell.store.records._
|
||||
import docspell.store.usertask.{UserTask, UserTaskScope, UserTaskStore}
|
||||
import docspell.store.{AddResult, Store}
|
||||
|
||||
import com.github.eikek.calev._
|
||||
|
||||
trait OCollective[F[_]] {
|
||||
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* Copyright 2020 Docspell Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.backend.signup
|
||||
|
||||
import docspell.common._
|
||||
@ -11,3 +17,9 @@ final case class ExternalAccount(
|
||||
def toAccountId: AccountId =
|
||||
AccountId(collName, login)
|
||||
}
|
||||
|
||||
object ExternalAccount {
|
||||
def apply(accountId: AccountId): ExternalAccount =
|
||||
ExternalAccount(accountId.collective, accountId.user, AccountSource.OpenId)
|
||||
|
||||
}
|
||||
|
@ -8,11 +8,13 @@ package docspell.backend.signup
|
||||
|
||||
import cats.effect.{Async, Resource}
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.PasswordCrypt
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.all._
|
||||
import docspell.store.records.{RCollective, RInvitation, RUser}
|
||||
import docspell.store.{AddResult, Store}
|
||||
|
||||
import doobie.free.connection.ConnectionIO
|
||||
import org.log4s.getLogger
|
||||
|
||||
@ -83,23 +85,29 @@ object OSignup {
|
||||
SignupResult.signupClosed.pure[F]
|
||||
case _ =>
|
||||
if (data.source == AccountSource.Local)
|
||||
SignupResult.failure(new Exception("Account source must not be LOCAL!")).pure[F]
|
||||
else for {
|
||||
recs <- makeRecords(data.collName, data.login, Password(""), data.source)
|
||||
cres <- store.add(RCollective.insert(recs._1), RCollective.existsById(data.collName))
|
||||
ures <- store.add(RUser.insert(recs._2), RUser.exists(data.login))
|
||||
res = cres match {
|
||||
case AddResult.Failure(ex) =>
|
||||
SignupResult.failure(ex)
|
||||
case _ =>
|
||||
ures match {
|
||||
case AddResult.Failure(ex) =>
|
||||
SignupResult.failure(ex)
|
||||
case _ =>
|
||||
SignupResult.success
|
||||
}
|
||||
}
|
||||
} yield res
|
||||
SignupResult
|
||||
.failure(new Exception("Account source must not be LOCAL!"))
|
||||
.pure[F]
|
||||
else
|
||||
for {
|
||||
recs <- makeRecords(data.collName, data.login, Password(""), data.source)
|
||||
cres <- store.add(
|
||||
RCollective.insert(recs._1),
|
||||
RCollective.existsById(data.collName)
|
||||
)
|
||||
ures <- store.add(RUser.insert(recs._2), RUser.exists(data.login))
|
||||
res = cres match {
|
||||
case AddResult.Failure(ex) =>
|
||||
SignupResult.failure(ex)
|
||||
case _ =>
|
||||
ures match {
|
||||
case AddResult.Failure(ex) =>
|
||||
SignupResult.failure(ex)
|
||||
case _ =>
|
||||
SignupResult.success
|
||||
}
|
||||
}
|
||||
} yield res
|
||||
}
|
||||
|
||||
private def retryInvite(res: SignupResult): Boolean =
|
||||
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* Copyright 2020 Docspell Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.backend.signup
|
||||
import docspell.common._
|
||||
|
||||
|
Reference in New Issue
Block a user