diff --git a/client/startup/requestGuestAccount.js b/client/startup/requestGuestAccount.js new file mode 100644 index 0000000000000..c018836c6ce15 --- /dev/null +++ b/client/startup/requestGuestAccount.js @@ -0,0 +1,17 @@ +Meteor.startup(function() { + Tracker.autorun(function() { + if (!Session.get('no-guest')) { + if (!Meteor.userId() + && RocketChat.settings.get('Accounts_AllowGuestAccess') + && RocketChat.settings.get('Accounts_AutomaticallyLoginAsGuest')) { + Meteor.call('getGuestAccount', function(error, user) { + if (error) { + console.log (error); + return; + } + Meteor.loginWithPassword(user, ''); + }); + } + } + }); +}); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 571c573e2d124..be2b2e6c5faf2 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -21,10 +21,15 @@ "Accounts_AllowedDomainsList": "Allowed Domains List", "Accounts_AllowedDomainsList_Description": "Comma-separated list of allowed domains", "Accounts_AllowEmailChange": "Allow Email Change", + "Accounts_AllowGuestAccess": "Allow anonymous/guest access", + "Accounts_GuestNamePostfix": "Guest name postfix", + "Accounts_GuestNamePrefix": "Guest name prefix", + "Accounts_AllowGuestToChooseName": "Allow guests to choose name", "Accounts_AllowPasswordChange": "Allow Password Change", "Accounts_AllowUserAvatarChange": "Allow User Avatar Change", "Accounts_AllowUsernameChange": "Allow Username Change", "Accounts_AllowUserProfileChange": "Allow User Profile Change", + "Accounts_AutomaticallyLoginAsGuest": "Automatically login as guest", "Accounts_AvatarResize": "Resize Avatars", "Accounts_AvatarSize": "Avatar Size", "Accounts_AvatarStorePath": "Avatar Storage Path", @@ -897,6 +902,8 @@ "Log_View_Limit": "Log View Limit", "Logged_out_of_other_clients_successfully": "Logged out of other clients successfully", "Login": "Login", + "Login_As_Guest": "Login as Guest", + "Login_Or_Register_Message_Box": "Login or Register to send messages", "Login_with": "Login with %s", "Logout": "Logout", "Logout_Others": "Logout From Other Logged In Locations", diff --git a/packages/rocketchat-lib/server/functions/sendMessage.coffee b/packages/rocketchat-lib/server/functions/sendMessage.coffee index bdce87faba4d4..572123b5cb1f5 100644 --- a/packages/rocketchat-lib/server/functions/sendMessage.coffee +++ b/packages/rocketchat-lib/server/functions/sendMessage.coffee @@ -1,5 +1,5 @@ RocketChat.sendMessage = (user, message, room, upsert = false) -> - if not user or not message or not room._id + if not user or not message or not room._id or user.guestId return false unless message.ts? diff --git a/packages/rocketchat-lib/server/methods/sendMessage.coffee b/packages/rocketchat-lib/server/methods/sendMessage.coffee index 95e8f71a66ab0..2354a9c12fe95 100644 --- a/packages/rocketchat-lib/server/methods/sendMessage.coffee +++ b/packages/rocketchat-lib/server/methods/sendMessage.coffee @@ -8,6 +8,9 @@ Meteor.methods if not Meteor.userId() throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'sendMessage' }) + if Meteor.user().guestId + throw new Meteor.Error('error-invalid-user', "Guests cannot send messages", { method: 'sendMessage' }) + if message.ts tsDiff = Math.abs(moment(message.ts).diff()) if tsDiff > 60000 diff --git a/packages/rocketchat-lib/server/methods/setUsername.js b/packages/rocketchat-lib/server/methods/setUsername.js index b64b1ddc5b3bf..6c2f3d1814043 100644 --- a/packages/rocketchat-lib/server/methods/setUsername.js +++ b/packages/rocketchat-lib/server/methods/setUsername.js @@ -28,6 +28,13 @@ Meteor.methods({ throw new Meteor.Error('username-invalid', `${_.escape(username)} is not a valid username, use only letters, numbers, dots, hyphens and underscores`); } + const guestPrefix = RocketChat.settings.get('Accounts_GuestNamePrefix'); + const guestPostfix = RocketChat.settings.get('Accounts_GuestNamePostfix'); + + if (user.guestId) { + username = guestPrefix + username + guestPostfix; + } + if (user.username !== undefined) { if (!username.toLowerCase() === user.username.toLowerCase()) { if (!RocketChat.checkUsernameAvailability(username)) { diff --git a/packages/rocketchat-lib/server/models/Messages.coffee b/packages/rocketchat-lib/server/models/Messages.coffee index 8ac7b4efcd7bb..b3dbf2981d936 100644 --- a/packages/rocketchat-lib/server/models/Messages.coffee +++ b/packages/rocketchat-lib/server/models/Messages.coffee @@ -369,6 +369,8 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base # INSERT createWithTypeRoomIdMessageAndUser: (type, roomId, message, user, extraData) -> + if user.guestId + return room = RocketChat.models.Rooms.findOneById roomId, { fields: { sysMes: 1 }} if room?.sysMes is false return diff --git a/packages/rocketchat-lib/server/models/Users.coffee b/packages/rocketchat-lib/server/models/Users.coffee index f5c6a201f3c1f..b8e924b1d6ea1 100644 --- a/packages/rocketchat-lib/server/models/Users.coffee +++ b/packages/rocketchat-lib/server/models/Users.coffee @@ -104,7 +104,8 @@ class ModelUsers extends RocketChat.models._Base username: { $nin: exceptions } - }] + }], + guestId: { $exists: false } } return @find query, options @@ -123,7 +124,8 @@ class ModelUsers extends RocketChat.models._Base { username: { $nin: exceptions } } - ] + ], + guestId: { $exists: false } return @find query, options @@ -247,6 +249,13 @@ class ModelUsers extends RocketChat.models._Base return @update _id, update + setGuestId: (_id, guestId) -> + update = + $set: + guestId: guestId + + return @update _id, update + setCustomFields: (_id, fields) -> values = {} for key, value of fields diff --git a/packages/rocketchat-lib/server/startup/settings.coffee b/packages/rocketchat-lib/server/startup/settings.coffee index 9690a669d0d09..affed93914d54 100644 --- a/packages/rocketchat-lib/server/startup/settings.coffee +++ b/packages/rocketchat-lib/server/startup/settings.coffee @@ -4,6 +4,11 @@ RocketChat.settings.add('uniqueID', process.env.DEPLOYMENT_ID or Random.id(), { # When you define a setting and want to add a description, you don't need to automatically define the i18nDescription # if you add a node to the i18n.json with the same setting name but with `_Description` it will automatically work. RocketChat.settings.addGroup 'Accounts', -> + @add 'Accounts_AllowGuestAccess', false, { type: 'boolean', public: true} + @add 'Accounts_GuestNamePrefix', 'anonymous-', { type: 'string', public: true, enableQuery: { _id: 'Accounts_AllowGuestAccess', value: true } } + @add 'Accounts_GuestNamePostfix', '', { type: 'string', public: true, enableQuery: { _id: 'Accounts_AllowGuestAccess', value: true } } + @add 'Accounts_AllowGuestToChooseName', true, { type: 'boolean', public: true, enableQuery: { _id: 'Accounts_AllowGuestAccess', value: true } } + @add 'Accounts_AutomaticallyLoginAsGuest', true, { type: 'boolean', public: true, enableQuery: { _id: 'Accounts_AllowGuestAccess', value: true } } @add 'Accounts_AllowDeleteOwnAccount', false, { type: 'boolean', public: true, enableQuery: { _id: 'Accounts_AllowUserProfileChange', value: true } } @add 'Accounts_AllowUserProfileChange', true, { type: 'boolean', public: true } @add 'Accounts_AllowUserAvatarChange', true, { type: 'boolean', public: true } diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html index 21e6041d869d6..0c785ea23b872 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html @@ -8,23 +8,25 @@ {{/if}}
- {{#with user}} -
-

{{username}}

-

{{name}}

+ {{#unless isGuest}} + {{#with user}} +
+

{{username}}

+

{{name}}

-
    - {{#if utc}}
  • {{userTime}} (UTC {{utc}})
  • {{/if}} - {{#each visitorEmails}}
  • {{address}}{{#if verified}} {{/if}}
  • {{/each}} - {{#each phone}}
  • {{phoneNumber}}
  • {{/each}} - {{#if lastLogin}}
  • {{_ "Created_at"}}: {{createdAt}}
  • {{/if}} - {{#if lastLogin}}
  • {{_ "Last_login"}}: {{lastLogin}}
  • {{/if}} - {{#if ip}}
  • {{ip}}
  • {{/if}} - {{#if os}}
  • {{os}}
  • {{/if}} - {{#if browser}}
  • {{browser}}
  • {{/if}} -
-
- {{/with}} +
    + {{#if utc}}
  • {{userTime}} (UTC {{utc}})
  • {{/if}} + {{#each visitorEmails}}
  • {{address}}{{#if verified}} {{/if}}
  • {{/each}} + {{#each phone}}
  • {{phoneNumber}}
  • {{/each}} + {{#if lastLogin}}
  • {{_ "Created_at"}}: {{createdAt}}
  • {{/if}} + {{#if lastLogin}}
  • {{_ "Last_login"}}: {{lastLogin}}
  • {{/if}} + {{#if ip}}
  • {{ip}}
  • {{/if}} + {{#if os}}
  • {{os}}
  • {{/if}} + {{#if browser}}
  • {{browser}}
  • {{/if}} +
+
+ {{/with}} + {{/unless}} {{#with room}}
diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.js b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.js index 3ee2edc3c034d..e86b95f137c2a 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.js +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.js @@ -29,6 +29,14 @@ Template.visitorInfo.helpers({ return this.tags && this.tags.join(', '); }, + isGuest() { + const user = Template.instance().user.get(); + if (user) { + return user.guestId || Meteor.user().guestId; + } + return false; + }, + customFields() { const fields = []; let livechatData = {}; diff --git a/packages/rocketchat-theme/client/imports/base.less b/packages/rocketchat-theme/client/imports/base.less index 78381af1c3cf1..09008e42cea35 100644 --- a/packages/rocketchat-theme/client/imports/base.less +++ b/packages/rocketchat-theme/client/imports/base.less @@ -4001,6 +4001,7 @@ body:not(.is-cordova) { .submit, .register, .forgot-password, + .login-as-guest, .back-to-login { margin-top: 12px; } diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee index 8759f6c5a8f7c..ec58ac5c5d53a 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee @@ -10,6 +10,12 @@ Template.userInfo.helpers user = Template.instance().user.get() return user.username + isGuest: -> + user = Template.instance().user.get() + if user + return user.guestId or Meteor.user().guestId + return false + email: -> user = Template.instance().user.get() return user.emails?[0]?.address diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html index 4d31d1bb69e94..90edbfa4e02bd 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html @@ -41,69 +41,70 @@

{{username}}

{{/with}}
{{/if}} + {{#if guestLoginAllowed}} +
+ +
+ {{/if}} {{/if}} {{/if}} {{/if}} diff --git a/packages/rocketchat-ui-login/client/username/username.coffee b/packages/rocketchat-ui-login/client/username/username.coffee index 225d190b79cb5..0d43c720c6654 100644 --- a/packages/rocketchat-ui-login/client/username/username.coffee +++ b/packages/rocketchat-ui-login/client/username/username.coffee @@ -2,12 +2,17 @@ Template.username.onCreated -> self = this self.username = new ReactiveVar - Meteor.call 'getUsernameSuggestion', (error, username) -> + if Meteor.user().guestId self.username.set ready: true - username: username - Meteor.defer -> - self.find('input').focus() + username: '' + else + Meteor.call 'getUsernameSuggestion', (error, username) -> + self.username.set + ready: true + username: username + Meteor.defer -> + self.find('input').focus() Template.username.helpers username: -> diff --git a/packages/rocketchat-ui-message/message/messageBox.coffee b/packages/rocketchat-ui-message/message/messageBox.coffee index 0948937f8e132..24da97ef4134b 100644 --- a/packages/rocketchat-ui-message/message/messageBox.coffee +++ b/packages/rocketchat-ui-message/message/messageBox.coffee @@ -9,6 +9,10 @@ katexSyntax = -> return false Template.messageBox.helpers + guestDisabled: -> + return if Meteor.user().guestId then 'disabled' else '' + placeholder: -> + return if Meteor.user().guestId then t('Login_Or_Register_Message_Box') else t('Message') roomName: -> roomData = Session.get('roomData' + this._id) return '' unless roomData @@ -87,6 +91,8 @@ Template.messageBox.helpers return RocketChat.settings.get('FileUpload_MediaTypeWhiteList') showFileUpload: -> + if Meteor.user().guestId + return false if (RocketChat.settings.get('FileUpload_Enabled')) roomData = Session.get('roomData' + this._id) if roomData?.t is 'd' @@ -98,13 +104,13 @@ Template.messageBox.helpers showMic: -> - return Template.instance().showMicButton.get() + return Template.instance().showMicButton.get() and not Meteor.user().guestId showVRec: -> - return Template.instance().showVideoRec.get() + return Template.instance().showVideoRec.get() and not Meteor.user().guestId showSend: -> - if not Template.instance().isMessageFieldEmpty.get() + if not Template.instance().isMessageFieldEmpty.get() and not Meteor.user().guestId return 'show-send' showLocation: -> diff --git a/packages/rocketchat-ui-message/message/messageBox.html b/packages/rocketchat-ui-message/message/messageBox.html index 65a36bf0e1097..28587c6a716bd 100644 --- a/packages/rocketchat-ui-message/message/messageBox.html +++ b/packages/rocketchat-ui-message/message/messageBox.html @@ -5,7 +5,7 @@ {{#if allowedToSend}}
- +
diff --git a/packages/rocketchat-ui-sidenav/client/accountBox.coffee b/packages/rocketchat-ui-sidenav/client/accountBox.coffee index b0f2bf2741b2e..383e97bf8b7fe 100644 --- a/packages/rocketchat-ui-sidenav/client/accountBox.coffee +++ b/packages/rocketchat-ui-sidenav/client/accountBox.coffee @@ -17,6 +17,9 @@ Template.accountBox.helpers username: username } + isGuest: -> + return Meteor.user()?.guestId + showAdminOption: -> return RocketChat.authz.hasAtLeastOnePermission( ['view-statistics', 'view-room-administration', 'view-user-administration', 'view-privileged-setting' ]) or RocketChat.AdminBox.getOptions().length > 0 @@ -36,6 +39,7 @@ Template.accountBox.events event.preventDefault() user = Meteor.user() Meteor.logout -> + Session.set 'no-guest', true RocketChat.callbacks.run 'afterLogoutCleanUp', user Meteor.call('logoutCleanUp', user) FlowRouter.go 'home' @@ -58,6 +62,14 @@ Template.accountBox.events event.preventDefault(); AccountBox.openFlex() + 'click #login': -> + user = Meteor.user() + Accounts.logout -> + Session.set 'no-guest', true + RocketChat.callbacks.run 'afterLogoutCleanUp', user + Meteor.call 'logoutCleanUp', user + FlowRouter.go 'home' + 'click .account-box-item': -> if @href FlowRouter.go @href diff --git a/packages/rocketchat-ui-sidenav/client/accountBox.html b/packages/rocketchat-ui-sidenav/client/accountBox.html index 61282239834df..408bf2b0c3603 100644 --- a/packages/rocketchat-ui-sidenav/client/accountBox.html +++ b/packages/rocketchat-ui-sidenav/client/accountBox.html @@ -13,18 +13,24 @@

{{username}}

{{/with}} diff --git a/packages/rocketchat-ui-vrecord/client/vrecord.coffee b/packages/rocketchat-ui-vrecord/client/vrecord.coffee index 0796958091d62..b481c56baa354 100644 --- a/packages/rocketchat-ui-vrecord/client/vrecord.coffee +++ b/packages/rocketchat-ui-vrecord/client/vrecord.coffee @@ -1,18 +1,21 @@ Template.vrecDialog.helpers + isGuest: -> + return Meteor.user().guestId + recordIcon: -> - if VideoRecorder.cameraStarted.get() and VideoRecorder.recording.get() + if VideoRecorder.cameraStarted.get() and VideoRecorder.recording.get() and !@isGuest return 'icon-stop' else return 'icon-circle' okDisabled: -> - if VideoRecorder.cameraStarted.get() and VideoRecorder.recordingAvailable.get() + if VideoRecorder.cameraStarted.get() and VideoRecorder.recordingAvailable.get() and !@isGuest return '' else return 'disabled' recordDisabled: -> - if VideoRecorder.cameraStarted.get() + if VideoRecorder.cameraStarted.get() and !@isGuest return '' else return 'disabled' diff --git a/packages/rocketchat-ui/lib/fileUpload.coffee b/packages/rocketchat-ui/lib/fileUpload.coffee index 690e4c707111e..6da95d1494564 100644 --- a/packages/rocketchat-ui/lib/fileUpload.coffee +++ b/packages/rocketchat-ui/lib/fileUpload.coffee @@ -14,6 +14,8 @@ readAsArrayBuffer = (file, callback) -> @fileUpload = (files) -> + if Meteor.user().guestId + return roomId = Session.get('openedRoom') files = [].concat files diff --git a/server/lib/accounts.js b/server/lib/accounts.js index 5dad2be2517cb..b5a8f3624a23c 100644 --- a/server/lib/accounts.js +++ b/server/lib/accounts.js @@ -60,7 +60,9 @@ Accounts.onCreateUser(function(options, user = {}) { RocketChat.callbacks.run('beforeCreateUser', options, user); user.status = 'offline'; - user.active = !RocketChat.settings.get('Accounts_ManuallyApproveNewUsers'); + // Only guests are allowed to have empty password + const isGuest = options.email.match(/@rocket-chat\.guest/) && options.password === ''; + user.active = !RocketChat.settings.get('Accounts_ManuallyApproveNewUsers') || isGuest; if (!user.name) { if (options.profile && options.profile.name) { @@ -112,9 +114,12 @@ Accounts.insertUserDoc = _.wrap(Accounts.insertUserDoc, function(insertUserDoc, _id: _id }); + const isGuest = user.emails[0] && user.emails[0].address.match(/@rocket-chat\.guest/); + if (user.username && options.joinDefaultChannels !== false && user.joinDefaultChannels !== false) { Meteor.runAsUser(_id, function() { - return Meteor.call('joinDefaultChannels', options.joinDefaultChannelsSilenced); + const silencedJoin = (isGuest ? true : options.joinDefaultChannelsSilenced); + return Meteor.call('joinDefaultChannels', silencedJoin); }); } @@ -135,6 +140,10 @@ Accounts.insertUserDoc = _.wrap(Accounts.insertUserDoc, function(insertUserDoc, } } + if (isGuest) { + roles = ['guest']; + } + RocketChat.authz.addUserRoles(_id, roles); return _id; diff --git a/server/methods/getGuestAccount.js b/server/methods/getGuestAccount.js new file mode 100644 index 0000000000000..372e332eb383a --- /dev/null +++ b/server/methods/getGuestAccount.js @@ -0,0 +1,46 @@ +Meteor.methods({ + getGuestAccount: () => { + const guestPrefix = RocketChat.settings.get('Accounts_GuestNamePrefix'); + const guestPostfix = RocketChat.settings.get('Accounts_GuestNamePostfix'); + + const guests = RocketChat.models.Users.find( + { + username: {$regex: new RegExp(guestPrefix + '\d' + guestPostfix, 'i')}, + guestId: {$exists: true} + }, + {sort: {guestId: -1}} + ).fetch(); + + let guestId = null, username = guestPrefix, anonymousCounter = 1; + if (!_.isEmpty(guests)) { + // Since we are sorting the results by guestId, the first one + // is always the greatest id + guestId = (guests[0].guestId ? guests[0].guestId + 1 : 1); + anonymousCounter = guests.length + 1; + } else { + guestId = 1; + } + + username += anonymousCounter + guestPostfix; + while (RocketChat.models.Users.findOne({ name: username })) { + anonymousCounter ++; + username = guestPrefix + anonymousCounter + guestPostfix; + } + + const email = username + '@rocket-chat.guest'; + + const userId = Accounts.createUser({ + email: email, + password: '' + }); + + RocketChat.models.Users.setName(userId, username); + RocketChat.models.Users.setGuestId(userId, guestId); + if (!RocketChat.settings.get('Accounts_AllowGuestToChooseName')) { + RocketChat.models.Users.setUsername(userId, username); + } + Accounts.setPassword(userId, ''); + + return email; + } +}); diff --git a/server/publications/activeUsers.js b/server/publications/activeUsers.js index 9ca4d82d0859b..501c4476c5ae6 100644 --- a/server/publications/activeUsers.js +++ b/server/publications/activeUsers.js @@ -7,7 +7,8 @@ Meteor.publish('activeUsers', function() { fields: { username: 1, status: 1, - utcOffset: 1 + utcOffset: 1, + guestId: 1 } }); }); diff --git a/server/publications/userData.js b/server/publications/userData.js index 2d12096f02105..c574010488614 100644 --- a/server/publications/userData.js +++ b/server/publications/userData.js @@ -15,6 +15,7 @@ Meteor.publish('userData', function() { language: 1, settings: 1, roles: 1, + guestId: 1, active: 1, defaultRoom: 1, customFields: 1, diff --git a/server/startup/removeOfflineGuests.js b/server/startup/removeOfflineGuests.js new file mode 100644 index 0000000000000..9fb4ab4082b99 --- /dev/null +++ b/server/startup/removeOfflineGuests.js @@ -0,0 +1,14 @@ +Meteor.startup(() => { + const query = { + username: {$exists: 1}, + status: {$in: ['online', 'away', 'busy']} + }; + RocketChat.models.Users.find(query).observeChanges({ + removed: id => { + const user = RocketChat.models.Users.findOne({_id: id}); + if (user && user.guestId) { + RocketChat.models.Users.remove(id); + } + } + }); +});