From 5f0a1aa3390e8d5842670b3fa34daff562fbb493 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Fri, 10 Nov 2023 17:36:10 -0300 Subject: [PATCH] Let user join the meeting using Graphql actions --- .../users/UserJoinMeetingReqMsgHdlr.scala | 197 ++++++++++++------ .../users/ValidateAuthTokenReqMsgHdlr.scala | 139 ++++++------ .../bigbluebutton/core/db/MeetingDAO.scala | 2 +- .../core/db/UserConnectionStatusDAO.scala | 2 +- .../org/bigbluebutton/core/db/UserDAO.scala | 55 +++-- bbb-graphql-client-test/src/App.js | 4 +- bbb-graphql-client-test/src/MyInfo.js | 45 ++-- bbb-graphql-server/bbb_schema.sql | 4 + bbb-graphql-server/metadata/actions.graphql | 7 + bbb-graphql-server/metadata/actions.yaml | 7 + .../tables/public_v_user_current.yaml | 35 +++- .../controllers/ConnectionController.groovy | 12 +- 12 files changed, 317 insertions(+), 192 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala index 18270df0e6..d2b37c454e 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala @@ -2,15 +2,14 @@ package org.bigbluebutton.core.apps.users import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers -import org.bigbluebutton.core.db.{ UserStateDAO } +import org.bigbluebutton.core.db.{ UserDAO, UserStateDAO } import org.bigbluebutton.core.domain.MeetingState2x -import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers } -import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter } -import org.bigbluebutton.core2.message.senders.MsgBuilder +import org.bigbluebutton.core.models._ +import org.bigbluebutton.core.running._ +import org.bigbluebutton.core2.message.senders._ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers { this: MeetingActor => - val liveMeeting: LiveMeeting val outGW: OutMsgRouter @@ -18,64 +17,136 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers { log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId) Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match { - case Some(reconnectingUser) => - if (reconnectingUser.userLeftFlag.left) { - log.info("Resetting flag that user left meeting. user {}", msg.body.userId) - // User has reconnected. Just reset it's flag. ralam Oct 23, 2018 - sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false) - Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId) - } - - state - case None => - // Check if maxParticipants has been reached - // User are able to reenter if he already joined previously with the same extId - val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match { - case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers) - case None => false - } - val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 && - RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers && - userHasJoinedAlready == false - - if (!hasReachedMaxParticipants) { - val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state) - - if (liveMeeting.props.meetingProp.isBreakout) { - BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus) - } - - // Warn previous users that someone connected with same Id - for { - regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, - liveMeeting.registeredUsers) - } yield { - RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers) - .filter(u => u.id != regUser.id) - .foreach { previousUser => - val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg( - previousUser.id, - liveMeeting.props.meetingProp.intId, - "info", - "promote", - "app.mobileAppModal.userConnectedWithSameId", - "Notification to warn that user connect again from other browser/device", - Vector(regUser.name) - ) - outGW.send(notifyUserEvent) - } - } - - // fresh user joined (not due to reconnection). Clear (pop) the cached voice user - VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId) - UserStateDAO.updateExpired(msg.body.userId, false) - - newState - } else { - log.info("Ignoring user {} attempt to join, once the meeting {} has reached max participants: {}", msg.body.userId, msg.header.meetingId, liveMeeting.props.usersProp.maxUsers) - state - } + case Some(user) => handleUserReconnecting(user, msg, state) + case None => handleUserJoining(msg, state) } } -} + private def handleUserJoining(msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = { + + val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers) + log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]") + + regUser.fold { + handleFailedUserJoin(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN) + state + } { user => + val validationResult = for { + _ <- checkIfUserGuestStatusIsAllowed(user) + _ <- checkIfUserIsBanned(user) + _ <- checkIfUserLoggedOut(user) + _ <- validateMaxParticipants(user) + } yield user + + validationResult.fold( + reason => handleFailedUserJoin(msg, reason._1, reason._2), + validUser => handleSuccessfulUserJoin(msg, validUser) + ) + } + } + + private def handleSuccessfulUserJoin(msg: UserJoinMeetingReqMsg, regUser: RegisteredUser) = { + val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state) + updateParentMeetingWithNewListOfUsers() + notifyPreviousUsersWithSameExtId(regUser) + clearCachedVoiceUser(regUser) + clearExpiredUserState(regUser) + invalidateUserGraphqlConnection(regUser) + + newState + } + + private def handleFailedUserJoin(msg: UserJoinMeetingReqMsg, failReason: String, failReasonCode: String) = { + log.info("Ignoring user {} attempt to join in meeting {}. Reason Code: {}, Reason Message: {}", msg.body.userId, msg.header.meetingId, failReasonCode, failReason) + UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason) + state + } + + private def handleUserReconnecting(user: UserState, msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = { + if (user.userLeftFlag.left) { + resetUserLeftFlag(msg) + } + state + } + + private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = { + log.info("Resetting flag that user left meeting. user {}", msg.body.userId) + sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false) + Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId) + } + + private def validateMaxParticipants(regUser: RegisteredUser): Either[(String, String), Unit] = { + val userHasJoinedAlready = RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers) + val maxParticipants = liveMeeting.props.usersProp.maxUsers - 1 + + if (maxParticipants > 0 && //0 = no limit + RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= maxParticipants && + !userHasJoinedAlready) { + Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS)) + } else { + Right(()) + } + } + + private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = { + if (user.guestStatus != GuestStatus.ALLOW) { + Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED)) + } else { + Right(()) + } + } + + private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = { + if (user.banned) { + Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING)) + } else { + Right(()) + } + } + + private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = { + if (user.loggedOut) { + Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT)) + } else { + Right(()) + } + } + + private def updateParentMeetingWithNewListOfUsers() = { + if (liveMeeting.props.meetingProp.isBreakout) { + BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus) + } + } + + private def notifyPreviousUsersWithSameExtId(regUser: RegisteredUser) = { + RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers) + .filter(_.id != regUser.id) + .foreach { previousUser => + sendUserConnectedNotification(previousUser, regUser, liveMeeting) + } + } + + private def sendUserConnectedNotification(previousUser: RegisteredUser, newUser: RegisteredUser, liveMeeting: LiveMeeting) = { + val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg( + previousUser.id, + liveMeeting.props.meetingProp.intId, + "info", + "promote", + "app.mobileAppModal.userConnectedWithSameId", + "Notification to warn that user connect again from other browser/device", + Vector(newUser.name) + ) + outGW.send(notifyUserEvent) + } + + private def clearCachedVoiceUser(regUser: RegisteredUser) = + // fresh user joined (not due to reconnection). Clear (pop) the cached voice user + VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id) + + private def clearExpiredUserState(regUser: RegisteredUser) = + UserStateDAO.updateExpired(regUser.id, false) + + private def invalidateUserGraphqlConnection(regUser: RegisteredUser) = + Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW) + +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala index 3e36f9734d..d42b18cc7c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala @@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.bus.InternalEventBus +import org.bigbluebutton.core.db.UserDAO import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.models._ import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter } @@ -15,103 +16,79 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers { val eventBus: InternalEventBus def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = { - log.debug("RECEIVED ValidateAuthTokenReqMsg msg {}", msg) + log.debug(s"Received ValidateAuthTokenReqMsg msg $msg") - var failReason = "Invalid auth token." - var failReasonCode = EjectReasonCode.VALIDATE_TOKEN + val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers) + log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]") - log.info("Number of registered users [{}]", RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)) - val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, - liveMeeting.registeredUsers) - regUser match { - case Some(u) => - // Check if maxParticipants has been reached - // User are able to reenter if he already joined previously with the same extId - val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 && - RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers && - RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false + regUser.fold { + sendFailedValidateAuthTokenRespMsg(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN) + } { user => + val validationResult = for { + _ <- checkIfUserGuestStatusIsAllowed(user) + _ <- checkIfUserIsBanned(user) + _ <- checkIfUserLoggedOut(user) + _ <- validateMaxParticipants(user) + } yield user - // Check if banned user is rejoining. - // Fail validation if ejected user is rejoining. - // ralam april 21, 2020 - if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut && !hasReachedMaxParticipants) { - userValidated(u, state) - } else { - if (u.banned) { - failReason = "Banned user rejoining" - failReasonCode = EjectReasonCode.BANNED_USER_REJOINING - } else if (u.loggedOut) { - failReason = "User had logged out" - failReasonCode = EjectReasonCode.USER_LOGGED_OUT - } else if (hasReachedMaxParticipants) { - failReason = "The maximum number of participants allowed for this meeting has been reached." - failReasonCode = EjectReasonCode.MAX_PARTICIPANTS - } - validateTokenFailed( - outGW, - meetingId = liveMeeting.props.meetingProp.intId, - userId = msg.header.userId, - authToken = msg.body.authToken, - valid = false, - waitForApproval = false, - failReason, - failReasonCode, - state - ) - } + validationResult.fold( + reason => sendFailedValidateAuthTokenRespMsg(msg, reason._1, reason._2), + validUser => sendSuccessfulValidateAuthTokenRespMsg(validUser) + ) + } - case None => - validateTokenFailed( - outGW, - meetingId = liveMeeting.props.meetingProp.intId, - userId = msg.header.userId, - authToken = msg.body.authToken, - valid = false, - waitForApproval = false, - failReason, - failReasonCode, - state - ) + state + } + private def validateMaxParticipants(user: RegisteredUser): Either[(String, String), Unit] = { + if (liveMeeting.props.usersProp.maxUsers > 0 && + RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers && + RegisteredUsers.checkUserExtIdHasJoined(user.externId, liveMeeting.registeredUsers) == false) { + Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS)) + } else { + Right(()) } } - def validateTokenFailed( - outGW: OutMsgRouter, - meetingId: String, - userId: String, - authToken: String, - valid: Boolean, - waitForApproval: Boolean, - reason: String, - reasonCode: String, - state: MeetingState2x - ): MeetingState2x = { - val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0, - 0, reasonCode, reason) - outGW.send(event) - - // send a system message to force disconnection - // Comment out as meteor will disconnect the client. Requested by Tiago (ralam apr 28, 2020) - //Sender.sendDisconnectClientSysMsg(meetingId, userId, SystemUser.ID, reasonCode, outGW) - - state + private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = { + if (user.guestStatus != GuestStatus.ALLOW) { + Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED)) + } else { + Right(()) + } } - def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String, - valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, - reasonCode: String = EjectReasonCode.NOT_EJECT, reason: String = "User not ejected"): Unit = { - val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn, - authTokenValidatedOn, reasonCode, reason) + private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = { + if (user.banned) { + Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING)) + } else { + Right(()) + } + } + + private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = { + if (user.loggedOut) { + Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT)) + } else { + Right(()) + } + } + + private def sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = { + UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason) + + val event = MsgBuilder.buildValidateAuthTokenRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, msg.body.authToken, false, false, 0, + 0, failReasonCode, failReason) outGW.send(event) } - def userValidated(user: RegisteredUser, state: MeetingState2x): MeetingState2x = { + def sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = { val meetingId = liveMeeting.props.meetingProp.intId val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user) - sendValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, valid = true, waitForApproval = false, updatedUser.registeredOn, updatedUser.lastAuthTokenValidatedOn) - state + val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, true, false, updatedUser.registeredOn, + updatedUser.lastAuthTokenValidatedOn, EjectReasonCode.NOT_EJECT, "User not ejected") + outGW.send(event) } def sendAllUsersInMeeting(requesterId: String): Unit = { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala index 9077cdc2f7..b6ea0d16e9 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala @@ -77,12 +77,12 @@ object MeetingDAO { ).onComplete { case Success(rowsAffected) => { DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!") + ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat()) MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp) MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps) MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp) MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp) MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp) - ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat()) MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp) MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups) MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserConnectionStatusDAO.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserConnectionStatusDAO.scala index 170cd3e4be..12cb51bb48 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserConnectionStatusDAO.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserConnectionStatusDAO.scala @@ -19,7 +19,7 @@ class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatu val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt") } -object UserConnectionStatusdDAO { +object UserConnectionStatusDAO { def insert(meetingId: String, userId: String) = { DatabaseConnection.db.run( diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserDAO.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserDAO.scala index 4dc2bb1d0a..25a6d0c450 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserDAO.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/UserDAO.scala @@ -6,29 +6,31 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Failure, Success} case class UserDbModel( - userId: String, - extId: String, - meetingId: String, - name: String, - role: String, - avatar: String = "", - color: String = "", - sessionToken: String = "", - authed: Boolean = false, - joined: Boolean = false, - banned: Boolean = false, - loggedOut: Boolean = false, - guest: Boolean, - guestStatus: String, - registeredOn: Long, - excludeFromDashboard: Boolean, + userId: String, + extId: String, + meetingId: String, + name: String, + role: String, + avatar: String = "", + color: String = "", + sessionToken: String = "", + authed: Boolean = false, + joined: Boolean = false, + joinErrorMessage: Option[String], + joinErrorCode: Option[String], + banned: Boolean = false, + loggedOut: Boolean = false, + guest: Boolean, + guestStatus: String, + registeredOn: Long, + excludeFromDashboard: Boolean, ) class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") { override def * = ( - userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply) + userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply) val userId = column[String]("userId", O.PrimaryKey) val extId = column[String]("extId") val meetingId = column[String]("meetingId") @@ -39,6 +41,8 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") { val sessionToken = column[String]("sessionToken") val authed = column[Boolean]("authed") val joined = column[Boolean]("joined") + val joinErrorCode = column[Option[String]]("joinErrorCode") + val joinErrorMessage = column[Option[String]]("joinErrorMessage") val banned = column[Boolean]("banned") val loggedOut = column[Boolean]("loggedOut") val guest = column[Boolean]("guest") @@ -62,6 +66,8 @@ object UserDAO { sessionToken = regUser.sessionToken, authed = regUser.authed, joined = regUser.joined, + joinErrorCode = None, + joinErrorMessage = None, banned = regUser.banned, loggedOut = regUser.loggedOut, guest = regUser.guest, @@ -73,10 +79,10 @@ object UserDAO { ).onComplete { case Success(rowsAffected) => { DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!") - ChatUserDAO.insertUserPublicChat(meetingId, regUser.id) - UserConnectionStatusdDAO.insert(meetingId, regUser.id) + UserConnectionStatusDAO.insert(meetingId, regUser.id) UserCustomParameterDAO.insert(regUser.id, regUser.customParameters) UserClientSettingsDAO.insert(regUser.id, meetingId) + ChatUserDAO.insertUserPublicChat(meetingId, regUser.id) } case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user: $e") } @@ -94,6 +100,17 @@ object UserDAO { } } + def updateJoinError(userId: String, joinErrorCode: String, joinErrorMessage: String) = { + DatabaseConnection.db.run( + TableQuery[UserDbTableDef] + .filter(_.userId === userId) + .map(u => (u.joined, u.joinErrorCode, u.joinErrorMessage)) + .update((false, Some(joinErrorCode), Some(joinErrorMessage))) + ).onComplete { + case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user (Joined) table!") + case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user (Joined): $e") + } + } def delete(intId: String) = { diff --git a/bbb-graphql-client-test/src/App.js b/bbb-graphql-client-test/src/App.js index 4ddaedf26b..c1f5afcb50 100644 --- a/bbb-graphql-client-test/src/App.js +++ b/bbb-graphql-client-test/src/App.js @@ -30,6 +30,7 @@ function App() { const [sessionToken, setSessionToken] = useState(null); const [userId, setUserId] = useState(null); const [userName, setUserName] = useState(null); + const [userAuthToken, setUserAuthToken] = useState(null); const [graphqlClient, setGraphqlClient] = useState(null); const [enterApiResponse, setEnterApiResponse] = useState(''); @@ -46,6 +47,7 @@ function App() { if(json?.response?.internalUserID) { setUserId(json.response.internalUserID); setUserName(json.response.fullname); + setUserAuthToken(json.response.authToken); } }); } @@ -110,7 +112,7 @@ function App() {

- +

diff --git a/bbb-graphql-client-test/src/MyInfo.js b/bbb-graphql-client-test/src/MyInfo.js index 9f5d55c69d..ea61db4af5 100644 --- a/bbb-graphql-client-test/src/MyInfo.js +++ b/bbb-graphql-client-test/src/MyInfo.js @@ -1,7 +1,7 @@ -import {gql, useMutation, useQuery, useSubscription} from '@apollo/client'; +import {gql, useMutation, useSubscription} from '@apollo/client'; import React from "react"; -export default function MyInfo() { +export default function MyInfo({userAuthToken}) { //where is not necessary once user can update only its own status //Hasura accepts "now()" as value to timestamp fields @@ -20,17 +20,32 @@ export default function MyInfo() { updateUserClientEchoTestRunningAtMeAsNow(); }; + const [dispatchUserJoin] = useMutation(gql` + mutation UserJoin($authToken: String!, $clientType: String!) { + userJoin( + authToken: $authToken, + clientType: $clientType, + ) + } + `); + const handleDispatchUserJoin = (authToken) => { + dispatchUserJoin({ + variables: { + authToken: authToken, + clientType: 'HTML5', + }, + }); + }; + const { loading, error, data } = useSubscription( gql`subscription { user_current { userId name - meeting { - name - } - echoTestRunningAt - isRunningEchoTest + joined + joinErrorCode + joinErrorMessage } }` ); @@ -45,23 +60,23 @@ export default function MyInfo() { {/*Id*/} userId name - Meeting - echoTestRunningAt - isRunningEchoTest + joined + joinErrorCode + joinErrorMessage {data.user_current.map((curr) => { - console.log('meeting', curr); + console.log('user_current', curr); return ( {curr.userId} {curr.name} - {curr.meeting.name} - {curr.echoTestRunningAt} - + {curr.joined ? 'Yes' : 'No'} + {curr.joined ? '' : } - {curr.isRunningEchoTest ? 'Yes' : 'No'} + {curr.joinErrorCode} + {curr.joinErrorMessage} ); })} diff --git a/bbb-graphql-server/bbb_schema.sql b/bbb-graphql-server/bbb_schema.sql index ff97444c2e..468cabbeee 100644 --- a/bbb-graphql-server/bbb_schema.sql +++ b/bbb-graphql-server/bbb_schema.sql @@ -230,6 +230,8 @@ CREATE TABLE "user" ( "sessionToken" varchar(16), "authed" bool, "joined" bool, + "joinErrorCode" varchar(50), + "joinErrorMessage" varchar(400), "banned" bool, "loggedOut" bool, -- when user clicked Leave meeting button "guest" bool, --used for dialIn @@ -392,6 +394,8 @@ AS SELECT "user"."userId", "user"."role", "user"."authed", "user"."joined", + "user"."joinErrorCode", + "user"."joinErrorMessage", "user"."disconnected", "user"."expired", "user"."ejected", diff --git a/bbb-graphql-server/metadata/actions.graphql b/bbb-graphql-server/metadata/actions.graphql index 47ac9d6d97..4a6a0c4c86 100644 --- a/bbb-graphql-server/metadata/actions.graphql +++ b/bbb-graphql-server/metadata/actions.graphql @@ -23,3 +23,10 @@ type Mutation { ): Boolean } +type Mutation { + userJoin( + authToken: String! + clientType: String! + ): Boolean +} + diff --git a/bbb-graphql-server/metadata/actions.yaml b/bbb-graphql-server/metadata/actions.yaml index 7116ff4b64..f1ec57ab0e 100644 --- a/bbb-graphql-server/metadata/actions.yaml +++ b/bbb-graphql-server/metadata/actions.yaml @@ -17,6 +17,13 @@ actions: handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}' permissions: - role: bbb_client + - name: userJoin + definition: + kind: synchronous + handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}' + permissions: + - role: pre_join_bbb_client + - role: bbb_client custom_types: enums: [] input_objects: [] diff --git a/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_user_current.yaml b/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_user_current.yaml index adb29a5240..67b6236977 100644 --- a/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_user_current.yaml +++ b/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_user_current.yaml @@ -149,6 +149,8 @@ select_permissions: - isDialIn - isModerator - isRunningEchoTest + - joinErrorCode + - joinErrorMessage - joined - locked - loggedOut @@ -158,16 +160,39 @@ select_permissions: - pinned - presenter - raiseHand + - registeredAt - registeredOn - role - speechLocale - userId filter: - _and: - - meetingId: - _eq: X-Hasura-MeetingId - - userId: - _eq: X-Hasura-UserId + userId: + _eq: X-Hasura-UserId + - role: pre_join_bbb_client + permission: + columns: + - authed + - banned + - color + - disconnected + - ejectReason + - ejectReasonCode + - ejected + - expired + - extId + - guest + - joinErrorCode + - joinErrorMessage + - joined + - loggedOut + - name + - registeredAt + - registeredOn + - userId + filter: + userId: + _eq: X-Hasura-UserId + comment: "" update_permissions: - role: bbb_client permission: diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ConnectionController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ConnectionController.groovy index fc5c1eb5eb..d2e88b9a5e 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ConnectionController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ConnectionController.groovy @@ -81,12 +81,12 @@ class ConnectionController { def builder = new JsonBuilder() builder { "response" "authorized" - "X-Hasura-Role" "bbb_client" - "X-Hasura-Locked" u.locked ? "true" : "false" - "X-Hasura-LockedInMeeting" u.locked ? userSession.meetingID : "" - "X-Hasura-LockedUserId" u.locked ? userSession.internalUserId : "" - "X-Hasura-ModeratorInMeeting" u.isModerator() ? userSession.meetingID : "" - "X-Hasura-PresenterInMeeting" u.isPresenter() ? userSession.meetingID : "" + "X-Hasura-Role" u ? "bbb_client" : "pre_join_bbb_client" + "X-Hasura-Locked" u && u.locked ? "true" : "false" + "X-Hasura-LockedInMeeting" u && u.locked ? userSession.meetingID : "" + "X-Hasura-LockedUserId" u && u.locked ? userSession.internalUserId : "" + "X-Hasura-ModeratorInMeeting" u && u.isModerator() ? userSession.meetingID : "" + "X-Hasura-PresenterInMeeting" u && u.isPresenter() ? userSession.meetingID : "" "X-Hasura-UserId" userSession.internalUserId "X-Hasura-MeetingId" userSession.meetingID }