Comparison

mod_invites_api/mod_invites_api.lua @ 5595:f7410850941f

mod_invites_api: Change and add new commands for `module.command` to improve UX.
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Wed, 26 Jul 2023 18:43:45 +0700
parent 5143:1cae382e88a1
child 5628:a74b07764d3f
comparison
equal deleted inserted replaced
5594:14480ca9576e 5595:f7410850941f
1 local http_formdecode = require "net.http".formdecode; 1 local http_formdecode = require "net.http".formdecode;
2 2 local jid = require "util.jid";
3 local api_key_store; 3 local usermanager = require "core.usermanager";
4 local invites; 4 local datetime = require "util.datetime";
5
6 -- Whether local users can invite other users to create an account on this server
7 local allow_user_invites = module:get_option_boolean("allow_user_invites", false);
8 -- Who can see and use the contact invite command. It is strongly recommened to
9 -- keep this available to all local users. To allow/disallow invite-registration
10 -- on the server, use the option above instead.
11 local allow_contact_invites = module:get_option_boolean("allow_contact_invites", true);
12
13 local api_key_store, api_key_store_kv, invites, date, config, path;
5 -- COMPAT: workaround to avoid executing inside prosodyctl 14 -- COMPAT: workaround to avoid executing inside prosodyctl
6 if prosody.shutdown then 15 if prosody.shutdown then
7 module:depends("http"); 16 module:depends("http");
8 api_key_store = module:open_store("invite_api_keys", "map"); 17 api_key_store = module:open_store("invite_api_keys", "map");
9 invites = module:depends("invites"); 18 invites = module:depends("invites");
19 api_key_store_kv = module:open_store("invite_api_keys");
10 end 20 end
11 21
12 local function get_api_user(request, params) 22 local function get_api_user(request, params)
13 local combined_key; 23 local combined_key;
14 24
56 66
57 if api_user.allowed_methods and not api_user.allowed_methods[event.request.method] then 67 if api_user.allowed_methods and not api_user.allowed_methods[event.request.method] then
58 return 405; 68 return 405;
59 end 69 end
60 70
61 local invite = invites.create_account(nil, { source = "api/token/"..api_user.id }); 71 local invite;
72 local username, domain = jid.prepped_split(api_user.jid);
73 if username and usermanager.user_exists(username, domain) then
74 local ttl = module:get_option_number("invite_expiry", 86400 * 7);
75 invite = invites.create_contact(username,
76 allow_user_invites, -- allow_registration
77 { source = "api/token/"..api_user.id }, -- additional_data
78 ttl
79 );
80 else
81 invite = invites.create_account(nil, { source = "api/token/"..api_user.id });
82 end
62 if not invite then 83 if not invite then
63 return 500; 84 return 500;
64 end 85 end
65 86
66 event.response.headers.Location = invite.landing_page or invite.uri; 87 event.response.headers.Location = invite.landing_page or invite.uri;
77 ["GET"] = handle_request; 98 ["GET"] = handle_request;
78 }; 99 };
79 }); 100 });
80 end 101 end
81 102
103 function get_value(callback)
104 for key_id, key_info in pairs(api_key_store_kv:get(nil) or {}) do
105 date = datetime.datetime(key_info.created_at);
106 callback(key_id, key_info);
107 end
108 end
109
110 local function get_url(id, token)
111 local config, path = module:get_option("http_paths");
112 if config then
113 for k, v in pairs(config) do
114 if k == module.name then
115 path = v;
116 break;
117 else
118 path = "/"..module.name;
119 end
120 end
121 else path = "/"..module.name ; end
122
123 local url_base = module:get_option_string("http_external_link", module.host);
124 return url_base..path.."?key="..id.."/"..token.."&redirect=true";
125 end
126
82 function module.command(arg) 127 function module.command(arg)
83 if #arg < 2 then 128 if #arg < 2 then
84 print("Usage:"); 129 print("========================================================================");
85 print(""); 130 print("");
86 print(" prosodyctl mod_"..module.name.." create NAME"); 131 print("Create:");
87 print(" prosodyctl mod_"..module.name.." delete KEY_ID"); 132 print("");
88 print(" prosodyctl mod_"..module.name.." list"); 133 print("> prosodyctl mod_"..module.name.." create JID NAME");
89 print(""); 134 print("");
135 print("Query:");
136 print("");
137 print("> prosodyctl mod_"..module.name.." get JID NAME");
138 print("> prosodyctl mod_"..module.name.." date JID NAME");
139 print("> prosodyctl mod_"..module.name.." id JID NAME");
140 print("> prosodyctl mod_"..module.name.." key JID NAME");
141 print("> prosodyctl mod_"..module.name.." url JID NAME");
142 print("");
143 print("Revoke:");
144 print("");
145 print("> prosodyctl mod_"..module.name.." delete JID NAME");
146 print("> prosodyctl mod_"..module.name.." delete-id HOST ID");
147 print("");
148 print("Renew:");
149 print("");
150 print("> prosodyctl mod_"..module.name.." renew JID NAME");
151 print("");
152 print("Help:");
153 print("");
154 print("> prosodyctl mod_"..module.name.." help COMMAND");
155 print("");
156 print("========================================================================");
157 return;
90 end 158 end
91 159
92 local command = table.remove(arg, 1); 160 local command = table.remove(arg, 1);
93 161 if command == "help" then
94 local host = table.remove(arg, 1); 162 help_command = table.remove(arg, 1);
95 if not prosody.hosts[host] then 163 if help_command == "create" then
96 print("Error: please supply a valid host"); 164 print("========================================================================");
165 print("");
166 print("Create a key:");
167 print("");
168 print("> prosodyctl mod_"..module.name.." create JID NAME");
169 print("");
170 print("------------------------------------------------------------------------");
171 print("");
172 print("Usage:");
173 print("");
174 print(" `JID` can either be a user's account or a host.");
175 print(" When `JID` is a host, you need to supply a `NAME`.");
176 print("");
177 print(" Each user's account can only have 1 API key but hosts are unlimited.");
178 print("");
179 print("========================================================================");
180 elseif help_command == "renew" then
181 print("========================================================================");
182 print("");
183 print("Re-new a key:");
184 print("");
185 print("> prosodyctl mod_"..module.name.." create JID NAME");
186 print("");
187 print("------------------------------------------------------------------------");
188 print("");
189 print("Usage:");
190 print("");
191 print(" `JID` can either be a user's account or a host.");
192 print(" When `JID` is a host, you need to supply a `NAME`.");
193 print("");
194 print(" The old `ID` will be kept and a new token will be generated for the API");
195 print(" key you specified.");
196 print("");
197 print("========================================================================");
198 elseif help_command == "get" then
199 print("========================================================================");
200 print("");
201 print("Get info of a key:");
202 print("");
203 print("> prosodyctl mod_"..module.name.." get JID NAME");
204 print("");
205 print("------------------------------------------------------------------------");
206 print("");
207 print("Usage:");
208 print("");
209 print(" When `JID` is a domain, it will list all the keys under that host.");
210 print(" When `JID` is a user's account, it fetches the key for that user.");
211 print(" If you supply both a host and a `NAME`, it fetches the key with `NAME`");
212 print(" under that host.")
213 print("");
214 print(" Output for a host is: DATE, ID, JID, NAME.");
215 print("");
216 print(" Output for a user's account is: DATE, ID, URL.");
217 print("");
218 print(" Output for a host with a valid `NAME` is: DATE, ID, URL.");
219 print("");
220 print("========================================================================");
221 elseif help_command == "date" then
222 print("========================================================================");
223 print("");
224 print("Get the time stamp of a key:");
225 print("");
226 print("> prosodyctl mod_"..module.name.." date JID NAME");
227 print("");
228 print("------------------------------------------------------------------------");
229 print("");
230 print("Usage:");
231 print("");
232 print(" Same as the `get` command but print only the birthday of the key.");
233 print("");
234 print("========================================================================");
235 elseif help_command == "id" then
236 print("========================================================================");
237 print("");
238 print("Print the ID of a key:");
239 print("");
240 print("> prosodyctl mod_"..module.name.." id JID NAME");
241 print("");
242 print("------------------------------------------------------------------------");
243 print("");
244 print("Usage:");
245 print("");
246 print(" Same as the `get` command but print only the ID of the key.");
247 print("");
248 print("========================================================================");
249 elseif help_command == "key" then
250 print("========================================================================");
251 print("");
252 print("Print the API key:");
253 print("");
254 print("> prosodyctl mod_"..module.name.." key JID NAME");
255 print("");
256 print("------------------------------------------------------------------------");
257 print("");
258 print("Usage:");
259 print("");
260 print(" Same as the `get` command but print only the key.");
261 print("");
262 print(" The key has the format: ID/TOKEN");
263 print("");
264 print(" The `renew` command will generate a new token and revoke the old one.");
265 print("");
266 print("========================================================================");
267 elseif help_command == "url" then
268 print("========================================================================");
269 print("");
270 print("Print the URL of a key:");
271 print("");
272 print("> prosodyctl mod_"..module.name.." url JID NAME");
273 print("");
274 print("------------------------------------------------------------------------");
275 print("");
276 print("Usage:");
277 print("");
278 print(" Same as the `get` command but print only the URL of the key.");
279 print("");
280 print("========================================================================");
281 elseif help_command == "delete" then
282 print("========================================================================");
283 print("");
284 print("Delete a key by JID and NAME:");
285 print("");
286 print("> prosodyctl mod_"..module.name.." delete JID NAME");
287 print("");
288 print("------------------------------------------------------------------------");
289 print("");
290 print("Usage:");
291 print("");
292 print(" Same as `create` command but delete the key specified.");
293 print("");
294 print("========================================================================");
295 elseif help_command == "delete-id" then
296 print("========================================================================");
297 print("");
298 print("Delete API key by ID:");
299 print("");
300 print("> prosodyctl mod_"..module.name.." delete HOST ID");
301 print("");
302 print("------------------------------------------------------------------------");
303 print("");
304 print("Usage:");
305 print("");
306 print(" Input must be a valid host - cannot be a user's account.");
307 print("");
308 print(" To get the ID of a key, use the `id` command.");
309 print("");
310 print("========================================================================");
311 end
312 return;
313 end
314
315 local username, domain = jid.prepped_split(table.remove(arg, 1));
316 if not prosody.hosts[domain] then
317 print("Error: please supply a valid host.");
97 return 1; 318 return 1;
98 end 319 end
99 require "core.storagemanager".initialize_host(host); 320 require "core.storagemanager".initialize_host(domain);
100 module.host = host; --luacheck: ignore 122/module 321 module.host = domain; --luacheck: ignore 122/module
322 usermanager.initialize_host(module.host);
101 api_key_store = module:open_store("invite_api_keys", "map"); 323 api_key_store = module:open_store("invite_api_keys", "map");
324 api_key_store_kv = module:open_store("invite_api_keys");
102 325
103 if command == "create" then 326 if command == "create" then
104 local id = require "util.id".short(); 327 local util_id = require "util.id".short();
105 local token = require "util.id".long(); 328 local util_token = require "util.id".long();
106 api_key_store:set(nil, id, { 329 local found = false;
107 id = id; 330 local os_time = os.time();
108 token = token; 331 if username then
109 name = arg[1]; 332 if usermanager.user_exists(username, module.host) then
110 created_at = os.time(); 333 get_value(function (id, info)
111 allowed_methods = { GET = true, POST = true }; 334 if username.."@"..module.host == info.jid then
112 }); 335 date = datetime.datetime(info.created_at);
113 print(id.."/"..token); 336 print("Found:");
337 print(date, id, info.jid);
338 util_id = id;
339 util_token = info.token;
340 found = true;
341 end
342 end);
343 if found == false then
344 api_key_store:set(nil, util_id, {
345 id = util_id;
346 token = util_token;
347 name = nil;
348 jid = username.."@"..domain;
349 created_at = os_time;
350 allowed_methods = { GET = true, POST = true };
351 });
352 date = datetime.datetime(os_time);
353 print("Created:");
354 print(date, util_id.."/"..util_token, username.."@"..domain);
355 end
356 return;
357 else
358 print("Error: "..username.."@"..domain.." does not exists.");
359 return 1;
360 end
361 elseif domain then
362 local arg_name = table.remove(arg, 1);
363 if not arg_name or arg_name == "" then
364 print("Error: key for host needs a `NAME`.");
365 return;
366 end
367 found = false;
368 get_value(function (id, info)
369 if domain == info.jid and arg_name == info.name then
370 date = datetime.datetime(info.created_at);
371 print("Found:");
372 print(date, id, info.jid, info.name);
373 util_id = id;
374 util_token = info.token;
375 found = true;
376 end
377 end);
378 if found == false then
379 api_key_store:set(nil, util_id, {
380 id = util_id;
381 token = util_token;
382 name = arg_name;
383 jid = domain;
384 created_at = os_time;
385 allowed_methods = { GET = true, POST = true };
386 });
387 date = datetime.datetime(os_time);
388 print("Created:");
389 print(date, util_id.."/"..util_token, domain, arg_name);
390 end
391 return;
392 end
393 elseif command == "renew" then
394 local util_token = require "util.id".long();
395 local os_time = os.time();
396 if username then
397 local found;
398 if usermanager.user_exists(username, module.host) then
399 get_value(function (id, info)
400 if username.."@"..module.host == info.jid then
401 api_key_store:set(nil, id, {
402 id = id;
403 token = util_token;
404 name = arg[1];
405 jid = username.."@"..domain;
406 created_at = os_time;
407 allowed_methods = { GET = true, POST = true };
408 });
409 found = true;
410 date = datetime.datetime(os_time);
411 print("Re-newed:");
412 print(date, id.."/"..util_token, info.jid);
413 end
414 end);
415 if not found then
416 print("Error: Could not find the key for "..username.."@"..domain);
417 print("");
418 print("To make this API key, run:");
419 print("prosodyctl "..module.name.." create "..username.."@"..domain);
420 return;
421 end
422 return;
423 else
424 print("Error: "..username.."@"..domain.." does not exists.");
425 return 1;
426 end
427 elseif domain then
428 local arg_name = table.remove(arg, 1);
429 if not arg_name or arg_name == "" then
430 print("Error: key for host needs a `NAME`.");
431 return 1;
432 end
433 found = false;
434 get_value(function (id, info)
435 if domain == info.jid and arg_name == info.name then
436 api_key_store:set(nil, id, {
437 id = id;
438 token = util_token;
439 name = arg_name;
440 jid = domain;
441 created_at = os_time;
442 allowed_methods = { GET = true, POST = true };
443 });
444 date = datetime.datetime(os_time);
445 print("Re-newed:");
446 print(date, id.."/"..util_token, info.jid, info.name);
447 found = true;
448 end
449 end);
450 if not found then
451 date = datetime.datetime(os_time);
452 print("Error: Could not find "..arg_name.." in "..domain);
453 print("");
454 print("To make this API key, run:");
455 print("prosodyctl "..module.name.." create "..domain.." "..arg_name);
456 return;
457 end
458 return;
459 end
460 elseif command == "get" then
461 local name = table.remove(arg, 1);
462 local found = false;
463 if name and not username then
464 get_value(function (id, info)
465 if info.name == name then
466 print(date, id, get_url(id, info.token));
467 found = true;
468 end
469 end);
470 if found == false then
471 print("Error: could not find "..name.." in "..domain);
472 print("");
473 print("You can create it with:");
474 print("");
475 print("> prosodyctl "..module.name.." create "..domain.." "..name);
476 end
477 return;
478 elseif username then
479 get_value(function (id, info)
480 local j = jid.prepped_split(info.jid);
481 if j == username then
482 print(date, id, get_url(id, info.token));
483 found = true;
484 end
485 end);
486 if found == false then
487 print("Error: could not find the key for "..username.."@"..domain);
488 print("");
489 print("You can create it with:");
490 print("");
491 print("> prosodyctl "..module.name.." create "..username.."@"..domain);
492 end
493 return;
494 else
495 get_value(function (id, info)
496 if info.jid == module.host then
497 print(date, id, info.jid, info.name or "<unknown>");
498 else
499 print(date, id, info.jid, info.name or "");
500 end
501 end);
502 return;
503 end
504 elseif command == "date" then
505 local name = table.remove(arg, 1);
506 if name and not username then
507 get_value(function (id, info)
508 if info.name == name then
509 print(date);
510 end
511 end);
512 return;
513 elseif username then
514 get_value(function (id, info)
515 local j = jid.prepped_split(info.jid);
516 if j == username then
517 print(date);
518 end
519 end);
520 return;
521 else
522 get_value(function (id, info)
523 if info.jid == module.host then
524 print(date, info.jid, info.name or "<unknown>");
525 else
526 print(date, info.jid);
527 end
528 end);
529 return;
530 end
531 elseif command == "id" then
532 local name = table.remove(arg, 1);
533 if name and not username then
534 get_value(function (id, info)
535 if info.name == name then
536 print(id);
537 end
538 end);
539 return;
540 elseif username then
541 get_value(function (id, info)
542 local j = jid.prepped_split(info.jid);
543 if j == username then
544 print(id);
545 end
546 end);
547 return;
548 else
549 get_value(function (id, info)
550 if info.jid == module.host then
551 print(id, info.jid, info.name or "<unknown>");
552 else
553 print(id, info.jid);
554 end
555 end);
556 return;
557 end
558 elseif command == "key" then
559 local name = table.remove(arg, 1);
560 if name and not username then
561 get_value(function (id, info)
562 if info.name == name then
563 print(id.."/"..info.token);
564 end
565 end);
566 return;
567 elseif username then
568 get_value(function (id, info)
569 local j = jid.prepped_split(info.jid);
570 if j == username then
571 print(id.."/"..info.token);
572 end
573 end);
574 return;
575 else
576 get_value(function (id, info)
577 if info.jid == module.host then
578 print(id.."/"..info.token, info.jid, info.name or "<unknown>");
579 else
580 print(id.."/"..info.token, info.jid);
581 end
582 end);
583 return;
584 end
585 elseif command == "url" then
586 local name = table.remove(arg, 1);
587 if name and not username then
588 get_value(function (id, info)
589 if info.name == name then
590 print(get_url(id, info.token));
591 end
592 end);
593 return;
594 elseif username then
595 get_value(function (id, info)
596 local j = jid.prepped_split(info.jid);
597 if j == username then
598 print(get_url(id, info.token));
599 end
600 end);
601 return;
602 else
603 get_value(function (id, info)
604 if info.jid == module.host then
605 print(get_url(id, info.token), info.jid, info.name or "<unknown>");
606 else
607 print(get_url(id, info.token), info.jid);
608 end
609 end);
610 return;
611 end
114 elseif command == "delete" then 612 elseif command == "delete" then
115 local id = table.remove(arg, 1); 613 local name = table.remove(arg, 1);
116 if not api_key_store:get(nil, id) then 614 if name and not username then
615 get_value(function (id, info)
616 if info.name == name then
617 api_key_store:set(nil, id, nil);
618 print("Deleted:");
619 print(date, id.."/"..info.token, info.jid, info.name or "");
620 end
621 end);
622 return;
623 elseif username then
624 get_value(function (id, info)
625 local j = jid.prepped_split(info.jid);
626 if j == username then
627 api_key_store:set(nil, id, nil);
628 print("Deleted:");
629 print(date, id.."/"..info.token, info.jid, info.name or "");
630 end
631 end);
632 return;
633 else
634 print("Error: Needs a valid `JID`. Or a host and a `NAME`.");
635 end
636 elseif command == "delete-id" then
637 if username then
638 print("Error: Input must be a valid host - cannot be a user's account.");
639 return 1;
640 end
641 local arg_id = table.remove(arg, 1);
642 if not api_key_store:get(nil, arg_id) then
117 print("Error: key not found"); 643 print("Error: key not found");
118 return 1; 644 return 1;
119 end 645 else
120 api_key_store:set(nil, id, nil); 646 get_value(function (id, info)
121 elseif command == "list" then 647 if arg_id == id then
122 local api_key_store_kv = module:open_store("invite_api_keys"); 648 api_key_store:set(nil, id, nil);
123 for key_id, key_info in pairs(api_key_store_kv:get(nil) or {}) do 649 print("Deleted:");
124 print(key_id, key_info.name or "<unknown>"); 650 print(date, id, info.jid, info.name or "");
651 end
652 end);
653 return;
125 end 654 end
126 else 655 else
127 print("Unknown command - "..command); 656 print("Unknown command - "..command);
128 end 657 end
129 end 658 end