1package Mango::Auth::SCRAM;
2
3use Mojo::Base 'Mango::Auth';
4use Mojo::Util qw(dumper md5_sum encode b64_encode b64_decode);
5use Mango::BSON 'bson_doc';
6
7EVAL: {
8  local $@;
9  die "Authen::SCRAM is required to use SCRAM-SHA-1\n"
10    unless eval { require Authen::SCRAM::Client; 1 };
11}
12
13sub _credentials {
14  my ($self, $creds) = @_;
15
16  # [db, user, pass]
17  $creds->[2]
18    = md5_sum(encode("UTF-8", $creds->[1] . ":mongo:" . $creds->[2]));
19  $self->{credentials} = $creds;
20}
21
22sub _authenticate {
23  my ($self, $id) = @_;
24
25  my $mango = $self->mango;
26  my $cnx   = $self->mango->{connections}{$id};
27  my $creds = $self->{credentials};
28
29  my ($db, $user, $pass) = @$creds;
30
31  my $scram_client = Authen::SCRAM::Client->new(
32    skip_saslprep => 1,
33    username      => $user,
34    password      => $pass
35  );
36
37  my $delay = Mojo::IOLoop::Delay->new;
38  my $conv_id;
39
40  $delay->steps(
41    sub {
42      my ($d, $mango, $err, $doc) = @_;
43      $conv_id = $doc->{conversationId};
44      my $final_msg = $scram_client->final_msg(b64_decode $doc->{payload});
45
46      my $command = $self->_cmd_sasl_continue($conv_id, $final_msg);
47      $mango->_fast($id, $db, $command, $d->begin(0));
48    },
49    sub {
50      my ($d, $mango, $err, $doc) = @_;
51      $scram_client->validate(b64_decode $doc->{payload});
52
53      my $command = $self->_cmd_sasl_continue($conv_id, '');
54      $mango->_fast($id, $db, $command, $d->begin(0));
55    },
56    sub {
57      my ($d, $mango, $err, $doc) = @_;
58      $mango->emit(connection => $id)->_next;
59    }
60  );
61
62  my $command = $self->_cmd_sasl_start($scram_client->first_msg);
63  $mango->_fast($id, $db, $command, $delay->begin(0));
64
65  $delay->wait;
66  $delay->ioloop->one_tick unless $delay->ioloop->is_running;
67}
68
69sub _cmd_sasl_start {
70  my ($self, $first_msg) = @_;
71
72  bson_doc(
73    'saslStart'     => 1,
74    'mechanism'     => 'SCRAM-SHA-1',
75    'payload'       => b64_encode($first_msg, ''),
76    'autoAuthorize' => 1,
77  );
78}
79
80sub _cmd_sasl_continue {
81  my ($self, $conv_id, $final_msg) = @_;
82
83  bson_doc(
84    'saslContinue'   => 1,
85    'conversationId' => $conv_id,
86    'payload'        => $final_msg ? b64_encode($final_msg, '') : ''
87  );
88}
89
901;
91
92=encoding utf8
93
94=head1 NAME
95
96Mango::Auth::SCRAM - SCRAM-SHA-1 Authentication
97
98=head1 DESCRIPTION
99
100The default authentication backend for L<Mango> using the SCRAM-SHA-1 algorithm.
101It requires L<Authen::SCRAM>.
102
103=head1 SEE ALSO
104
105L<Mango>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
106
107=cut
108