From 6c23a71d44c1dea0f0039890643edd0464c688af Mon Sep 17 00:00:00 2001 From: Nick Muerdter <12112+GUI@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:21:18 -0700 Subject: [PATCH] Add fields to capture registration options for users. --- db/schema.sql | 5 +- src/api-umbrella/web-app/actions/v1/users.lua | 6 + src/api-umbrella/web-app/models/api_user.lua | 25 ++++ src/migrations.lua | 9 ++ test/apis/v1/users/test_create.rb | 123 ++++++++++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) diff --git a/db/schema.sql b/db/schema.sql index 83c3ee914..b9b9125e6 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -1103,7 +1103,9 @@ CREATE TABLE api_umbrella.api_users ( registration_recaptcha_v3_action character varying(255), registration_recaptcha_v3_error_codes character varying(50)[], registration_recaptcha_v2_hostname character varying(255), - registration_recaptcha_v3_hostname character varying(255) + registration_recaptcha_v3_hostname character varying(255), + registration_options jsonb, + registration_input_options jsonb ); @@ -2821,3 +2823,4 @@ INSERT INTO api_umbrella.lapis_migrations (name) VALUES ('1699559696'); INSERT INTO api_umbrella.lapis_migrations (name) VALUES ('1699650325'); INSERT INTO api_umbrella.lapis_migrations (name) VALUES ('1700281762'); INSERT INTO api_umbrella.lapis_migrations (name) VALUES ('1700346585'); +INSERT INTO api_umbrella.lapis_migrations (name) VALUES ('1701483732'); diff --git a/src/api-umbrella/web-app/actions/v1/users.lua b/src/api-umbrella/web-app/actions/v1/users.lua index fa8bab6f3..015ce4c86 100644 --- a/src/api-umbrella/web-app/actions/v1/users.lua +++ b/src/api-umbrella/web-app/actions/v1/users.lua @@ -299,6 +299,12 @@ function _M.create(self) user_params["registration_source"] = "api" end user_params["registration_key_creator_api_user_id"] = request_headers["x-api-user-id"] + if not is_empty(options) then + user_params["registration_options"] = options + end + if not is_empty(self.params["options"]) then + user_params["registration_input_options"] = self.params["options"] + end -- If email verification is enabled, then create the record and mark its -- email_verified field as true. Since the API key won't be part of the API diff --git a/src/api-umbrella/web-app/models/api_user.lua b/src/api-umbrella/web-app/models/api_user.lua index 27fd3d580..c48ba050f 100644 --- a/src/api-umbrella/web-app/models/api_user.lua +++ b/src/api-umbrella/web-app/models/api_user.lua @@ -11,6 +11,7 @@ local hmac = require "api-umbrella.utils.hmac" local is_array = require "api-umbrella.utils.is_array" local is_hash = require "api-umbrella.utils.is_hash" local json_array_fields = require "api-umbrella.web-app.utils.json_array_fields" +local json_encode = require "api-umbrella.utils.json_encode" local json_null_default = require "api-umbrella.web-app.utils.json_null_default" local lyaml = require "lyaml" local model_ext = require "api-umbrella.web-app.utils.model_ext" @@ -381,6 +382,22 @@ ApiUser = model_ext.new_class("api_users", { model_ext.add_error(errors, "metadata_yaml_string", t("Metadata"), data["_metadata_yaml_string_parse_error"]) end + if data["registration_options"] and not is_hash(data["registration_options"]) and data["registration_options"] ~= db_null then + model_ext.add_error(errors, "registration_options", t("Registration options"), t("unexpected type (must be a hash)")) + end + + if data["registration_options"] and is_hash(data["registration_options"]) and data["registration_options"] ~= db_null and string.len(json_encode(data["registration_options"])) > 4000 then + model_ext.add_error(errors, "registration_options", t("Registration options"), t("is too long")) + end + + if data["registration_input_options"] and not is_hash(data["registration_input_options"]) and data["registration_input_options"] ~= db_null then + model_ext.add_error(errors, "registration_input_options", t("Registration input options"), t("unexpected type (must be a hash)")) + end + + if data["registration_input_options"] and is_hash(data["registration_input_options"]) and data["registration_input_options"] ~= db_null and string.len(json_encode(data["registration_input_options"])) > 4000 then + model_ext.add_error(errors, "registration_input_options", t("Registration input options"), t("is too long")) + end + return errors end, @@ -396,6 +413,14 @@ ApiUser = model_ext.new_class("api_users", { if is_array(values["registration_recaptcha_v3_error_codes"]) and values["registration_recaptcha_v3_error_codes"] ~= db_null then values["registration_recaptcha_v3_error_codes"] = db_raw(pg_encode_array(values["registration_recaptcha_v3_error_codes"])) end + + if is_hash(values["registration_options"]) and values["registration_options"] ~= db_null then + values["registration_options"] = db_raw(pg_encode_json(values["registration_options"])) + end + + if is_hash(values["registration_input_options"]) and values["registration_input_options"] ~= db_null then + values["registration_input_options"] = db_raw(pg_encode_json(values["registration_input_options"])) + end end, after_save = function(self, values) diff --git a/src/migrations.lua b/src/migrations.lua index 440a79ada..81e4b390f 100644 --- a/src/migrations.lua +++ b/src/migrations.lua @@ -1495,4 +1495,13 @@ return { db.query("COMMIT") end, + [1701483732] = function() + db.query("BEGIN") + + db.query("ALTER TABLE api_users ADD COLUMN registration_options jsonb") + db.query("ALTER TABLE api_users ADD COLUMN registration_input_options jsonb") + + db.query(grants_sql) + db.query("COMMIT") + end, } diff --git a/test/apis/v1/users/test_create.rb b/test/apis/v1/users/test_create.rb index 9169f2181..295a59b39 100644 --- a/test/apis/v1/users/test_create.rb +++ b/test/apis/v1/users/test_create.rb @@ -807,6 +807,129 @@ def test_accepts_empty_origin_for_admins assert_response_code(201, response) end + def test_registration_options_not_set + response = Typhoeus.post("https://127.0.0.1:9081/api-umbrella/v1/users.json", http_options.deep_merge(non_admin_key_creator_api_key).deep_merge({ + :headers => { "Content-Type" => "application/json" }, + :body => MultiJson.dump({ + :user => FactoryBot.attributes_for(:api_user), + }), + })) + assert_response_code(201, response) + + data = MultiJson.load(response.body) + user = ApiUser.find(data["user"]["id"]) + assert_equal({ + "contact_url" => "https://localhost/contact/", + "site_name" => "API Umbrella", + }, user.registration_options) + assert_nil(user.registration_input_options) + end + + def test_registration_options_empty + response = Typhoeus.post("https://127.0.0.1:9081/api-umbrella/v1/users.json", http_options.deep_merge(non_admin_key_creator_api_key).deep_merge({ + :headers => { "Content-Type" => "application/json" }, + :body => MultiJson.dump({ + :user => FactoryBot.attributes_for(:api_user), + :options => {}, + }), + })) + assert_response_code(201, response) + + data = MultiJson.load(response.body) + user = ApiUser.find(data["user"]["id"]) + assert_equal({ + "contact_url" => "https://localhost/contact/", + "site_name" => "API Umbrella", + }, user.registration_options) + assert_nil(user.registration_input_options) + end + + def test_registration_options_set + response = Typhoeus.post("https://127.0.0.1:9081/api-umbrella/v1/users.json", http_options.deep_merge(non_admin_key_creator_api_key).deep_merge({ + :headers => { "Content-Type" => "application/json" }, + :body => MultiJson.dump({ + :user => FactoryBot.attributes_for(:api_user), + :options => { + :foo => "bar", + :send_welcome_email => true, + :contact_url => "example@#{unique_test_hostname}", + :email_from_address => "example@127.0.0.1", + }, + }), + })) + assert_response_code(201, response) + + data = MultiJson.load(response.body) + user = ApiUser.find(data["user"]["id"]) + assert_equal({ + "foo" => "bar", + "send_welcome_email" => true, + "contact_url" => "https://localhost/contact/", + "email_from_address" => "example@127.0.0.1", + "site_name" => "API Umbrella", + }, user.registration_options) + assert_equal({ + "foo" => "bar", + "send_welcome_email" => true, + "contact_url" => "example@#{unique_test_hostname}", + "email_from_address" => "example@127.0.0.1", + }, user.registration_input_options) + end + + def test_registration_options_below_length_limit + response = Typhoeus.post("https://127.0.0.1:9081/api-umbrella/v1/users.json", http_options.deep_merge(non_admin_key_creator_api_key).deep_merge({ + :headers => { "Content-Type" => "application/json" }, + :body => MultiJson.dump({ + :user => FactoryBot.attributes_for(:api_user), + :options => { + :foo => "a" * 3900, + }, + }), + })) + assert_response_code(201, response) + + data = MultiJson.load(response.body) + user = ApiUser.find(data["user"]["id"]) + assert_equal({ + "foo" => "a" * 3900, + "contact_url" => "https://localhost/contact/", + "site_name" => "API Umbrella", + }, user.registration_options) + assert_equal({ + "foo" => "a" * 3900, + }, user.registration_input_options) + end + + def test_registration_options_too_long + response = Typhoeus.post("https://127.0.0.1:9081/api-umbrella/v1/users.json", http_options.deep_merge(non_admin_key_creator_api_key).deep_merge({ + :headers => { "Content-Type" => "application/json" }, + :body => MultiJson.dump({ + :user => FactoryBot.attributes_for(:api_user), + :options => { + :foo => "a" * 4000, + }, + }), + })) + assert_response_code(422, response) + data = MultiJson.load(response.body) + assert_equal({ + "errors" => [ + { + "code" => "INVALID_INPUT", + "field" => "registration_options", + "message" => "is too long", + "full_message" => "Registration options: is too long", + }, + { + "code" => "INVALID_INPUT", + "field" => "registration_input_options", + "message" => "is too long", + "full_message" => "Registration input options: is too long", + }, + ], + }, data) + end + private def non_admin_key_creator_api_key