diff --git a/conf/vroom.conf.sample b/conf/vroom.conf.sample index be5da27..c88f08b 100644 --- a/conf/vroom.conf.sample +++ b/conf/vroom.conf.sample @@ -23,10 +23,11 @@ secret => 'ChangeMe!', # App # Rooms without any activity for that long (in seconds) will be destroyed inactivityTimeout => 3600, -# Inactivity timeout (in seconds) for persistent rooms +# Inactivity timeout (in seconds) for rooms which have an owner password # 0 means they are not deleted. You can use a high number -# so that persistent rooms are kept long enough, but deleted when not used -persistentInactivityTimeout => 0, +# so that those rooms are kept long enough, but deleted when really not used +# The admin interface lets you flag some room as persistent, meaning they will never be deleted +reservedInactivityTimeout => 0, # A list of room names which are valid but too common to allow reservation # with an owner password commonRoomNames => [ diff --git a/lib/Vroom/I18N/en.pm b/lib/Vroom/I18N/en.pm index 73b0032..cb3cf18 100644 --- a/lib/Vroom/I18N/en.pm +++ b/lib/Vroom/I18N/en.pm @@ -122,16 +122,16 @@ our %Lexicon = ( "PASSWORD_PROTECT" => "Password protect", "PASSWORD_PROTECT_SET" => "A password will be needed to join this room", "PASSWORD_PROTECT_UNSET" => "No password will be asked to join this room", - "ROOM_NOW_PERSISTENT" => "This room is now persistent", - "ROOM_NO_MORE_PERSISTENT" => "This room isn't persistent anymore", + "ROOM_NOW_RESERVED" => "This room is now reserved", + "ROOM_NO_MORE_RESERVED" => "This room isn't reserved anymore", "PASSWORDS_DO_NOT_MATCH" => "Passwords do not match", - "MAKE_THIS_ROOM_PERSISTENT" => "Make this room persistent", - "SET_OWNER_PASS_PERSISTENT" => "To make this room persistent, you must set a manager password. Keep it carefully, " . + "RESERVE_THIS_ROOM" => "Reserve this room", + "SET_OWNER_PASS" => "To reserve this room, you must set an owner password. Keep it carefully, " . "it'll grant you access to the configuration menus next time you connect.", "A_STANDARD_ROOM_EXPIRES_AFTER_d" => "A standard room will be deleted after %d hour(s) without activity", - "A_PERSISTENT_ROOM" => "A persistant room", + "A_RESERVED_ROOM" => "A reserved room", "EXPIRE_AFTER_d" => "will be deleted after %d day(s) without activity", - "NEVER_EXPIRE" => "will be kept forever", + "WILL_NEVER_EXPIRE" => "will be kept forever", "CONFIRM_PASSWORD" => "Confirm password", "PROTECT_ROOM_WITH_PASSWORD" => "If this password is set, participants will have to type it before the system let them in", "ERROR_COMMON_ROOM_NAME" => "Sorry, this room name is too comon to be reserved", @@ -167,8 +167,11 @@ our %Lexicon = ( "NUMBER_OF_PARTICIPANTS" => "Number of participants", "LOCKED" => "Locked", "ASK_FOR_NAME" => "Require to enter a name", - "PASSWORD_PROTECTED" => "Password protection", + "JOIN_PASSWORD" => "Password to join the room", + "OWNER_PASSWORD" => "Password to manage the room", "PERSISTENT" => "Persistent", + "ROOM_NOW_PERSISTENT" => "This room is now persistent", + "ROOM_NO_MORE_PERSISTENT" => "This rooms isn't persistent anymore", "EMAIL_INVITE" => "Email invitation", "DELETE_THIS_ROOM" => "Delete this room", "CONFIRM_DELETE" => "Confirm delation", @@ -194,7 +197,7 @@ our %Lexicon = ( "HELP_PASSWORD_BUTTON" => "This button will protect access to this room with a password. Note that this password " . "isn't asked it you join the room through an email invitation (in which case the " . "authentication is done with a uniq token valid for two hours)", - "HELP_PERSISTENT_BUTTON" => "Make this room persistent, you'll be able to leave, reconnecte, and get configuration menus back. " . + "HELP_RESERVE_BUTTON" => "Reserve this room, you'll be able to leave, reconnect, and get configuration menus back. " . "The room will also be kept much longer.", "HELP_ASK_FOR_NAME_BUTTON" => "This will enforce participants to set their name before joining the room.", "HELP_WIPE_DATA_BUTTON" => "This will wipe room data (chat history and collaborative pad content)", @@ -321,15 +324,15 @@ our %Lexicon = ( "later, which are simple participants). For example, he can protect access with a password " . "which will be required before you can join the room. He also can set the manager's password " . "which will allow him, if he leaves the room, to recover its privileges when he connects again.", - "PERSISTENT_ROOMS" => "Persistant rooms", - "HELP_PERSISTENT_ROOMS" => "By default, rooms are ephemeral, which means they are automatically deleted if they " . - "have no activity for a long time. The room's creator can define a manager's password, " . - "which will make the room persistent. Note that a persistent room can still be deleted " . - "if it's not used for a very long period of time.", + "RESERVED_ROOMS" => "Reserved rooms", + "HELP_RESERVED_ROOMS" => "By default, rooms are ephemeral, which means they are automatically deleted if they " . + "have no activity for some time. The room's creator can define an owner's password, " . + "which will make the room reserved. A reserved room can still be deleted " . + "if it's not used for a very long period of time, but will last longuer on the system", "RESERVE_YOUR_ROOM" => "Reserve your room", "HELP_RESERVE_YOUR_ROOM" => "Want to reserve your room name so it's always available for you (company name, ongoing project " . - "etc.) ? Just set both a join password and the manager password. The room will be kept " . - "as long as the manager password is set (and as long as you use it from time to time)", + "etc.) ? Just set both a join password and the owner password. The room will be kept " . + "as long as the owner password is set (and as long as you use it from time to time)", "BE_NOTIFIED" => "Notifications", "HELP_BE_NOTIFIED" => "You can be notified by email as soon as someone joins one of your rooms. For example, " . "create a room, add a password to make it persistent and add the link in your email signature. " . diff --git a/lib/Vroom/I18N/fr.pm b/lib/Vroom/I18N/fr.pm index ed86aa1..cd97569 100644 --- a/lib/Vroom/I18N/fr.pm +++ b/lib/Vroom/I18N/fr.pm @@ -128,17 +128,17 @@ our %Lexicon = ( "PASSWORD_PROTECT" => "Protéger par mot de passe", "PASSWORD_PROTECT_SET" => "Un mot de passe sera demandé pour rejoindre ce salon", "PASSWORD_PROTECT_UNSET" => "Aucun mot de passe ne sera demandé pour rejoindre ce salon", - "ROOM_NOW_PERSISTENT" => "Ce salon est maintenant persistant", - "ROOM_NO_MORE_PERSISTENT" => "Ce salon n'est plus persistant", + "ROOM_NOW_RESERVED" => "Ce salon est maintenant réservé", + "ROOM_NO_MORE_RESERVED" => "Ce salon ne vous est plus réservé", "PASSWORDS_DO_NOT_MATCH" => "Les mots de passe ne correspondent pas", - "MAKE_THIS_ROOM_PERSISTENT" => "Rendre ce salon persistant", - "SET_OWNER_PASS_PERSISTENT" => "Pour rendre ce salon persistant, vous devez saisir un mot de passe. " . + "RESERVE_THIS_ROOM" => "Réserver ce salon", + "SET_OWNER_PASS" => "Pour réserver ce salon, vous devez saisir un mot de passe. " . "Conservez le soigneusement, il vous permettra de retrouver " . "l'accès aux menus de configuration quand vous vous reconnecterez.", "A_STANDARD_ROOM_EXPIRES_AFTER_d" => "Un salon classique sera détruit après %d heure(s) sans activité", - "A_PERSISTENT_ROOM" => "Un salon persistant", + "A_RESERVED_ROOM" => "Un salon réservé", "EXPIRE_AFTER_d" => "sera détruit après %d jour(s) sans activité", - "NEVER_EXPIRE" => "sera conservé indéfiniement", + "WILL_NEVER_EXPIRE" => "sera conservé indéfiniement", "CONFIRM_PASSWORD" => "Confirmation du mot de passe", "PROTECT_ROOM_WITH_PASSWORD" => "Si ce mot de passe est configuré, les participants devront le saisir avant de pouvoir " . "rejoindre le salon", @@ -174,8 +174,11 @@ our %Lexicon = ( "NUMBER_OF_PARTICIPANTS" => "Nombre de participants", "LOCKED" => "Verrouillé", "ASK_FOR_NAME" => "Exige de saisir un nom", - "PASSWORD_PROTECTED" => "Protection par mot de passe", + "JOIN_PASSWORD" => "Mot de passe d'accès au salon", + "OWNER_PASSWORD" => "Mot de passe de gestionnaire", "PERSISTENT" => "Persistant", + "ROOM_NOW_PERSISTENT" => "Ce salon est maintenant persistant", + "ROOM_NO_MORE_PERSISTENT" => "Ce salon n'est plus persistant", "EMAIL_INVITE" => "Invitation par email", "DELETE_THIS_ROOM" => "Supprimer ce salon", "CONFIRM_DELETE" => "Confirmer la suppression", @@ -213,7 +216,7 @@ our %Lexicon = ( "pas demandé lorsque l'on " . "rejoint un salon suite à une invitation par email (l'authentification se fait " . "par un jeton unique valide pendant deux heures", - "HELP_PERSISTENT_BUTTON" => "Permet de rendre le salon persistant. Vous pourrez donc vous reconnecter et " . + "HELP_RESERVE_BUTTON" => "Permet de réserver le salon. Vous pourrez donc vous reconnecter et " . "récupérer l'accès aux menus de configuration. Le salon sera également conservé " . "bien plus longtemps sur le système", "HELP_ASK_FOR_NAME_BUTTON" => "Permet d'imposer la saisie du nom avant de pouvoir rejoindre le salon", @@ -356,12 +359,13 @@ our %Lexicon = ( "de passe du gestionnaire ce qui lui permettra, s'il quitte le salon, de retrouver " . "ses privilèges lorsqu'il se connecte à nouveau. Ces privilèges peuvent aussi être " . "donnés à d'autres participants", - "PERSISTENT_ROOMS" => "Salons persistants", - "HELP_PERSISTENT_ROOMS" => "Par défaut, les salons sont éphémères, c'est à dire qu'ils sont automatiquement " . - "supprimés si ils ne présentent aucune activité pendant une durée prolongée. " . + "RESERVED_ROOMS" => "Salons réservés", + "HELP_RESERVED_ROOMS" => "Par défaut, les salons sont éphémères, c'est à dire qu'ils sont automatiquement " . + "supprimés si ils ne présentent aucune activité pendant un certains temps. " . "Le créateur du salon peut définir un mot de passe de gestionaire, ce qui " . - "rendra le salon persistant. Notez qu'un salon persistant peut tout de même " . - "être supprimé si il n'est pas utilisé pendant une très longue période.", + "rendra réservera le salon. Un salon réservé peut tout de même " . + "être supprimé si il n'est pas utilisé pendant une très longue période, mais le " . + "délais sera bien plus long.", "RESERVE_YOUR_ROOM" => "Réservez votre salon", "HELP_RESERVE_YOUR_ROOM" => "Vous souhaitez réserver le nom de votre salon pour qu'il soit toujours disponible " . "pour vous (nom de votre entreprise, nom d'un projet en cours etc.) ? Configurez simplement " . diff --git a/public/js/vroom.js b/public/js/vroom.js index 142f358..3e6b386 100644 --- a/public/js/vroom.js +++ b/public/js/vroom.js @@ -400,7 +400,7 @@ function initManage(){ } else if (param === 'ownerPassSwitch'){ if (state){ - $('#persistentModal').modal('show'); + $('#ownerPassModal').modal('show'); sw.bootstrapSwitch('toggleState', true); } else{ @@ -409,6 +409,11 @@ function initManage(){ sendAction(data,sw); } } + else if (param === 'persistentSwitch'){ + data.action = 'setPersistent'; + data.type = (state) ? 'set' : 'unset'; + sendAction(data,sw); + } // Something isn't implemented yet ? else{ $.notify(locale.ERROR_OCCURRED, 'error'); @@ -438,7 +443,7 @@ function initManage(){ } }); - $('#persistentForm').submit(function(event) { + $('#ownerPassForm').submit(function(event) { event.preventDefault(); var pass = $('#ownerPass').val(); var pass2 = $('#ownerPassConfirm').val(); @@ -451,7 +456,7 @@ function initManage(){ data.password = pass sendAction(data, $('#ownerPassSwitch')); $('#ownerPassSwitch').bootstrapSwitch('toggleState', true); - $('#persistentModal').modal('hide'); + $('#ownerPassModal').modal('hide'); } else{ $('#ownerPassConfirm').notify(locale.PASSWORDS_DO_NOT_MATCH, 'error'); @@ -599,8 +604,8 @@ function initVroom(room) { $('#joinPassButton').prop('checked', true); } if (data.owner_auth == 'yes'){ - $('#persistentLabel').addClass('btn-danger active'); - $('#persistentButton').prop('checked', true); + $('#ownerPassLabel').addClass('btn-danger active'); + $('#ownerPassButton').prop('checked', true); } } }); @@ -1430,13 +1435,13 @@ function initVroom(room) { var who = (peers[data.id].hasName) ? peers[data.id].displayName : locale.A_ROOM_ADMIN; if (data.payload.action == 'set'){ $.notify(sprintf(locale.OWNER_PASSWORD_CHANGED_BY_s, stringEscape(who)), 'info'); - $('#persistentLabel').addClass('btn-danger active'); - $('#persistentButton').prop('checked', true); + $('#ownerPassLabel').addClass('btn-danger active'); + $('#ownerPassButton').prop('checked', true); } else{ $.notify(sprintf(locale.OWNER_PASSWORD_REMOVED_BY_s, stringEscape(who)), 'info'); - $('#persistentLabel').removeClass('btn-danger active'); - $('#persistentButton').prop('checked', false); + $('#ownerPassLabel').removeClass('btn-danger active'); + $('#ownerPassButton').prop('checked', false); } } else{ @@ -1983,15 +1988,15 @@ function initVroom(room) { } }); - $('#persistentButton').change(function(){ + $('#ownerPassButton').change(function(){ var action = ($(this).is(':checked')) ? 'set':'unset'; if (action == 'set'){ - $('#persistentModal').modal('show'); + $('#ownerPassModal').modal('show'); // Uncheck the button now // so it's not inconsistent if we just close the modal dialog // submitting the form will recheck it - $('#persistentButton').prop('checked', false); - $('#persistentLabel').removeClass('active'); + $('#ownerPassButton').prop('checked', false); + $('#ownerPassLabel').removeClass('active'); } else{ $.ajax({ @@ -2008,7 +2013,7 @@ function initVroom(room) { if (data.status == 'success'){ $.notify(data.msg, 'info'); webrtc.sendToAll('owner_password', {action: 'remove'}); - $('#persistentLabel').removeClass('btn-danger active'); + $('#ownerPassLabel').removeClass('btn-danger active'); } else{ $.notify(data.msg, 'error'); @@ -2019,7 +2024,7 @@ function initVroom(room) { } }); - $('#persistentForm').submit(function(event) { + $('#ownerPassForm').submit(function(event) { event.preventDefault(); var pass = $('#ownerPass').val(); var pass2 = $('#ownerPassConfirm').val(); @@ -2034,15 +2039,15 @@ function initVroom(room) { }, error: function() { $.notify(locale.ERROR_OCCURRED, 'error'); - $('#persistentLabel').removeClass('btn-danger active'); + $('#ownerPassLabel').removeClass('btn-danger active'); }, success: function(data) { $('#ownerPass').val(''); $('#ownerPassConfirm').val(''); if (data.status == 'success'){ - $('#persistentModal').modal('hide'); - $('#persistentLabel').addClass('btn-danger active'); - $('#persistentButton').prop('checked', true); + $('#ownerPassModal').modal('hide'); + $('#ownerPassLabel').addClass('btn-danger active'); + $('#ownerPassButton').prop('checked', true); $.notify(data.msg, 'info'); webrtc.sendToAll('owner_password', {action: 'set'}); } @@ -2224,7 +2229,7 @@ function initVroom(room) { }); // Empty password fields on modal dismiss - $('#joinPassModal,#persistentModal').on('hide.bs.modal',function(){ + $('#joinPassModal,#ownerPassModal').on('hide.bs.modal',function(){ $(this).find(':input').val(''); }); diff --git a/public/vroom.pl b/public/vroom.pl index 8e4f329..0a29193 100755 --- a/public/vroom.pl +++ b/public/vroom.pl @@ -122,7 +122,7 @@ our $config = plugin Config => { poweredBy => 'Firewall Services', template => 'default', inactivityTimeout => 3600, - persistentInactivityTimeout => 0, + reservedInactivityTimeout => 5184000, commonRoomNames => [ qw() ], logLevel => 'info', chromeExtensionId => 'ecicdpoejfllflombfanbhfpgcimjddn', @@ -380,7 +380,7 @@ helper delete_rooms => sub { $self->app->log->debug('Removing unused rooms'); my $timeout = time()-$config->{inactivityTimeout}; my $sth = eval { - $self->db->prepare("SELECT `name` FROM `rooms` WHERE `activity_timestamp` < $timeout AND `persistent`='0';") + $self->db->prepare("SELECT `name` FROM `rooms` WHERE `activity_timestamp` < $timeout AND `persistent`='0' AND `owner_password` IS NULL;") } || return undef; $sth->execute(); my @toDeleteName = (); @@ -388,10 +388,10 @@ helper delete_rooms => sub { push @toDeleteName, $room; } my @toDeleteId = (); - if ($config->{persistentInactivityTimeout} > 0){ - $timeout = time()-$config->{persistentInactivityTimeout}; + if ($config->{reservedInactivityTimeout} > 0){ + $timeout = time()-$config->{reservedInactivityTimeout}; $sth = eval { - $self->db->prepare("SELECT `name` FROM `rooms` WHERE `activity_timestamp` < $timeout AND `persistent`='1';") + $self->db->prepare("SELECT `name` FROM `rooms` WHERE `activity_timestamp` < $timeout AND `persistent`='0' AND `owner_password` IS NOT NULL;") } || return undef; $sth->execute(); while (my $room = $sth->fetchrow_array){ @@ -560,21 +560,40 @@ helper set_owner_pass => sub { # Might be separated in the future if ($pass){ my $sth = eval { - $self->db->prepare("UPDATE `rooms` SET `owner_password`=?,`persistent`='1' WHERE `name`=?;") + $self->db->prepare("UPDATE `rooms` SET `owner_password`=? WHERE `name`=?;") } || return undef; my $pass = Crypt::SaltedHash->new(algorithm => 'SHA-256')->add($pass)->generate; $sth->execute($pass,$room) || return undef; - $self->app->log->debug($self->session('name') . " has set an owner password on room $room, which is now persistent"); + $self->app->log->debug($self->session('name') . " has set an owner password on room $room"); } else{ my $sth = eval { - $self->db->prepare("UPDATE `rooms` SET `owner_password`=?,`persistent`='0' WHERE `name`=?;") + $self->db->prepare("UPDATE `rooms` SET `owner_password`=? WHERE `name`=?;") } || return undef; $sth->execute(undef,$room) || return undef; - $self->app->log->debug($self->session('name') . " has removed the owner password on room $room, which is not persistent anymore"); + $self->app->log->debug($self->session('name') . " has removed the owner password on room $room"); } }; +# Make the room persistent +helper set_persistent => sub { + my $self = shift; + my ($room,$set) = @_; + my $data = $self->get_room($room); + return undef unless ($data); + my $sth = eval { + $self->db->prepare("UPDATE `rooms` SET `persistent`=? WHERE `name`=?") + } || return undef; + $sth->execute($set,$room) || return undef; + if ($set eq '1'){ + $self->app->log->debug("Room $room is now persistent"); + } + else{ + $self->app->log->debug("Room $room isn't persistent anymore"); + } + return 1; +}; + # Add an email address to the list of notifications helper add_notification => sub { my $self = shift; @@ -1238,7 +1257,7 @@ post '/*action' => [action => [qw/action admin\/action/]] => sub { $msg = $self->l('ERROR_COMMON_ROOM_NAME'); } elsif ($self->set_owner_pass($room,$pass)){ - $msg = ($pass) ? $self->l('ROOM_NOW_PERSISTENT') : $self->l('ROOM_NO_MORE_PERSISTENT'); + $msg = ($pass) ? $self->l('ROOM_NOW_RESERVED') : $self->l('ROOM_NO_MORE_RESERVED'); $status = 'success'; } } @@ -1258,6 +1277,30 @@ post '/*action' => [action => [qw/action admin\/action/]] => sub { } ); } + # Handle persistence + elsif ($action eq 'setPersistent'){ + my $type = $self->param('type'); + my $status = 'error'; + my $msg = $self->l('ERROR_OCCURRED'); + # Only possible through /admin/action + if ($prefix ne 'admin'){ + $msg = $self->l('NOT_ALLOWED'); + } + elsif($type eq 'set' && $self->set_persistent($room,'1')){ + $status = 'success'; + $msg = $self->l('ROOM_NOW_PERSISTENT'); + } + elsif($type eq 'unset' && $self->set_persistent($room,'0')){ + $status = 'success'; + $msg = $self->l('ROOM_NO_MORE_PERSISTENT'); + } + return $self->render( + json => { + msg => $msg, + status => $status + } + ); + } # A participant is trying to auth as an owner, lets check that elsif ($action eq 'authenticate'){ my $pass = $self->param('password'); diff --git a/templates/default/help.html.ep b/templates/default/help.html.ep index 0a5332f..9101b80 100644 --- a/templates/default/help.html.ep +++ b/templates/default/help.html.ep @@ -64,13 +64,13 @@