diff --git a/app/controllers/AuthenticationController.scala b/app/controllers/AuthenticationController.scala index f6609755e09..03d8fa7a6ae 100755 --- a/app/controllers/AuthenticationController.scala +++ b/app/controllers/AuthenticationController.scala @@ -621,6 +621,8 @@ class AuthenticationController @Inject()( dataStoreToken <- bearerTokenAuthenticatorService.createAndInitDataStoreTokenForUser(user) _ <- organizationService .createOrganizationDirectory(organization._id, dataStoreToken) ?~> "organization.folderCreation.failed" + _ <- Fox.runOptional(signUpData.acceptedTermsOfService)(version => + organizationService.acceptTermsOfService(organization._id, version)) } yield { Mailer ! Send(defaultMails .newOrganizationMail(organization.name, email, request.headers.get("Host").getOrElse(""))) @@ -730,7 +732,8 @@ trait AuthForms { firstName: String, lastName: String, password: String, - inviteToken: Option[String]) + inviteToken: Option[String], + acceptedTermsOfService: Option[Int]) def signUpForm(implicit messages: Messages): Form[SignUpData] = Form( @@ -745,8 +748,9 @@ trait AuthForms { "firstName" -> nonEmptyText, "lastName" -> nonEmptyText, "inviteToken" -> optional(nonEmptyText), - )((organization, organizationName, email, password, firstName, lastName, inviteToken) => - SignUpData(organization, organizationName, email, firstName, lastName, password._1, inviteToken))( + "acceptedTermsOfService" -> optional(number) + )((organization, organizationName, email, password, firstName, lastName, inviteToken, acceptTos) => + SignUpData(organization, organizationName, email, firstName, lastName, password._1, inviteToken, acceptTos))( signUpData => Some( (signUpData.organization, @@ -755,7 +759,8 @@ trait AuthForms { ("", ""), signUpData.firstName, signUpData.lastName, - signUpData.inviteToken)))) + signUpData.inviteToken, + signUpData.acceptedTermsOfService)))) // Sign in case class SignInData(email: String, password: String) diff --git a/app/controllers/OrganizationController.scala b/app/controllers/OrganizationController.scala index 19ae573e35a..f1bd61c9c4e 100755 --- a/app/controllers/OrganizationController.scala +++ b/app/controllers/OrganizationController.scala @@ -3,7 +3,6 @@ package controllers import org.apache.pekko.actor.ActorSystem import play.silhouette.api.Silhouette import com.scalableminds.util.accesscontext.{DBAccessContext, GlobalAccessContext} -import com.scalableminds.util.time.Instant import com.scalableminds.util.tools.{Fox, FoxImplicits} import mail.{DefaultMails, Send} @@ -141,10 +140,7 @@ class OrganizationController @Inject()( def acceptTermsOfService(version: Int): Action[AnyContent] = sil.SecuredAction.async { implicit request => for { _ <- bool2Fox(request.identity.isOrganizationOwner) ?~> "termsOfService.onlyOrganizationOwner" - _ <- bool2Fox(conf.WebKnossos.TermsOfService.enabled) ?~> "termsOfService.notEnabled" - requiredVersion = conf.WebKnossos.TermsOfService.version - _ <- bool2Fox(version == requiredVersion) ?~> Messages("termsOfService.versionMismatch", requiredVersion, version) - _ <- organizationDAO.acceptTermsOfService(request.identity._organization, version, Instant.now) + _ <- organizationService.acceptTermsOfService(request.identity._organization, version) } yield Ok } diff --git a/app/models/organization/OrganizationService.scala b/app/models/organization/OrganizationService.scala index 6e105e18c2c..0760d67cb3b 100644 --- a/app/models/organization/OrganizationService.scala +++ b/app/models/organization/OrganizationService.scala @@ -1,6 +1,7 @@ package models.organization import com.scalableminds.util.accesscontext.{DBAccessContext, GlobalAccessContext} +import com.scalableminds.util.time.Instant import com.scalableminds.util.tools.{Fox, FoxImplicits, TextUtils} import com.scalableminds.webknossos.datastore.rpc.RPC import com.typesafe.scalalogging.LazyLogging @@ -10,6 +11,7 @@ import models.dataset.{DataStore, DataStoreDAO} import models.folder.{Folder, FolderDAO, FolderService} import models.team.{PricingPlan, Team, TeamDAO} import models.user.{Invite, MultiUserDAO, User, UserDAO, UserService} +import play.api.i18n.{Messages, MessagesProvider} import play.api.libs.json.{JsArray, JsObject, Json} import utils.{ObjectId, WkConf} @@ -165,4 +167,13 @@ class OrganizationService @Inject()(organizationDAO: OrganizationDAO, def newUserMailRecipient(organization: Organization)(implicit ctx: DBAccessContext): Fox[String] = fallbackOnOwnerEmail(organization.newUserMailingList, organization) + def acceptTermsOfService(organizationId: String, version: Int)(implicit ctx: DBAccessContext, + mp: MessagesProvider): Fox[Unit] = + for { + _ <- bool2Fox(conf.WebKnossos.TermsOfService.enabled) ?~> "termsOfService.notEnabled" + requiredVersion = conf.WebKnossos.TermsOfService.version + _ <- bool2Fox(version == requiredVersion) ?~> Messages("termsOfService.versionMismatch", requiredVersion, version) + _ <- organizationDAO.acceptTermsOfService(organizationId, version, Instant.now) + } yield () + } diff --git a/conf/application.conf b/conf/application.conf index 0ae8b6f25dd..eb5ed4d566b 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -116,7 +116,7 @@ webKnossos { Please add the information of the operator to comply with GDPR. """ termsOfService { - enabled = false + enabled = true # The URL will be embedded into an iFrame url = "https://webknossos.org/terms-of-service" acceptanceDeadline = "2023-01-01T00:00:00Z" @@ -146,7 +146,7 @@ features { discussionBoard = "https://forum.image.sc/tag/webknossos" discussionBoardRequiresAdmin = false hideNavbarLogin = false - isWkorgInstance = false + isWkorgInstance = true recommendWkorgInstance = true taskReopenAllowedInSeconds = 30 allowDeleteDatasets = true diff --git a/frontend/javascripts/admin/api/terms_of_service.ts b/frontend/javascripts/admin/api/terms_of_service.ts index c1cf868c5a8..8d989704f98 100644 --- a/frontend/javascripts/admin/api/terms_of_service.ts +++ b/frontend/javascripts/admin/api/terms_of_service.ts @@ -7,17 +7,3 @@ export function getTermsOfService(): Promise<{ }> { return Request.receiveJSON("/api/termsOfService"); } - -export type AcceptanceInfo = { - acceptanceDeadline: number; - acceptanceDeadlinePassed: boolean; - acceptanceNeeded: boolean; -}; - -export async function requiresTermsOfServiceAcceptance(): Promise { - return await Request.receiveJSON("/api/termsOfService/acceptanceNeeded"); -} - -export function acceptTermsOfService(version: number): Promise { - return Request.receiveJSON(`/api/termsOfService/accept?version=${version}`, { method: "POST" }); -} diff --git a/frontend/javascripts/admin/auth/registration_form_generic.tsx b/frontend/javascripts/admin/auth/registration_form_generic.tsx index a4686ce5829..6787ecd4c3b 100644 --- a/frontend/javascripts/admin/auth/registration_form_generic.tsx +++ b/frontend/javascripts/admin/auth/registration_form_generic.tsx @@ -9,6 +9,8 @@ import Store from "oxalis/throttled_store"; import messages from "messages"; import { setHasOrganizationsAction } from "oxalis/model/actions/ui_actions"; import { setActiveOrganizationAction } from "oxalis/model/actions/organization_actions"; +import { useFetch } from "libs/react_helpers"; +import { getTermsOfService } from "admin/api/terms_of_service"; const FormItem = Form.Item; const { Password } = Input; @@ -26,6 +28,8 @@ type Props = { function RegistrationFormGeneric(props: Props) { const [form] = Form.useForm(); + const terms = useFetch(getTermsOfService, null, []); + const onFinish = async (formValues: Record) => { await Request.sendJSONReceiveJSON( props.organizationIdToCreate != null @@ -274,28 +278,57 @@ function RegistrationFormGeneric(props: Props) { - {props.hidePrivacyStatement ? null : ( - - value - ? Promise.resolve() - : Promise.reject(new Error(messages["auth.privacy_check_required"])), - }, - ]} - > - - I agree to storage and processing of my personal data as described in the{" "} - - privacy statement - - . - - - )} +
+ {props.hidePrivacyStatement ? null : ( + + value + ? Promise.resolve() + : Promise.reject(new Error(messages["auth.privacy_check_required"])), + }, + ]} + > + + I agree to storage and processing of my personal data as described in the{" "} + + privacy statement + + . + + + )} + {terms != null && !terms.enabled ? null : ( + + value + ? Promise.resolve() + : Promise.reject(new Error(messages["auth.tos_check_required"])), + }, + ]} + > + + I agree to the{" "} + {terms == null ? ( + "terms of service" + ) : ( + + terms of service + + )} + . + + + )} +
+