blob: 3eda76aa2eb3db8008af334b10514761549bda9e [file] [log] [blame]
Akron80027d12016-01-20 17:36:36 +01001package Kalamar::Plugin::KalamarUser;
2use Mojo::Base 'Mojolicious::Plugin';
3use Mojo::ByteStream 'b';
4
5has 'api';
Akrondadacf12016-01-22 19:24:59 +01006has 'ua';
Akron80027d12016-01-20 17:36:36 +01007
Akronc86bbc42017-03-29 13:19:06 +02008# TODO: Merge with meta-button
9
Akron80027d12016-01-20 17:36:36 +010010sub register {
11 my ($plugin, $mojo, $param) = @_;
12
13 # Load parameter from config file
14 if (my $config_param = $mojo->config('Kalamar')) {
15 $param = { %$param, %$config_param };
16 };
17
Akron2cb9a3d2016-02-09 23:59:46 +010018 # Load 'notifications' plugin
19 unless (exists $mojo->renderer->helpers->{notify}) {
20 $mojo->plugin(Notifications => {
21 HTML => 1
22 });
23 };
24
Akron80027d12016-01-20 17:36:36 +010025 # Set API!
26 $plugin->api($param->{api}) or return;
Akronc95c9e72017-03-30 21:53:51 +020027 $plugin->ua(Mojo::UserAgent->new(
28 connect_timeout => 15,
29 inactivity_timeout => 60
30 ));
Akron80027d12016-01-20 17:36:36 +010031
Akronbe9d5b32017-04-05 20:48:24 +020032 # Set app to server
33 $plugin->ua->server->app($mojo);
34
Akron80027d12016-01-20 17:36:36 +010035 # Get the user token necessary for authorization
36 $mojo->helper(
37 'user_auth' => sub {
38 my $c = shift;
39
40 # Get token from stash
41 my $token = $c->stash('auth');
42 return $token if $token;
43
Akron3cd391e2017-03-29 23:42:54 +020044 # Get auth from session
45 my $auth = $c->session('auth') or return;
46
Akron80027d12016-01-20 17:36:36 +010047 # Set token to stash
Akron3cd391e2017-03-29 23:42:54 +020048 $c->stash(auth => $auth);
49 return $auth;
50 }
51 );
52
53 $mojo->helper(
54 'user.ua' => sub {
55 my $c = shift;
56 my $auth = $c->user_auth;
Akronc95c9e72017-03-30 21:53:51 +020057 my $client = $c->req->headers->header('X-Forwarded-For');
58
Akron3cd391e2017-03-29 23:42:54 +020059 return $plugin->ua unless $auth;
Akronc95c9e72017-03-30 21:53:51 +020060
Akron3cd391e2017-03-29 23:42:54 +020061 my $ua = Mojo::UserAgent->new;
Akronbe9d5b32017-04-05 20:48:24 +020062
63 # Set app to server
64 $ua->server->app($mojo);
65
Akron3cd391e2017-03-29 23:42:54 +020066 $ua->on(
67 start => sub {
68 my ($ua, $tx) = @_;
Akronc95c9e72017-03-30 21:53:51 +020069 my $headers = $tx->req->headers;
70 $headers->header('Authorization' => $auth);
71 $headers->header('X-Forwarded-For' => $client);
Akron3cd391e2017-03-29 23:42:54 +020072 }
73 );
74 return $ua;
Akron80027d12016-01-20 17:36:36 +010075 }
76 );
77
78 # Login
79 $mojo->helper(
80 'user.login' => sub {
81 my $c = shift;
82 my ($user, $pwd) = @_;
83
Akrondadacf12016-01-22 19:24:59 +010084 return if (index($user, ':') >= 0);
Akron80027d12016-01-20 17:36:36 +010085
Akronc95c9e72017-03-30 21:53:51 +020086 $c->app->log->debug("Login from user $user:$pwd");
87
Akron80027d12016-01-20 17:36:36 +010088 my $url = Mojo::URL->new($plugin->api)->path('auth/apiToken');
Akron3cd391e2017-03-29 23:42:54 +020089 my $tx = $plugin->ua->get($url => {
Akronc95c9e72017-03-30 21:53:51 +020090 Authorization => 'Basic ' . b($user . ':' . $pwd)->b64_encode->trim
Akron80027d12016-01-20 17:36:36 +010091 });
92
93 # Login successful
94 if (my $res = $tx->success) {
Akrone8235be2016-06-27 11:02:18 +020095
Akronc95c9e72017-03-30 21:53:51 +020096 $c->app->log->debug("Transaction: " . $res->to_string);
97
Akronc86bbc42017-03-29 13:19:06 +020098 my $jwt = $res->json;
Akron80027d12016-01-20 17:36:36 +010099
Akronc95c9e72017-03-30 21:53:51 +0200100 unless ($jwt) {
101 $c->notify(error => 'Response is no valid JWT (remote)');
102 return;
103 };
Akron3cd391e2017-03-29 23:42:54 +0200104
105 # TODO: Deal with user return values.
106
Akronc86bbc42017-03-29 13:19:06 +0200107 my $auth = $jwt->{token_type} . ' ' . $jwt->{token};
Akron80027d12016-01-20 17:36:36 +0100108
Akronc86bbc42017-03-29 13:19:06 +0200109 $mojo->log->debug(qq!Login successful: "$user" with "$auth"!);
Akron80027d12016-01-20 17:36:36 +0100110
Akronc86bbc42017-03-29 13:19:06 +0200111 # Set session info
112 $c->session(user => $user);
113 $c->session(auth => $auth);
Akron80027d12016-01-20 17:36:36 +0100114
Akronc86bbc42017-03-29 13:19:06 +0200115 $c->stash(user => $user);
116 $c->stash(auth => $auth);
Akron80027d12016-01-20 17:36:36 +0100117
Akronc86bbc42017-03-29 13:19:06 +0200118 # Set cache
119 $c->chi('user')->set($auth => $user);
120 return 1;
Akron2cb9a3d2016-02-09 23:59:46 +0100121 }
122
123 elsif (my $e = $tx->error) {
Akronc86bbc42017-03-29 13:19:06 +0200124 $c->notify(
125 error =>
126 ($e->{code} ? $e->{code} . ': ' : '') .
127 $e->{message} . ' for Login (remote)'
128 );
Akronc95c9e72017-03-30 21:53:51 +0200129 $c->app->log->debug($e->{code} . ($e->{message} ? ' - ' . $e->{message} : ''));
Akron80027d12016-01-20 17:36:36 +0100130 };
131
132 $mojo->log->debug(qq!Login fail: "$user"!);
133
134 return;
135 }
136 );
137
138 # Get details, settings etc. with authorization
139 $mojo->helper(
140 'user.get' => sub {
141 my $c = shift;
142 my $param = shift;
143
Akrone8235be2016-06-27 11:02:18 +0200144 # 'info' is useless!
Akrondadacf12016-01-22 19:24:59 +0100145 return unless $param =~ m/^details|settings$/;
Akron80027d12016-01-20 17:36:36 +0100146
147 # The user may be logged in
148 my $auth = ($c->stash('auth') || $c->session('auth')) or return;
149
150 # Get namespaced cache
151 my $chi = $c->chi('user');
152
153 # Get user and check, if the user is real
154 my $user = $chi->get($auth);
155
156 # Check if the user is really logged in
157 my $value = $chi->get($user . '_' . $param);
158
159 unless ($value) {
Akrondadacf12016-01-22 19:24:59 +0100160
Akronc86bbc42017-03-29 13:19:06 +0200161 my $tx = $plugin->build_authorized_tx($auth, 'GET', 'user/' . $param);
162 $tx = $plugin->ua->start($tx);
Akrone8235be2016-06-27 11:02:18 +0200163
Akronc86bbc42017-03-29 13:19:06 +0200164 unless ($value = $tx->success) {
165 return;
166 }
167 # else {
168 # warn $c->dumper($value->json);
169 # };
170 if ($value) {
171 $value = $value->json;
172 };
Akrondadacf12016-01-22 19:24:59 +0100173
Akronc86bbc42017-03-29 13:19:06 +0200174 $chi->set($user . '_' . $param => $value);
Akron80027d12016-01-20 17:36:36 +0100175 };
176
177 # Return value
178 return $value;
179 }
180 );
181
Akrone8235be2016-06-27 11:02:18 +0200182 $mojo->helper(
183 'user.set' => sub {
184 my $c = shift;
185 my $param = shift;
186
187 # 'info' is useless!
188 return unless $param =~ m/^details|settings$/;
189
190 my $json_obj = shift;
191
192 # The user may be logged in
193 my $auth = ($c->stash('auth') || $c->session('auth')) or return;
194
195 # Get namespaced cache
196 my $chi = $c->chi('user');
197
198 # Get user and check, if the user is real
199 my $user = $chi->get($auth);
200
201 # Build a JSON transaction object
202 my $tx = $plugin->build_authorized_tx(
Akronc86bbc42017-03-29 13:19:06 +0200203 $auth, 'POST', 'user/' . $param, json => $json_obj
Akrone8235be2016-06-27 11:02:18 +0200204 );
205
206 # Start
207 $tx = $plugin->ua->start($tx);
208
209 my $res = $tx->success or return;
210
211 # Kill all caches!!
212 $chi->remove($user . '_' . $param);
213
214 # Return value
215 return $res->json;
216 }
217 );
Akron80027d12016-01-20 17:36:36 +0100218
219 # Logout
220 $mojo->helper(
221 'user.logout' => sub {
222 my $c = shift;
223
224 # TODO: csrf-protection!
225 # TODO: REVOKE ON THE SERVER ONCE SUPPORTED!
226
227 # Clear cache
228 $c->chi('user')->remove($c->user_auth);
229
230 # Expire session
231 $c->session(expires => 1);
232 return $c->redirect_to('index');
233 }
234 );
235};
236
Akronc86bbc42017-03-29 13:19:06 +0200237
Akrondadacf12016-01-22 19:24:59 +0100238sub build_authorized_tx {
Akron80027d12016-01-20 17:36:36 +0100239 my $plugin = shift;
240
Akrondadacf12016-01-22 19:24:59 +0100241 my $ua = $plugin->ua;
242 my ($auth, $method, $path, @values) = @_;
243
244 my $header;
245 if (@values && ref $values[0] eq 'HASH') {
246 $header = shift @values;
247 }
248 else {
249 $header = {};
250 };
251
Akron80027d12016-01-20 17:36:36 +0100252 my $url = Mojo::URL->new($plugin->api)->path($path);
253
Akrondadacf12016-01-22 19:24:59 +0100254 $header->{Authorization} = $auth;
Akron80027d12016-01-20 17:36:36 +0100255
Akrondadacf12016-01-22 19:24:59 +0100256 return $ua->build_tx($method, $url => $header => @values);
Akron80027d12016-01-20 17:36:36 +0100257};
258
259
2601;
261
262
263__END__
Akrondadacf12016-01-22 19:24:59 +0100264
265# Failure
266entity {
267 "errors":[
268 [204,"authentication token is expired","eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0MSIsImlzcyI6Imh0dHA6IiwiZXhwIjoxNDUyOTY2NzAxOTYxfQ.W_rJjJ8i82Srw7MiSPRGeIBLE-rMPmSPK9BA7Dt_7Yc"]
269 ]
270}
271