diff --git a/lib/Convos/Controller/Connection.pm b/lib/Convos/Controller/Connection.pm index 51b1c5062..e40ba2ff5 100644 --- a/lib/Convos/Controller/Connection.pm +++ b/lib/Convos/Controller/Connection.pm @@ -10,6 +10,7 @@ sub create { my $url = Mojo::URL->new($json->{url} || ''); $url->path("/$json->{conversation_id}") if $json->{conversation_id}; + $url->query->param(remote_address => $self->tx->remote_address); if ($self->settings('forced_connection')) { my $default_connection = Mojo::URL->new($self->settings('default_connection')); @@ -77,6 +78,7 @@ sub update { my $url = Mojo::URL->new($json->{url} || ''); $url = $connection->url unless $url->host; + $url->query->param(remote_address => $self->tx->remote_address); unless ($self->settings('forced_connection')) { $url->scheme($json->{protocol} || $connection->url->scheme || ''); diff --git a/lib/Convos/Core/Connection/Irc.pm b/lib/Convos/Core/Connection/Irc.pm index f4d1ade54..d640a745e 100644 --- a/lib/Convos/Core/Connection/Irc.pm +++ b/lib/Convos/Core/Connection/Irc.pm @@ -8,6 +8,7 @@ use Mojo::JSON qw(false true); use Mojo::Parameters; use Mojo::Util qw(b64_decode b64_encode gzip gunzip term_escape trim); use Parse::IRC (); +use Socket; use Time::HiRes 'time'; use constant IS_TESTING => $ENV{HARNESS_ACTIVE} || 0; @@ -18,6 +19,7 @@ use constant PERIDOC_INTERVAL => $ENV{CONVOS_IRC_PERIDOC_INTERVAL} || 60; require Convos; our $VERSION = Convos->VERSION; +our $CONVOS_URL = 'https://convos.chat'; our %CTCP_QUOTE = ("\012" => 'n', "\015" => 'r', "\0" => '0', "\cP" => "\cP"); my %CLASS_DATA; @@ -31,7 +33,7 @@ sub disconnect_p { $self->{myinfo}{authenticated} = false; $self->{myinfo}{capabilities} = {}; $self->{disconnecting} = 1; # Prevent getting queued - $self->_write("QUIT :https://convos.chat", sub { $self->_stream_remove($p) }); + $self->_write("QUIT :$CONVOS_URL", sub { $self->_stream_remove($p) }); return $p; } @@ -1033,19 +1035,27 @@ sub _stream { $self->SUPER::_stream($loop, $err, $stream); return if $err; - my $url = $self->url; - my $nick = $self->nick; - my $user = $url->username || $nick; - $user =~ s/^[^a-zA-Z0-9]/x/; - my $mode = $url->query->param('mode') || 0; + my $url = $self->url; + if (my $password = $self->_web_irc_password) { + my $remote_address = $url->query->param('remote_address') || '127.0.0.1'; + my $remote_hostname = gethostbyaddr(inet_aton($remote_address), AF_INET) || $remote_address; + $self->_write(sprintf "WEBIRC %s %s %s %s\r\n", + $password, 'convos', $remote_hostname, $remote_address); + } + $self->_write("CAP LS\r\n"); $self->_write(sprintf "PASS %s\r\n", $url->password) if length $url->password and !$self->_sasl_mechanism; + + my $nick = $self->nick; $self->_write("NICK $nick\r\n"); - my $convos = "https://convos.chat"; + my $mode = $url->query->param('mode') || 0; + my $user = $url->username || $nick; my $realname = $url->query->param('realname'); - $realname = $realname ? "$realname via $convos" : $convos; + $realname = $realname ? "$realname via $CONVOS_URL" : $CONVOS_URL; + + $user =~ s/^[^a-zA-Z0-9]/x/; $self->_write("USER $user $mode * :$realname\r\n"); } @@ -1095,6 +1105,15 @@ CHUNK: } } +sub _web_irc_password { + my $name = shift->name; + state $pw = {}; + return $pw->{$name} if $pw->{$name}; + my $key = sprintf 'CONVOS_WEBIRC_PASSWORD_%s', uc $name; + $key =~ s!\W!_!g; + return $pw->{$key} = Mojo::URL->new($ENV{$key} || ''); +} + # This method is used to write a message to the IRC server and wait for a # response in the form of one or more events. # diff --git a/t/Server/Irc.pm b/t/Server/Irc.pm index 9bfb57ee3..89fddcca8 100644 --- a/t/Server/Irc.pm +++ b/t/Server/Irc.pm @@ -208,7 +208,8 @@ sub _patch_connection_class { my $self = shift; Mojo::Util::monkey_patch( - $self->connection_class => can => sub { + $self->connection_class, + can => sub { my ($conn, $method) = @_; return shift->SUPER::can(@_) unless ref $conn and $conn->name eq 'server' && $method =~ m!^_\w+_event_!; @@ -216,7 +217,7 @@ sub _patch_connection_class { } ); - Mojo::Util::monkey_patch($self->connection_class => write => sub { shift->_write(@_) }) + Mojo::Util::monkey_patch($self->connection_class, write => sub { shift->_write(@_) }) unless $self->connection_class->can('write'); } diff --git a/t/irc-webirc.t b/t/irc-webirc.t new file mode 100644 index 000000000..52a43a0fc --- /dev/null +++ b/t/irc-webirc.t @@ -0,0 +1,30 @@ +#!perl +BEGIN { $ENV{CONVOS_SKIP_CONNECT} = 1 } +use lib '.'; +use t::Helper; +use t::Server::Irc; +use Convos::Core; +use Convos::Core::Backend::File; + +$ENV{CONVOS_WEBIRC_PASSWORD_EXAMPLE} = 'secret_passphrase'; + +my $server = t::Server::Irc->new->start; +my $core = Convos::Core->new; +my $user = $core->user({email => 'superwoman@example.com'}); +$user->save_p->$wait_success; + +my $connection = $user->connection({name => 'example', protocol => 'irc'}); +$connection->save_p->$wait_success; + +$server->client($connection)->server_event_ok( + '_irc_event_webirc', + sub { + my ($connection, $msg) = @_; + is_deeply $msg->{params}, [qw(secret_passphrase convos localhost 127.0.0.1)], 'webirc message'; + } +)->server_event_ok('_irc_event_cap')->server_event_ok('_irc_event_nick') + ->server_write_ok(":example CAP * LS :\r\n")->client_event_ok('_irc_event_cap') + ->server_write_ok(['welcome.irc'])->client_event_ok('_irc_event_rpl_welcome') + ->process_ok('webirc'); + +done_testing; diff --git a/t/web-connections.t b/t/web-connections.t index 5ef8b419c..f3bd19532 100644 --- a/t/web-connections.t +++ b/t/web-connections.t @@ -37,30 +37,31 @@ $t->get_ok('/api/connections')->status_is(200)->json_is( protocol => 'irc', service_accounts => [qw(chanserv nickserv)], state => 'disconnected', - url => 'irc://irc.example.com:6667', + url => 'irc://irc.example.com:6667?remote_address=127.0.0.1', wanted_state => 'disconnected', } )->json_is('/connections/1/connection_id', 'irc-localhost') ->json_is('/connections/1/name', 'localhost') ->json_is('/connections/1/wanted_state', 'disconnected') - ->json_is('/connections/1/url', "irc://localhost:$port"); + ->json_is('/connections/1/url', "irc://localhost:$port?remote_address=127.0.0.1"); $t->post_ok('/api/connection/irc-doesnotexist', json => {url => 'foo://example.com:9999'}) ->status_is(404); $t->post_ok('/api/connection/irc-example', json => {})->status_is(200); my $connection = $user->get_connection('irc-localhost'); -$t->post_ok('/api/connection/irc-localhost', json => {url => "irc://localhost:$port"}) - ->status_is(200)->json_is('/name' => 'localhost')->json_is('/state' => 'disconnected'); -$t->post_ok('/api/connection/irc-localhost', json => {url => 'irc://example.com:9999'}) - ->status_is(200)->json_is('/name' => 'localhost') - ->json_like('/url' => qr{irc://example\.com:9999}); +$t->post_ok('/api/connection/irc-localhost', + json => {url => "irc://localhost:$port?remote_address=127.0.0.1"})->status_is(200) + ->json_is('/name' => 'localhost')->json_is('/state' => 'disconnected'); +$t->post_ok('/api/connection/irc-localhost', + json => {url => 'irc://example.com:9999?remote_address=127.0.0.1'})->status_is(200) + ->json_is('/name' => 'localhost')->json_like('/url' => qr{irc://example\.com:9999}); $connection->state(disconnected => ''); $t->post_ok('/api/connection/irc-localhost', json => {url => 'irc://example.com:9999', wanted_state => 'connected'})->status_is(200) ->json_is('/name' => 'localhost')->json_is('/state' => 'queued') - ->json_is('/url' => 'irc://example.com:9999?nick=superman&tls=1'); + ->json_is('/url' => 'irc://example.com:9999?remote_address=127.0.0.1&nick=superman&tls=1'); $connection->state(connected => ''); $t->post_ok( @@ -71,26 +72,29 @@ $t->post_ok( } )->status_is(200)->json_is('/name' => 'localhost')->json_is('/state' => 'connected') ->json_is('/on_connect_commands', ['/msg NickServ identify s3cret', '/msg too_cool 123']) - ->json_is('/url' => 'irc://example.com:9999?tls=1&nick=superman'); + ->json_is('/url' => 'irc://example.com:9999?tls=1&remote_address=127.0.0.1&nick=superman'); $t->post_ok('/api/connection/irc-localhost', json => {url => 'irc://foo:bar@example.com:9999?tls=0&nick=superman'})->status_is(200) - ->json_is('/url' => 'irc://foo:bar@example.com:9999?tls=0&nick=superman') + ->json_is('/url' => 'irc://foo:bar@example.com:9999?tls=0&nick=superman&remote_address=127.0.0.1') ->json_is('/state' => 'queued'); $connection->state(connected => ''); $t->post_ok('/api/connection/irc-localhost', json => {url => 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman', wanted_state => 'connected'}) - ->status_is(200)->json_is('/url' => 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman') + ->status_is(200) + ->json_is( + '/url' => 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman&remote_address=127.0.0.1') ->json_is('/state' => 'queued'); -is $connection->TO_JSON(1)->{url}, 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman', - 'to json url'; +is $connection->TO_JSON(1)->{url}, + 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman&remote_address=127.0.0.1', 'to json url'; $t->post_ok('/api/connection/irc-localhost', json => {url => 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman'})->status_is(200); -is $connection->TO_JSON(1)->{url}, 'irc://foo:s3cret@example.com:9999?tls=0&nick=superman', +is $connection->TO_JSON(1)->{url}, + 'irc://foo:s3cret@example.com:9999?tls=0&remote_address=127.0.0.1&nick=superman', 'no change with same username'; $t->get_ok('/api/connections')->status_is(200)->json_is('/connections/1/on_connect_commands',