Add Gleam implementation of new B2C auth

This commit is contained in:
Mikko Ahlroth 2023-01-10 22:18:23 +02:00
parent 02e0cf0b9b
commit a625631c17
20 changed files with 716 additions and 28 deletions

3
.gitignore vendored
View file

@ -1,6 +1,9 @@
# The directory Mix will write compiled artifacts to.
/_build/
# Gleam build dir
/build/
# If you run "mix test --cover", coverage assets end up here.
/cover/

View file

@ -1,2 +1,3 @@
erlang 25.0.4
elixir 1.13.4-otp-25
gleam 0.25.3

10
gleam.toml Normal file
View file

@ -0,0 +1,10 @@
name = "geo_therminator"
version = "0.3.0"
target = "erlang"
[dependencies]
gleam_stdlib = "~> 0.25"
gleam_http = "~> 3.1"
gleam_hackney = "~> 0.2.1"
gleam_erlang = "~> 0.17.1"
gleam_json = "~> 0.5.0"

26
manifest.toml Normal file
View file

@ -0,0 +1,26 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "certifi", version = "2.9.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "266DA46BDB06D6C6D35FDE799BCB28D36D985D424AD7C08B5BB48F5B5CDD4641" },
{ name = "gleam_erlang", version = "0.17.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "BAAA84F5BCC4477E809BA3E03BB3009A3894A6544C1511626C44408E39DB2AE6" },
{ name = "gleam_hackney", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "hackney", "gleam_http"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "CCACA00027C827436D8EB945651392B6E5798CFC9E69907A28BE61832B0C02A4" },
{ name = "gleam_http", version = "3.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "B66B7A1539CCB577119E4DC80DD3484C1A652CB032967954498EEDBAE3355763" },
{ name = "gleam_json", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "E42443C98AA66E30143C24818F2CEA801491C10CE6B1A5EDDF3FC4ABDC7601CB" },
{ name = "gleam_stdlib", version = "0.25.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "AD0F89928E0B919C8F8EDF640484633B28DBF88630A9E6AE504617A3E3E5B9A2" },
{ name = "hackney", version = "1.18.1", build_tools = ["rebar3"], requirements = ["certifi", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat", "metrics", "idna"], otp_app = "hackney", source = "hex", outer_checksum = "A4ECDAFF44297E9B5894AE499E9A070EA1888C84AFDD1FD9B7B2BC384950128E" },
{ name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" },
{ name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" },
{ name = "mimerl", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323" },
{ name = "parse_trans", version = "3.3.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B" },
{ name = "ssl_verify_fun", version = "1.1.6", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680" },
{ name = "thoas", version = "0.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "442296847ACA11DB8D25180693D7CA3073D6D7179F66952F07B16415306513B6" },
{ name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" },
]
[requirements]
gleam_erlang = "~> 0.17.1"
gleam_hackney = "~> 0.2.1"
gleam_http = "~> 3.1"
gleam_json = "~> 0.5.0"
gleam_stdlib = "~> 0.25"

23
mix.exs
View file

@ -1,13 +1,20 @@
defmodule GeoTherminator.MixProject do
use Mix.Project
@app :geo_therminator
def project do
[
app: :geo_therminator,
version: "0.2.0",
app: @app,
version: "0.3.0",
elixir: "~> 1.13",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:gettext] ++ Mix.compilers(),
erlc_paths: [
"build/dev/erlang/#{@app}/_gleam_artefacts"
],
erlc_include_path: "build/dev/erlang/#{@app}/include",
archives: [mix_gleam: "~> 0.6.1"],
compilers: [:gleam, :gettext] ++ Mix.compilers(),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
@ -54,7 +61,12 @@ defmodule GeoTherminator.MixProject do
{:dotenv_parser, "~> 1.2"},
{:finch, "~> 0.9.0"},
{:desktop, "~> 1.4"},
{:cubdb, "~> 2.0"}
{:cubdb, "~> 2.0"},
{:gleam_stdlib, "~> 0.25"},
{:gleam_http, "~> 3.1"},
{:gleam_hackney, "~> 0.2.1"},
{:gleam_erlang, "~> 0.17.1"},
{:gleam_json, "~> 0.5.0"}
]
if Mix.target() in [:android, :ios] do
@ -73,7 +85,8 @@ defmodule GeoTherminator.MixProject do
defp aliases do
[
setup: ["deps.get"],
"assets.deploy": ["esbuild default --minify", "phx.digest"]
"assets.deploy": ["esbuild default --minify", "phx.digest"],
"deps.get": ["deps.get", "gleam.deps.get"]
]
end
end

View file

@ -1,42 +1,56 @@
%{
"castore": {:hex, :castore, "0.1.13", "ccf3ab251ffaebc4319f41d788ce59a6ab3f42b6c27e598ad838ffecee0b04f9", [:mix], [], "hexpm", "a14a7eecfec7e20385493dbb92b0d12c5d77ecfd6307de10102d58c94e8c49c0"},
"castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"cubdb": {:hex, :cubdb, "2.0.1", "24cab8fb4128df704c52ed641f5ed70af352f7a3a80cebbb44c3bbadc3fd5f45", [:mix], [], "hexpm", "57cf25aebfc34f4580d9075da06882b4fe3e0739f5353d4dcc213e9cc1b10cdf"},
"cubdb": {:hex, :cubdb, "2.0.2", "d4253885084dae37a8ff73887d232864eb38ecac962aa08543e686b0183a1d62", [:mix], [], "hexpm", "c99cc8f9e6c4deb98d16cca5ded1928edd22e48b4736b76e8a1a85367d7fe921"},
"dbus": {:hex, :dbus, "0.8.0", "7c800681f35d909c199265e55a8ee4aea9ebe4acccce77a0740f89f29cc57648", [:make], [], "hexpm", "a9784f2d9717ffa1f74169144a226c39633ac0d9c7fe8cb3594aeb89c827cca5"},
"debouncer": {:hex, :debouncer, "0.1.7", "a7f59fb55cdb54072aff8ece461f4d041d2a709da84e07ed0ab302d348724640", [:mix], [], "hexpm", "b7fd0623df8ab16933bb164d19769884b18c98cab8677cd53eed59587f290603"},
"desktop": {:hex, :desktop, "1.4.1", "69e4741fbf72e2e71c8d6078cd18da0e6fddc6e87f67837ac501a2412a0ddc64", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_sni, "~> 0.2", [hex: :ex_sni, repo: "hexpm", optional: false]}, {:gettext, "> 0.10.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:oncrash, "~> 0.1", [hex: :oncrash, repo: "hexpm", optional: false]}, {:phoenix, "> 1.0.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:plug, "> 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fd3716fd912d2379448d94e445ce9de6e65e89cd2bd9e35f8d76efd497f1ec66"},
"desktop": {:hex, :desktop, "1.4.2", "48cb5f02aa77522bd9996bfe02c4b23f8dc40d30076ada46b660f4a20bd7a3a1", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_sni, "~> 0.2", [hex: :ex_sni, repo: "hexpm", optional: false]}, {:gettext, "> 0.10.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:oncrash, "~> 0.1", [hex: :oncrash, repo: "hexpm", optional: false]}, {:phoenix, "> 1.0.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:plug, "> 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bcaee5daf0c547ed988d26d4bec04388f104ce169a4e255a786cd64598ce3362"},
"dotenv_parser": {:hex, :dotenv_parser, "1.2.0", "f062900aeb57727b619aeb182fa4a8b1cbb7b4260ebec2b70b3d5c064885aff3", [:mix], [], "hexpm", "eddd69e7fde28618adb2e4153fa380db5c56161b32341e7a4e0530d86987c47f"},
"esbuild": {:hex, :esbuild, "0.3.4", "416203c642eb84b207f882cf7953a1fd7bb71e23f5f86554f983bb7bad18b897", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c472e38b37e9547113776b1e4b64b44ec540bcc7056dd252c2c3ffba41aa9793"},
"esbuild": {:hex, :esbuild, "0.6.0", "9ba6ead054abd43cb3d7b14946a0cdd1493698ccd8e054e0e5d6286d7f0f509c", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "30f9a05d4a5bab0d3e37398f312f80864e1ee1a081ca09149d06d474318fd040"},
"ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"},
"ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"},
"expo": {:hex, :expo, "0.1.0", "d4e932bdad052c374118e312e35280f1919ac13881cb3ac07a209a54d0c81dd8", [:mix], [], "hexpm", "c22c536021c56de058aaeedeabb4744eb5d48137bacf8c29f04d25b6c6bbbf45"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.9.0", "8b772324aebafcaba763f1dffaa3e7f52f8c4e52485f50f48bbb2f42219a2e87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a93bfcad9ca50fa3cb2d459f27667d9a87cfbb7fecf9b29b2e78a50bc2ab445d"},
"floki": {:hex, :floki, "0.32.0", "f915dc15258bc997d49be1f5ef7d3992f8834d6f5695270acad17b41f5bcc8e2", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "1c5a91cae1fd8931c26a4826b5e2372c284813904c8bacb468b5de39c7ececbd"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mint": {:hex, :mint, "1.4.0", "cd7d2451b201fc8e4a8fd86257fb3878d9e3752899eb67b0c5b25b180bde1212", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "10a99e144b815cbf8522dccbc8199d15802440fc7a64d67b6853adb6fa170217"},
"nimble_options": {:hex, :nimble_options, "0.3.7", "1e52dd7673d36138b1a5dede183b5d86dff175dc46d104a8e98e396b85b04670", [:mix], [], "hexpm", "2086907e6665c6b6579be54ef5001928df5231f355f71ed258f80a55e9f63633"},
"nimble_pool": {:hex, :nimble_pool, "0.2.4", "1db8e9f8a53d967d595e0b32a17030cdb6c0dc4a451b8ac787bf601d3f7704c3", [:mix], [], "hexpm", "367e8071e137b787764e6a9992ccb57b276dc2282535f767a07d881951ebeac6"},
"finch": {:hex, :finch, "0.9.1", "ab2b0151ba88543e221cb50bf0734860db55e8748816ee16e4997fe205f7b315", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d6b898a59d19f84958eaffec40580f5a9ff88a31e93156707fa8b1d552aa425"},
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
"gettext": {:hex, :gettext, "0.21.0", "15bbceb20b317b706a8041061a08e858b5a189654128618b53746bf36c84352b", [:mix], [{:expo, "~> 0.1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "04a66db4103b6d1d18f92240bb2c73167b517229316b7bef84e4eebbfb2f14f6"},
"gleam_erlang": {:hex, :gleam_erlang, "0.17.1", "40fff501e8ca39fa166f4c12ed13bb57e94fc5bb59a93b4446687d82d4a12ff9", [:gleam], [{:gleam_stdlib, "~> 0.22", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}], "hexpm", "baaa84f5bcc4477e809ba3e03bb3009a3894a6544c1511626c44408e39db2ae6"},
"gleam_hackney": {:hex, :gleam_hackney, "0.2.1", "ca3c5677b85f31885a4366c73a110803515d6d23a2e233e459dc164260315404", [:gleam], [{:gleam_http, "~> 3.0", [hex: :gleam_http, repo: "hexpm", optional: false]}, {:gleam_stdlib, "~> 0.18", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}, {:hackney, "~> 1.18", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "ccaca00027c827436d8eb945651392b6e5798cfc9e69907a28be61832b0c02a4"},
"gleam_http": {:hex, :gleam_http, "3.1.1", "609158240630e21fc70c69b21384e5ebbcd86f71bd378a6f7c2b87f910ab3561", [:gleam], [{:gleam_stdlib, "~> 0.18", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}], "hexpm", "b66b7a1539ccb577119e4dc80dd3484c1a652cb032967954498eedbae3355763"},
"gleam_json": {:hex, :gleam_json, "0.5.0", "aff4507ad7700ad794ada6671c6dfd0174696713659bd8782858135b19f41b58", [:gleam], [{:gleam_stdlib, "~> 0.19", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}, {:thoas, "~> 0.2", [hex: :thoas, repo: "hexpm", optional: false]}], "hexpm", "e42443c98aa66e30143c24818f2cea801491c10ce6b1a5eddf3fc4abdc7601cb"},
"gleam_stdlib": {:hex, :gleam_stdlib, "0.25.0", "656f39258dcc8772719e463bbe7d1d1c7800238a520b41558fad53ea206ee3ab", [:gleam], [], "hexpm", "ad0f89928e0b919c8f8edf640484633b28dbf88630a9e6ae504617a3e3e5b9a2"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"oncrash": {:hex, :oncrash, "0.1.0", "9cf4ae8eba4ea250b579470172c5e9b8c75418b2264de7dbcf42e408d62e30fb", [:mix], [], "hexpm", "6968e775491cd857f9b6ff940bf2574fd1c2fab84fa7e14d5f56c39174c00018"},
"phoenix": {:hex, :phoenix, "1.6.12", "f8f8ac077600f84419806dd53114b2e77aedde7a502e74181a7d886355aa0643", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d6cf5583c9c20f7103c40e6014ef802d96553b8e5d6585ad6e627bd5ddb0d12"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"},
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.1", "fb94a33c077141f9ac7930b322a7a3b99f9b144bf3a08dd667b9f9aaf0319889", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "6faf1373e5846c8ab68c2cf55cfa5c196c1fbbe0c72d12a4cdfaaac6ef189948"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.11", "205f6aa5405648c76f2abcd57716f42fc07d8f21dd8ea7b262dd12b324b50c95", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7177791944b7f90ed18f5935a6a5f07f760b36f7b3bdfb9d28c57440a3c43f99"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.12", "74f4c0ad02d7deac2d04f50b52827a5efdc5c6e7fac5cede145f5f0e4183aedc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "af6dd5e0aac16ff43571f527a8e0616d62cb80b10eb87aac82170243e50d99c8"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
"plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"saxy": {:hex, :saxy, "1.4.0", "c7203ad20001f72eaaad07d08f82be063fa94a40924e6bb39d93d55f979abcba", [:mix], [], "hexpm", "3fe790354d3f2234ad0b5be2d99822a23fa2d4e8ccd6657c672901dac172e9a9"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "1.2.0", "a8ce551485a9a3dac8d523542de130eafd12e40bbf76cf0ecd2528f24e812a44", [:rebar3], [], "hexpm", "1427e73667b9a2002cf1f26694c422d5c905df889023903c4518921d53e3e883"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"wx": {:hex, :bridge, "1.0.10", "5051dfe881e498a0bc056603df97b734bfe5fdc084f5e56dc42fab8049247144", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b30941e57a194e557a123c5246f123f8b13acdd5c580f75590a52e72bda15632"},
"thoas": {:hex, :thoas, "0.4.0", "86a72ccdc5ec388a13f9f843bcd6c1076640233b95440e47ffb8e3c0dbdb5a17", [:rebar3], [], "hexpm", "442296847aca11db8d25180693d7ca3073d6d7179f66952f07b16415306513b6"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}

321
src/azure/b2c.gleam Normal file
View file

@ -0,0 +1,321 @@
//// Implementation of Azure B2C authentication as used by the Thermia API.
//// Mostly translated from:
//// https://github.com/klejejs/python-thermia-online-api/blob/2f0ec4e45bfecbd90932a10247283cbcd6a6c48c/ThermiaOnlineAPI/api/ThermiaAPI.py
//// Used under the Gnu General Public License 3.0
import gleam/json
import gleam/base
import gleam/hackney
import gleam/uri
import gleam/http
import gleam/http/request
import gleam/http/response
import gleam/dynamic
import gleam/string
import gleam/list
import gleam/result
import gleam/int
import gleam/io
import azure/utils
import helpers/crypto
import helpers/uri as uri_helpers
const challenge_length = 43
const b2c_client_id = "09ea4903-9e95-45fe-ae1f-e3b7d32fa385"
const b2c_scope = b2c_client_id
const b2c_redirect_uri = "https://online-genesis.thermia.se/login"
const b2c_auth_url = "https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline"
const b2c_authorize_prefix = "var SETTINGS = "
pub type Tokens {
Tokens(access_token: String, refresh_token: String)
}
pub type B2CError {
B2CError(msg: String)
}
type Cookies =
List(#(String, String))
type AuthInfo {
AuthInfo(state_code: String, csrf_token: String, cookies: Cookies)
}
type SelfAsserted {
SelfAsserted(cookies: Cookies)
}
type Confirmed {
Confirmed(code: String)
}
/// Authenticate to API with B2C authentication, returning the tokens needed
/// for using the API.
pub fn authenticate(
username: String,
password: String,
) -> Result(Tokens, B2CError) {
let code_challenge = utils.generate_challenge(challenge_length)
try auth_info = authorize(code_challenge)
try self_asserted = signin(username, password, auth_info)
try confirmed = confirm(self_asserted, auth_info)
get_tokens(confirmed, code_challenge)
}
pub fn refresh(tokens: Tokens) -> Result(Tokens, B2CError) {
todo
}
fn authorize(code_challenge: String) -> Result(AuthInfo, B2CError) {
let auth_data = [
#("response_type", "code"),
#("code_challenge", hash_challenge(code_challenge)),
#("code_challenge_method", "S256"),
..base_request_data()
]
let req = build_req(authorize_url(), http.Get)
let req = request.set_query(req, auth_data)
try resp = run_req(req)
let body_split = string.split(resp.body, "\n")
try settings =
list.find(
body_split,
fn(line) { string.starts_with(line, b2c_authorize_prefix) },
)
|> map_error("Authorize settings string not found.")
let prefix_len = string.length(b2c_authorize_prefix)
let settings_json =
string.slice(settings, prefix_len, string.length(settings) - prefix_len - 2)
try data =
json.decode(settings_json, using: dynamic.dynamic)
|> map_error(
"Authorize settings JSON parsing error: " <> string.inspect(settings_json),
)
try csrf_token = data_get(data, "csrf", dynamic.string)
try state_code_block = data_get(data, "transId", dynamic.string)
try state_code =
state_code_block
|> string.split("=")
|> list.at(1)
|> map_error("State code parsing error: " <> state_code_block)
Ok(AuthInfo(
state_code: state_code,
csrf_token: csrf_token,
cookies: response.get_cookies(resp),
))
}
fn signin(
username: String,
password: String,
auth_info: AuthInfo,
) -> Result(SelfAsserted, B2CError) {
let self_asserted_data = [
#("request_type", "RESPONSE"),
#("signInName", username),
#("password", password),
]
let req =
build_req(self_asserted_url(), http.Post)
|> request.set_body(uri_helpers.form_urlencoded_serialize(
self_asserted_data,
))
|> request.set_query(base_query(auth_info))
|> request.set_header("x-csrf-token", auth_info.csrf_token)
let req =
list.fold(
auth_info.cookies,
req,
fn(acc, cookie) {
let #(name, value) = cookie
request.set_cookie(acc, name, value)
},
)
try resp = run_req(req)
case string.contains(resp.body, "{\"status\":\"400\"") {
True -> Error(B2CError(msg: "Wrong credentials."))
False -> Ok(SelfAsserted(cookies: response.get_cookies(resp)))
}
}
fn confirm(
self_asserted: SelfAsserted,
auth_info: AuthInfo,
) -> Result(Confirmed, B2CError) {
let csrf_cookie_key = "x-ms-cpim-csrf"
try csrf_cookie =
list.key_find(auth_info.cookies, csrf_cookie_key)
|> map_error("CSRF cookie not found in auth info.")
let cookies = [#(csrf_cookie_key, csrf_cookie), ..self_asserted.cookies]
let req = build_req(confirm_url(), http.Get)
let req =
list.fold(
cookies,
req,
fn(acc, cookie) { request.set_cookie(acc, cookie.0, cookie.1) },
)
|> request.set_query([
#("csrf_token", auth_info.csrf_token),
..base_query(auth_info)
])
try resp =
req
|> hackney.send()
|> map_error("Confirm HTTP request failed.")
try resp = case resp.status {
302 -> Ok(resp)
_ -> Error(B2CError(msg: "Confirm HTTP request bad error code."))
}
try location =
response.get_header(resp, "location")
|> map_error("Location not found for confirm response.")
try code =
location
|> string.split("code=")
|> list.at(1)
|> map_error("Confirmation code not found.")
Ok(Confirmed(code: code))
}
fn get_tokens(
confirmed: Confirmed,
code_challenge: String,
) -> Result(Tokens, B2CError) {
let request_token_data = [
#("code", confirmed.code),
#("code_verifier", code_challenge),
#("grant_type", "authorization_code"),
..base_request_data()
]
let req =
build_req(get_token_url(), http.Post)
|> request.set_body(uri_helpers.form_urlencoded_serialize(
request_token_data,
))
try resp = run_req(req)
try data =
json.decode(resp.body, using: dynamic.dynamic)
|> map_error("Get tokens JSON parsing error: " <> string.inspect(resp.body))
try token = data_get(data, "access_token", dynamic.string)
try refresh_token = data_get(data, "refresh_token", dynamic.string)
Ok(Tokens(access_token: token, refresh_token: refresh_token))
}
fn hash_challenge(challenge: String) -> String {
let hashed = crypto.hash(crypto.Sha256, challenge)
base.url_encode64(hashed, False)
}
fn authorize_url() -> String {
b2c_auth_url <> "/oauth2/v2.0/authorize"
}
fn self_asserted_url() -> String {
b2c_auth_url <> "/SelfAsserted"
}
fn confirm_url() -> String {
b2c_auth_url <> "/api/CombinedSigninAndSignup/confirmed"
}
fn get_token_url() -> String {
b2c_auth_url <> "/oauth2/v2.0/token"
}
fn build_req(url: String, method: http.Method) -> request.Request(String) {
assert Ok(req_url) = uri.parse(url)
assert Ok(req) = request.from_uri(req_url)
let req = request.set_method(req, method)
case method {
http.Post ->
request.set_header(
req,
"content-type",
"application/x-www-form-urlencoded; charset=UTF-8",
)
_ -> req
}
}
fn run_req(
req: request.Request(String),
) -> Result(response.Response(String), B2CError) {
try resp =
req
|> hackney.send()
|> map_error("HTTP request failed.")
case resp.status {
200 -> Ok(resp)
code ->
Error(B2CError(
msg: "Not OK response code " <> int.to_string(code) <> " from URL " <> uri.to_string(request.to_uri(
req,
)) <> "\n\n" <> string.inspect(resp),
))
}
}
fn base_request_data() -> List(#(String, String)) {
[
#("client_id", b2c_client_id),
#("scope", b2c_scope),
#("redirect_uri", b2c_redirect_uri),
]
}
fn base_query(auth_info: AuthInfo) -> List(#(String, String)) {
[
#("tx", "StateProperties=" <> auth_info.state_code),
#("p", "B2C_1A_SignUpOrSigninOnline"),
]
}
fn data_get(
data: dynamic.Dynamic,
key: String,
data_type: dynamic.Decoder(a),
) -> Result(a, B2CError) {
data
|> dynamic.field(key, data_type)
|> map_error(
"Field " <> key <> " of correct type not found in data: " <> string.inspect(
data,
),
)
}
fn map_error(r: Result(a, b), error_msg: String) -> Result(a, B2CError) {
result.map_error(r, fn(_) { B2CError(msg: error_msg) })
}

28
src/azure/utils.gleam Normal file
View file

@ -0,0 +1,28 @@
import gleam/string
import gleam/list
import gleam/int
import gleam/bit_string
import helpers/binary
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const alphabet_len = 62
pub fn generate_challenge(length: Int) -> String {
let alphabet_str = bit_string.from_string(alphabet)
list.range(0, length - 1)
|> list.map(fn(_) { random_char(alphabet_str) })
|> string.join(with: "")
}
fn random_char(alphabet: BitString) -> String {
let index = int.random(0, alphabet_len)
assert Ok(char) =
alphabet
|> binary.part(index, 1)
|> bit_string.to_string()
char
}

View file

@ -0,0 +1,8 @@
import gleam/erlang/atom.{Atom}
import gleam/dynamic.{Dynamic}
pub external fn get_env(Atom, Atom) -> Dynamic =
"Elixir.Application" "get_env"
pub external fn fetch_env_angry(Atom, Atom) -> Dynamic =
"Elixir.Application" "fetch_env!"

2
src/helpers/binary.gleam Normal file
View file

@ -0,0 +1,2 @@
pub external fn part(data: BitString, pos: Int, len: Int) -> BitString =
"binary" "part"

37
src/helpers/config.gleam Normal file
View file

@ -0,0 +1,37 @@
import gleam/erlang/atom
import gleam/dynamic
import gleam/uri
import helpers/application
fn app_name() -> atom.Atom {
assert Ok(name) = atom.from_string("geo_therminator")
name
}
pub fn api_timeout() -> Int {
let timeout =
application.fetch_env_angry(
app_name(),
atom.create_from_string("api_timeout"),
)
assert Ok(timeout_int) = dynamic.int(timeout)
timeout_int
}
pub fn api_auth_url() -> uri.Uri {
config_url("api_auth_url")
}
pub fn api_installations_url() -> uri.Uri {
config_url("api_installations_url")
}
fn config_url(config_key: String) -> uri.Uri {
let url =
application.fetch_env_angry(app_name(), atom.create_from_string(config_key))
assert Ok(url_str) = dynamic.string(url)
assert Ok(parsed_url) = uri.parse(url_str)
parsed_url
}

7
src/helpers/crypto.gleam Normal file
View file

@ -0,0 +1,7 @@
pub type HashAlgorithm {
// For now, only SHA256
Sha256
}
pub external fn hash(algo: HashAlgorithm, data: String) -> BitString =
"crypto" "hash"

View file

@ -0,0 +1,9 @@
import gleam/dynamic.{Dynamic}
import gleam/map.{Map}
import gleam/erlang/atom.{Atom}
pub type DateTime =
Map(Atom, Dynamic)
pub external fn from_iso8601(String) -> Result(#(DateTime, Int), Atom) =
"Elixir.DateTime" "from_iso8601"

4
src/helpers/io_lib.gleam Normal file
View file

@ -0,0 +1,4 @@
import gleam/erlang/charlist.{Charlist}
pub external fn format_int(format: String, data: List(Int)) -> Charlist =
"io_lib" "format"

47
src/helpers/uri.gleam Normal file
View file

@ -0,0 +1,47 @@
import gleam/string
import gleam/string_builder
import gleam/list
import gleam/uri
const not_encoded_by_gleam = [
#("!", "%21"),
#("$", "%24"),
#("'", "%27"),
#("(", "%28"),
#(")", "%29"),
#("+", "%2B"),
#("~", "%7E"),
]
/// x-www-form-urlencoded encoder, follows the spec defined in
/// https://url.spec.whatwg.org/#application/x-www-form-urlencoded
pub fn form_urlencoded_serialize(data: List(#(String, String))) -> String {
data
|> list.map(fn(item) {
let #(key, value) = item
serialize_value(key)
|> string_builder.append("=")
|> string_builder.append_builder(serialize_value(value))
})
|> string_builder.join("&")
|> string_builder.to_string()
}
fn serialize_value(value: String) -> string_builder.StringBuilder {
value
|> string.split(" ")
|> list.map(fn(part) {
let encoded = uri.percent_encode(part)
list.fold(
not_encoded_by_gleam,
encoded,
fn(acc, item) {
let #(char, replacement) = item
string.replace(acc, char, replacement)
},
)
|> string_builder.from_string()
})
|> string_builder.join("+")
}

104
src/pump_api/auth/api.gleam Normal file
View file

@ -0,0 +1,104 @@
import gleam/http/request
import gleam/json
import gleam/hackney
import gleam/result
import gleam/dynamic
import gleam/list
import pump_api/auth/user.{User}
import pump_api/auth/token.{Token}
import pump_api/auth/installation_info.{InstallationInfo}
import pump_api/http
import helpers/config
import helpers/date_time
pub type ApiError {
ApiRequestFailed
NotOkResponse
InvalidData
}
pub fn auth(username: String, password: String) -> Result(User, ApiError) {
let url = config.api_auth_url()
assert Ok(raw_req) = request.from_uri(url)
let data =
json.object([
#("username", json.string(username)),
#("password", json.string(password)),
])
let json_req = request.set_body(raw_req, http.Json(data))
try data = run_req(http.req(json_req))
try token_str = data_get(data, "token", dynamic.string)
try valid_to = data_get(data, "tokenValidToUtc", dynamic.string)
try valid_to_dt =
date_time.from_iso8601(valid_to)
|> map_error(InvalidData)
let token = Token(token: token_str, token_valid_to: valid_to_dt.0)
try user_name = data_get(data, "userName", dynamic.string)
try email = data_get(data, "email", dynamic.string)
try first_name = data_get(data, "firstName", dynamic.string)
try last_name = data_get(data, "lastName", dynamic.string)
try culture = data_get(data, "culture", dynamic.string)
try eula_accepted = data_get(data, "eulaAccepted", dynamic.bool)
try is_authenticated = data_get(data, "isAuthenticated", dynamic.bool)
try time_zone = data_get(data, "timeZone", dynamic.string)
Ok(User(
user_name: user_name,
email: email,
first_name: first_name,
last_name: last_name,
culture: culture,
eula_accepted: eula_accepted,
is_authenticated: is_authenticated,
time_zone: time_zone,
token: token,
))
}
pub fn installation_info(user: User) -> Result(List(InstallationInfo), ApiError) {
let url = config.api_installations_url()
assert Ok(raw_req) = request.from_uri(url)
let empty_req = request.set_body(raw_req, http.Empty)
try data = run_req(http.authed_req(user, empty_req))
try items =
data_get(data, "items", dynamic.list(of: dynamic.field("id", dynamic.int)))
Ok(list.map(items, fn(id) { InstallationInfo(id: id) }))
}
fn run_req(req: request.Request(String)) {
try resp =
req
|> hackney.send()
|> map_error(ApiRequestFailed)
try body = case resp.status {
200 -> Ok(resp.body)
_ -> Error(NotOkResponse)
}
body
|> json.decode(using: dynamic.dynamic)
|> map_error(InvalidData)
}
fn data_get(
data: dynamic.Dynamic,
key: String,
data_type: dynamic.Decoder(a),
) -> Result(a, ApiError) {
data
|> dynamic.field(key, data_type)
|> map_error(InvalidData)
}
fn map_error(r: Result(a, b), new_error: ApiError) -> Result(a, ApiError) {
result.map_error(r, fn(_) { new_error })
}

View file

@ -0,0 +1,3 @@
pub type InstallationInfo {
InstallationInfo(id: Int)
}

View file

@ -0,0 +1,5 @@
import helpers/date_time.{DateTime}
pub type Token {
Token(token: String, token_valid_to: DateTime)
}

View file

@ -0,0 +1,15 @@
import pump_api/auth/token
pub type User {
User(
user_name: String,
email: String,
first_name: String,
last_name: String,
culture: String,
eula_accepted: Bool,
is_authenticated: Bool,
time_zone: String,
token: token.Token,
)
}

31
src/pump_api/http.gleam Normal file
View file

@ -0,0 +1,31 @@
import gleam/http/request
import gleam/json.{Json}
import pump_api/auth/user.{User}
pub type Body {
Empty
Json(data: Json)
String(data: String)
}
pub type ApiRequest =
request.Request(Body)
pub fn authed_req(user: User, r: ApiRequest) {
r
|> request.set_header("authorization", "Bearer " <> user.token.token)
|> req()
}
pub fn req(r: ApiRequest) -> request.Request(String) {
let r = request.set_header(r, "accept", "application/json")
case r.body {
Empty -> request.set_body(r, "")
String(data) -> request.set_body(r, data)
Json(data) ->
r
|> request.set_header("content-type", "application/json")
|> request.set_body(json.to_string(data))
}
}