diff --git a/README.md b/README.md index fa45efdfb..92affe138 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,26 @@ including database connectivity information. * `make run` +## Creating Local Credentials + +First, you need to get a login token into the local database. We can do this by leveraging the +knowledge that an encrypted password entry of `''` will match against all supplied inputs: + + $ psql -U conch conch --command="insert into user_account (name, password, email) values ('me', '', 'your_email@joyent.com')" + +Now, we use this email and password to generate a login token: + + make run + curl -i -H'Content-Type: application/json' --url http://127.0.0.1:5001/login -d '{"email":"your_email@joyent.com","password":"anything"}' + +You will see output like this: + + {"jwt_token":"eyJInR5cCI6Iwhargarbl.eyJl9pZCI6ImM1MGYwhargarbl.WV3uJEvg0bqInI9pEtl04ZZ8ECN4yQOSmehello"} + +Save that token somewhere, such as in an environment variable or a file, for use in future API calls. You will include it in the "Authorization" header, for example: + + curl -i --url https://staging.conch.joyent.us/user/me --header "Authorization: Bearer eyJInR5cCI6Iwhargarbl.eyJl9pZCI6ImM1MGYwhargarbl.WV3uJEvg0bqInI9pEtl04ZZ8ECN4yQOSmehello" + ## Docker ### Compose diff --git a/lib/Conch/Plugin/ClientVerification.pm b/lib/Conch/Plugin/ClientVerification.pm index 89b7b5dc6..bdd0a7106 100644 --- a/lib/Conch/Plugin/ClientVerification.pm +++ b/lib/Conch/Plugin/ClientVerification.pm @@ -34,10 +34,11 @@ sub register ($self, $app, $config) { my $user_agent = $headers->user_agent; if (my $conch_ui_version = $headers->header('X-Conch-UI')) { - my ($major, $minor, $tiny, $rest) = $conch_ui_version =~ /^v(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?/; + my ($major, $minor, $tiny, $rest) = $conch_ui_version =~ /^v(\d+)\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?/; if (not $major or $major < 4) { - $c->log->warn('Conch UI too old: requires at least 4.x'); - return $c->status(403); + my $error = 'Conch UI too old: requires at least 4.x'; + $c->log->warn($error.' -- got major='.($major//'').', minor='.($minor//'')); + return $c->status(403, { error => $error }); } } elsif ($user_agent and $user_agent =~ /^conch shell/) { @@ -47,8 +48,9 @@ sub register ($self, $app, $config) { elsif ($user_agent and $user_agent =~ /^Conch\/((\d+)\.(\d+)\.(\d+)) /) { my ($all, $major, $minor, $rest) = ($1, $2, $3, $4); if ($all eq '0.0.0' or $major < 3) { - $c->log->warn('Conch Shell too old'); - return $c->status(403); + my $error = 'Conch Shell too old'; + $c->log->warn($error.' -- got major='.($major//'').', minor='.($minor//'')); + return $c->status(403, { error => $error }); } } }); diff --git a/lib/Conch/Plugin/Logging.pm b/lib/Conch/Plugin/Logging.pm index 79139181c..2166219c0 100644 --- a/lib/Conch/Plugin/Logging.pm +++ b/lib/Conch/Plugin/Logging.pm @@ -153,15 +153,20 @@ Logs the request and its response. headers => $req_headers, query_params => $c->req->query_params->to_hash, # no body_params: presently we do not permit application/x-www-form-urlencoded - $verbose && !(ref $req_json eq 'HASH' and exists $req_json->{password}) - ? ( body => $c->req->json // $c->req->text ) : (), + !$verbose ? () + : !defined $req_json ? ( body => $c->req->text ) + : ref $req_json ne 'HASH' || !exists $req_json->{password} ? ( body => $req_json ) + : ( body => +{ $req_json->%*, password => '--REDACTED--' } ), }, res => { headers => $res_headers, statusCode => $c->res->code, - $c->res->code >= 400 - || ($verbose && !(ref $res_json eq 'HASH' and grep /token/, keys $res_json->%*)) - ? ( body => $c->res->json // $c->res->text ) : (), + !$verbose && $c->res->code < 400 ? () + : !defined $res_json ? ( body => $c->res->text ) + : (ref $res_json ne 'HASH' || !grep /token/, keys $res_json->%*) + ? ( body => $res_json ) + : ( body => +{ $res_json->%*, + map +($_ => '--REDACTED--'), grep /token/, keys $res_json->%* } ), }, latency => int(1000 * $c->timing->elapsed('request_latency')), }; diff --git a/t/client-verification.t b/t/client-verification.t index 0e6045544..cc51a5e26 100644 --- a/t/client-verification.t +++ b/t/client-verification.t @@ -18,7 +18,8 @@ $t->get_ok('/ping', { 'User-Agent' => 'Mozilla/5.0' }) $t->get_ok('/ping', { 'User-Agent' => 'Mozilla/5.0 Macintosh', 'X-Conch-UI' => 'v3.0.2.1-gdeadbeef' }) ->status_is(403) - ->log_warn_is('Conch UI too old: requires at least 4.x') + ->json_is({ error => 'Conch UI too old: requires at least 4.x' }) + ->log_warn_is('Conch UI too old: requires at least 4.x -- got major=3, minor=0') ->log_info_is(superhashof({ req => superhashof({ user => 'NOT AUTHED', @@ -33,18 +34,23 @@ $t->get_ok('/ping', { 'User-Agent' => 'Mozilla/5.0 Macintosh', 'X-Conch-UI' => ' $t->get_ok('/ping', { 'User-Agent' => 'Mozilla/5.0 Macintosh', 'x-conch-ui' => 'v3.0.2.1-gdeadbeef' }) ->status_is(403) - ->log_warn_is('Conch UI too old: requires at least 4.x'); + ->json_is({ error => 'Conch UI too old: requires at least 4.x' }) + ->log_warn_is('Conch UI too old: requires at least 4.x -- got major=3, minor=0'); $t->get_ok('/ping', { 'User-Agent' => 'Mozilla/5.0 Macintosh', 'X-Conch-UI' => 'v4.0.0.3.gdeadbeef' }) ->status_is(200); +$t->get_ok('/ping', { 'User-Agent' => 'Mozilla/5.0 Macintosh', 'X-Conch-UI' => 'v4.1-0-gdeadbeef' }) + ->status_is(200); + $t->get_ok('/ping', { 'User-Agent' => 'conch shell v1.11.11-v1.11-0-g0ad9598' }) ->status_is(403) ->log_warn_is('Conch Shell too old'); $t->get_ok('/ping', { 'User-Agent' => 'Conch/0.0.0 ConchShell/blahblah...' }) ->status_is(403) - ->log_warn_is('Conch Shell too old'); + ->json_is({ error => 'Conch Shell too old' }) + ->log_warn_is('Conch Shell too old -- got major=0, minor=0'); $t->get_ok('/ping', { 'User-Agent' => 'Conch/3.12.0 ConchShell/blahblah...' }) ->status_is(200); diff --git a/t/conch-log.t b/t/conch-log.t index 11e7579dc..1f2eb7753 100644 --- a/t/conch-log.t +++ b/t/conch-log.t @@ -724,7 +724,7 @@ sub add_test_routes ($t) { remotePort => ignore, headers => superhashof({}), query_params => {}, - # no body! that contains the password!!! + body => { email => 'foo@example.com', password => '--REDACTED--' }, }, res => { headers => superhashof({}), @@ -759,11 +759,13 @@ sub add_test_routes ($t) { remotePort => ignore, headers => superhashof({}), query_params => {}, + # Test::Conch::authenticate sets set_session -> true + body => { email => 'conch@conch.joyent.us', password => '--REDACTED--', set_session => JSON::PP::true }, }, res => { headers => superhashof({}), statusCode => 200, - # no body! that contains the JWT!!! + body => { jwt_token => '--REDACTED--' }, }, }, 'dispatch line for /login success in verbose mode', @@ -799,7 +801,12 @@ sub add_test_routes ($t) { res => { headers => superhashof({}), statusCode => 201, - # no body! that contains the api token!!! + body => { + name => 'my api token', + token => '--REDACTED--', + (map +($_ => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/)), qw(created expires)), + last_used => undef, + }, }, }, 'dispatch line for creating a token in verbose mode does not contain the token string', @@ -830,7 +837,7 @@ sub add_test_routes ($t) { remotePort => ignore, headers => superhashof({ Authorization => '--REDACTED--' }), query_params => {}, - # no body! that contains the password!!! + body => { password => '--REDACTED--' }, }, res => { headers => superhashof({}),