diff --git a/code/__DEFINES/rust.dm b/code/__DEFINES/rust.dm index ce5382642f3..6ea0d162ffd 100644 --- a/code/__DEFINES/rust.dm +++ b/code/__DEFINES/rust.dm @@ -37,14 +37,18 @@ else // First check if it's built in the usual place. if(fexists("./rust/target/i686-pc-windows-msvc/debug/rustlibs.dll")) + log_world("\[Rustlibs] Loading debug build") return __rustlib = "./rust/target/i686-pc-windows-msvc/debug/rustlibs.dll" if(fexists("./rust/target/i686-pc-windows-msvc/release/rustlibs.dll")) + log_world("\[Rustlibs] Loading release build") return __rustlib = "./rust/target/i686-pc-windows-msvc/release/rustlibs.dll" // Then check in the current directory. if(fexists("./rustlibs[RUSTLIBS_SUFFIX].dll")) + log_world("\[Rustlibs] Loading repository build A") return __rustlib = "./rustlibs[RUSTLIBS_SUFFIX].dll" // And elsewhere. + log_world("\[Rustlibs] Loading repository build B") var/assignment_confirmed = (__rustlib = "rustlibs[RUSTLIBS_SUFFIX].dll") // This being spanned over multiple lines is kinda scuffed, but its needed because of https://www.byond.com/forum/post/2072419 return assignment_confirmed @@ -133,6 +137,23 @@ /proc/mapmanip_read_dmm(mapname) return RUSTLIB_CALL(mapmanip_read_dmm_file, mapname) +// MARK: MFA + +/proc/rustlibs_mfa_generate_secret() + return RUSTLIB_CALL(mfa_generate_secret) + +/proc/rustlibs_mfa_generate_qr(secret, ckey) + return RUSTLIB_CALL(mfa_generate_qr, secret, ckey) + +/proc/rustlibs_mfa_verify_code(secret, code) + return RUSTLIB_CALL(mfa_verify_code, secret, code) + + +// MARK: Misc + +/proc/rustlibs_generate_uuid() + return RUSTLIB_CALL(misc_new_uuid) + // MARK: TOML /proc/rustlibs_read_toml_file(path) var/list/output = json_decode(RUSTLIB_CALL(toml_file_to_json, path) || "null") diff --git a/code/__HELPERS/api.dm b/code/__HELPERS/api.dm deleted file mode 100644 index 5941dad585a..00000000000 --- a/code/__HELPERS/api.dm +++ /dev/null @@ -1,40 +0,0 @@ -// This file contains procs for interacting with the internal API - -/** - * Internal API Caller - * - * Makes calls to the internal Paradise API and returns a [/datum/http_response]. - * - * Arguments: - * * method - The relevant HTTP method to use - * * path - The path of the API call. DO NOT USE A LEADING SLASH - * * body - The request body, if applicable - */ -/proc/MakeAPICall(method, path, body) - if(!method || !path) - // Needs valid params - return null - if(!GLOB.configuration.system.api_host || !GLOB.configuration.system.api_key) - // Needs these set in config - return null - - if(IsAdminAdvancedProcCall()) - // Admins shouldnt fuck with this - to_chat(usr, SPAN_BOLDANNOUNCEOOC("API interaction blocked: Advanced ProcCall detected.")) - message_admins("[key_name(usr)] attempted to interact with the internal API via advanced proc-call") - log_admin("[key_name(usr)] attempted to interact with the internal API via advanced proc-call") - return - - var/datum/http_request/req = new() - var/target_url = "[GLOB.configuration.system.api_host]/[path]" - // You may be asking, "Hey AA, why is the above a var instead of just using it directly?" - // Ill tell you why. This lets you breakpoint it in the debugger to see if the URL is - // what you wanted or not, given how slashes and stuff can break it. - req.prepare(method, target_url, body, list("AuthKey" = GLOB.configuration.system.api_key)) - req.begin_async() - // Check if we are complete - UNTIL(req.is_complete()) - var/datum/http_response/res = req.into_response() - - return res - diff --git a/code/controllers/configuration/sections/system_configuration.dm b/code/controllers/configuration/sections/system_configuration.dm index eac494de3e0..33769bd75bc 100644 --- a/code/controllers/configuration/sections/system_configuration.dm +++ b/code/controllers/configuration/sections/system_configuration.dm @@ -10,10 +10,6 @@ var/is_production = FALSE /// If above is true, you can run a shell command var/shutdown_shell_command = null - /// Internal API host - var/api_host = null - /// Internal API key - var/api_key = null /// Github API token var/github_api_token = null /// List of IP addresses which bypass world topic rate limiting @@ -47,8 +43,6 @@ CONFIG_LOAD_STR(topic_key, data["communications_password"]) CONFIG_LOAD_STR(shutdown_shell_command, data["shutdown_shell_command"]) - CONFIG_LOAD_STR(api_host, data["api_host"]) - CONFIG_LOAD_STR(api_key, data["api_key"]) CONFIG_LOAD_STR(github_api_token, data["github_api_token"]) CONFIG_LOAD_LIST(topic_ip_ratelimit_bypass, data["topic_ip_ratelimit_bypass"]) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 61fd2674915..8a23e2668da 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -77,7 +77,7 @@ // If 2FA is enabled, makes sure they were authed within the last minute - if(check_2fa && GLOB.configuration.system.api_host) + if(check_2fa) // First see if they exist at all var/datum/db_query/check_query = SSdbcore.NewQuery("SELECT 2fa_status, ip FROM player WHERE ckey=:ckey", list("ckey" = ckey(key))) diff --git a/code/modules/admin/verbs/debug_verbs.dm b/code/modules/admin/verbs/debug_verbs.dm index 5b4a023e49f..3e55fcbebd5 100644 --- a/code/modules/admin/verbs/debug_verbs.dm +++ b/code/modules/admin/verbs/debug_verbs.dm @@ -38,10 +38,10 @@ USER_VERB(advanced_proccall, R_PROCCALL, "Advanced ProcCall", "Advanced ProcCall if(!procname) return // absolutely not - if(findtextEx(trim(lowertext(procname)), "rustg")) - message_admins(SPAN_USERDANGER("[key_name_admin(client)] attempted to proc call rust-g procs. Inform the host at once.")) - log_admin("[key_name(client)] attempted to proc call rust-g procs. Inform the host at once.") - GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_ADMIN, "[key_name(client)] attempted to proc call rustg things. Inform the host at once.") + if(findtextEx(trim(lowertext(procname)), "rustlibs")) + message_admins(SPAN_USERDANGER("[key_name_admin(client)] attempted to proc call rustlibs procs. Inform the host at once.")) + log_admin("[key_name(client)] attempted to proc call rustlibs procs. Inform the host at once.") + GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_ADMIN, "[key_name(client)] attempted to proc call rustlibs procs. Inform the host at once.") return if(targetselected && !hascall(target,procname)) diff --git a/code/modules/client/2fa.dm b/code/modules/client/2fa.dm index 52c83eff38a..4e9f0677b44 100644 --- a/code/modules/client/2fa.dm +++ b/code/modules/client/2fa.dm @@ -1,61 +1,47 @@ // This is in its own file as it has so much stuff to contend with /client/proc/edit_2fa() - if(!GLOB.configuration.system.api_host) - alert(usr, "This server does not have 2FA enabled.") - return // Client does not have 2FA enabled. Set it up. if(prefs._2fa_status == _2FA_DISABLED) - // Get us an auth token - var/datum/http_response/qrcr = MakeAPICall(RUSTLIBS_HTTP_METHOD_GET, "2fa/generate_qr?ckey=[ckey]") - // If this fails, shits gone bad - if(qrcr.errored) - alert(usr, "Something has gone VERY wrong ingame. Please inform the server host.\nError details: [qrcr.error]") - return + // Step 1 - Generate a secret + var/mfa_secret = rustlibs_mfa_generate_secret() - if(qrcr.status_code != 200) - alert(usr, "2FA QR code generation returned a non-200 code. Please inform the server host.\nError code: [qrcr.status_code]\nError details: [qrcr.body]") - return + // Step 2 - Generate QR code + var/mfa_qr_code = rustlibs_mfa_generate_qr(mfa_secret, ckey) - var/list/data = json_decode(qrcr.body) - var/qr_img_src = data["qr_image"] - var/datum/browser/B = new(usr, "2fa_qrc", "2FA QR Code", 600, 400) + var/datum/browser/B = new(usr, "2fa_qrc", "2FA QR Code", 600, 560) var/title_text = "

Below is a QR code to scan inside your authenticator app to generate 2FA codes. Please verify it before closing this window. (Behind this window is a text box)

" - var/img_data = "
" + var/img_data = "
" B.set_content("[title_text][img_data]") B.open(FALSE) var/entered_code = input(usr, "Please enter a code from your auth app. Failure to enter the code correctly will abort 2FA setup.", "2FA Validation") if(!entered_code) - // Cleanup so they can start again - var/datum/db_query/dbq = SSdbcore.NewQuery("DELETE FROM 2fa_secrets WHERE ckey=:ckey", list("ckey" = ckey)) - dbq.warn_execute() alert(usr, "2FA Setup aborted!") B.close() return - var/datum/http_response/vr = MakeAPICall(RUSTLIBS_HTTP_METHOD_GET, "2fa/validate_code?ckey=[ckey]&code=[entered_code]") - // If this fails, shits gone bad - if(vr.errored) - alert(usr, "Something has gone VERY wrong ingame. Please inform the server host.\nError details: [vr.error]") + // See if the code entered is corrected + var/mfa_success = rustlibs_mfa_verify_code(mfa_secret, entered_code) + + if(!mfa_success) + alert(usr, "Incorrect MFA code entered - check your phone date and time settings.") B.close() return - if(vr.status_code != 200) - // Cleanup so they can start again - var/datum/db_query/dbq = SSdbcore.NewQuery("DELETE FROM 2fa_secrets WHERE ckey=:ckey", list("ckey" = ckey)) - dbq.warn_execute() - - // See if its unauthorised. I used 400 for that dont at me - if(vr.status_code == 400) - alert(usr, "Invalid code entered. 2FA Setup aborted!") - B.close() - else - alert(usr, "2FA validation returned a non-200 code. Please inform the server host.\nError code: [vr.status_code]\nError details: [vr.body]") - B.close() - return - // If we are here, they authed successfully B.close() + + // Do our DB update + var/datum/db_query/insert_qry = SSdbcore.NewQuery("INSERT INTO 2fa_secrets (ckey, secret) VALUES (:ckey, :secret)", list( + "ckey" = ckey, + "secret" = mfa_secret + )) + + if(!insert_qry.warn_execute()) + qdel(insert_qry) + alert(usr, "MFA failed to save to the DB - please inform the server host.") + return + // Default to IP change only prefs._2fa_status = _2FA_ENABLED_IP prefs.save_preferences(src) @@ -93,18 +79,31 @@ alert(usr, "2FA deactivation aborted!") return - var/datum/http_response/vr = MakeAPICall(RUSTLIBS_HTTP_METHOD_GET, "2fa/validate_code?ckey=[ckey]&code=[entered_code]") - // If this fails, shits gone bad - if(vr.errored) - alert(usr, "Something has gone VERY wrong ingame. Please inform the server host.\nError details: [vr.error]") + // Get their secret + var/datum/db_query/load_qry = SSdbcore.NewQuery("SELECT secret FROM 2fa_secrets WHERE ckey=:ckey", list( + "ckey" = ckey, + )) + + if(!load_qry.warn_execute()) + qdel(load_qry) + alert(usr, "Failed to load your existing MFA token - please inform the server host.") return - if(vr.status_code != 200) - // See if its unauthorised. I used 400 for that dont at me - if(vr.status_code == 400) - alert(usr, "Invalid code entered. 2FA deactivation aborted!") - else - alert(usr, "2FA validation returned non-200 code. Please inform the server host.\nError code: [vr.status_code]\nError details: [vr.body]") + var/db_secret = null + + if(load_qry.NextRow()) + db_secret = load_qry.item[1] + + qdel(load_qry) + + if(!db_secret) + alert(usr, "Failed to load your existing MFA token - please inform the server host.") + return + + var/mfa_result = rustlibs_mfa_verify_code(db_secret, entered_code) + + if(!mfa_result) + alert(usr, "Invalid code entered. 2FA deactivation aborted. If you have lost your authenticator, please inform the server host.") return // If we are here, they authed properly diff --git a/config/example/config.toml b/config/example/config.toml index eefb6d305b5..5ae84466b88 100644 --- a/config/example/config.toml +++ b/config/example/config.toml @@ -799,10 +799,6 @@ active_lava_ruins = [ shutdown_on_reboot = false # If true to above, an optional shell command can be ran. Defaults to killing DD on windows #shutdown_shell_command = "taskkill /im dreamdaemon.exe /f" -# URL for the internal API host. See https://github.com/ParadiseSS13/ParadiseInternalAPI Do not use https:// or a trailing slash. Comment out to disable -#api_host = "http://127.0.0.1:8080" -# Access key for the internal API. -#api_key = "your_secret_here" # Github API token for in game bug reporting #github_api_token = "your_secret_here" # List of IP addresses to be ignored by the world/Topic rate limiting. Useful if you have other services diff --git a/paradise.dme b/paradise.dme index dee7109402c..712343a8999 100644 --- a/paradise.dme +++ b/paradise.dme @@ -200,7 +200,6 @@ #include "code\__HELPERS\_logging.dm" #include "code\__HELPERS\_string_lists.dm" #include "code\__HELPERS\AnimationLibrary.dm" -#include "code\__HELPERS\api.dm" #include "code\__HELPERS\atom_helpers.dm" #include "code\__HELPERS\basic_ai.dm" #include "code\__HELPERS\bitflag_lists.dm" diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 957592c0f20..4b617fc4ffd 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -228,6 +228,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + [[package]] name = "base64" version = "0.22.1" @@ -386,6 +392,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -489,6 +501,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -615,7 +633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a92e2d3660e5513454071018433d22bbdfc8a79b9bcf1b6399aa72e96b8586" dependencies = [ "lerp", - "rand", + "rand 0.8.6", "rand_pcg", "rand_seeder", "rayon", @@ -677,6 +695,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -714,7 +733,7 @@ dependencies = [ "inflate", "lodepng", "ndarray", - "rand", + "rand 0.8.6", ] [[package]] @@ -1030,6 +1049,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1167,6 +1195,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1438,6 +1479,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "mysql" version = "26.0.0" @@ -1500,7 +1551,7 @@ dependencies = [ "mysql-common-derive", "num-bigint", "num-traits", - "rand", + "rand 0.8.6", "regex", "saturating", "serde", @@ -1762,7 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.6", ] [[package]] @@ -1843,6 +1894,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.9.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.9.0" @@ -1966,6 +2030,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qrcodegen" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" + +[[package]] +name = "qrcodegen-image" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99530e45ded4640c0eab5420fc60f9a0ec1be51a22e49cc8578b9a0d8be70712" +dependencies = [ + "base64", + "image", + "qrcodegen", +] + [[package]] name = "quick-xml" version = "0.37.5" @@ -2020,8 +2107,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2031,7 +2128,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -2043,13 +2150,22 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rand_pcg" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2058,7 +2174,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf2890aaef0aa82719a50e808de264f9484b74b442e1a3a0e5ee38243ac40bdb" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2203,8 +2319,8 @@ dependencies = [ "mysql", "notify-rust", "once_cell", - "png", - "rand", + "png 0.17.16", + "rand 0.8.6", "redis", "regex", "scc", @@ -2212,6 +2328,7 @@ dependencies = [ "serde_json", "thread-priority", "toml 0.8.23", + "totp-rs", "ureq", "uuid", "walkdir", @@ -2690,6 +2807,23 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "totp-rs" +version = "5.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e69a15e21b2ff22c415446983978bded3244195f17d59cb113551c1e806f91" +dependencies = [ + "base32", + "constant_time_eq", + "hmac", + "qrcodegen-image", + "rand 0.9.4", + "sha1 0.10.6", + "sha2", + "url", + "urlencoding", +] + [[package]] name = "tracing" version = "0.1.41" @@ -2783,6 +2917,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2a823f76167..8dcec11bdef 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -48,6 +48,7 @@ once_cell = { version = "1.21" } mysql = { git = "https://github.com/ZeWaka/rust-mysql-simple.git", tag = "v26.0.0", default-features = false } dashmap = { version = "6.1", features = ["rayon", "serde"] } uuid = { version = "1.18.1", features = ["v4"] } +totp-rs = { version = "5.7.2", features = ["qr", "gen_secret"] } [features] # Use the native tls stack for the mysql db diff --git a/rust/README.MD b/rust/README.MD index c0d64781ed8..12973c84b22 100644 --- a/rust/README.MD +++ b/rust/README.MD @@ -9,6 +9,8 @@ It currently handles: - Atmospherics (`milla`) - DMM manipulation stuff (`mapmanip`) +- MFA +- Some other misc stuff like UUID generation It also imports and tweaks the following [rust-g](https://github.com/tgstation/rust-g) features: diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4999a3825a2..80b7dd81482 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,7 +1,10 @@ mod jobs; mod logging; mod mapmanip; +mod mfa; mod milla; +mod misc; +// If its prefixed with rustlibs, its a port of the rust-g ones mod rustlibs_dmi; mod rustlibs_file; mod rustlibs_git; diff --git a/rust/src/mapmanip/procgen/mod.rs b/rust/src/mapmanip/procgen/mod.rs index 14a189955aa..39a74a143a8 100644 --- a/rust/src/mapmanip/procgen/mod.rs +++ b/rust/src/mapmanip/procgen/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::{HashMap, HashSet}, iter::Map}; +use std::collections::{HashMap, HashSet}; use super::core::GridMap; diff --git a/rust/src/mfa/mod.rs b/rust/src/mfa/mod.rs new file mode 100644 index 00000000000..8fd43345c1e --- /dev/null +++ b/rust/src/mfa/mod.rs @@ -0,0 +1,57 @@ +use byondapi::value::ByondValue; +use totp_rs::{Algorithm, Secret, TOTP}; + +const ISSUER: &str = "Paradise Station"; + +// This is a common method to ensure parameters stay the same between creation and verification +fn instance_totp_manager(secret: String, accname: String) -> TOTP { + TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + Secret::Encoded(secret.to_owned()).to_bytes().unwrap(), + Some(ISSUER.to_string()), + accname.to_owned(), + ) + .unwrap() // who needs safety lmao +} + +#[byondapi::bind] +fn mfa_generate_secret() -> eyre::Result { + let mfa_secret = Secret::generate_secret().to_encoded().to_string(); + + Ok(ByondValue::new_str(mfa_secret)?) +} + +#[byondapi::bind] +fn mfa_generate_qr(secret: ByondValue, ckey: ByondValue) -> eyre::Result { + let secret_str = secret.get_string()?; + let ckey_str = ckey.get_string()?; + + // Get the QR from our secret + let png_base64 = instance_totp_manager(secret_str, ckey_str) + .get_qr_base64() + .unwrap(); + + // Send it back + Ok(ByondValue::new_str(format!( + "data:image/png;base64,{png_base64}" + ))?) +} + +#[byondapi::bind] +fn mfa_verify_code(secret: ByondValue, code: ByondValue) -> eyre::Result { + // Code MUST be a string to avoid 0 prefixing issues + let secret_str = secret.get_string()?; + let code_str = code.get_string()?; + + let totp = instance_totp_manager(secret_str, String::new()); + + // Check provided code + if totp.check_current(&code_str).unwrap_or(false) { + return Ok(ByondValue::new_num(1f32)); + } else { + return Ok(ByondValue::new_num(0f32)); + } +} diff --git a/rust/src/milla/tick.rs b/rust/src/milla/tick.rs index 185a7ff8f9f..219360e914d 100644 --- a/rust/src/milla/tick.rs +++ b/rust/src/milla/tick.rs @@ -318,8 +318,8 @@ mod tests { toxins_: Option, sleeping_agent_: Option, agent_b_: Option, - hydrogen: Option, - water_vapor: Option, + hydrogen_: Option, + water_vapor_: Option, thermal_energy_: Option, temperature_: Option, } @@ -333,8 +333,8 @@ mod tests { toxins_: None, sleeping_agent_: None, agent_b_: None, - hydrogen: None, - water_vapor: None, + hydrogen_: None, + water_vapor_: None, thermal_energy_: None, temperature_: None, } diff --git a/rust/src/misc/mod.rs b/rust/src/misc/mod.rs new file mode 100644 index 00000000000..c5622284289 --- /dev/null +++ b/rust/src/misc/mod.rs @@ -0,0 +1,8 @@ +use byondapi::value::ByondValue; +use uuid::Uuid; + +#[byondapi::bind] +fn misc_new_uuid() -> eyre::Result { + let uuid: String = Uuid::new_v4().to_string(); + Ok(ByondValue::new_str(uuid)?) +} diff --git a/rust/src/rustlibs_http/mod.rs b/rust/src/rustlibs_http/mod.rs index 36bc727a69c..eae7a4d8d8d 100644 --- a/rust/src/rustlibs_http/mod.rs +++ b/rust/src/rustlibs_http/mod.rs @@ -84,7 +84,7 @@ fn submit_request(prep: RequestPrep) -> Result { // TODO: this is a sinful hack, rewrite this as soon as the module is stable on live let response = match prep.req.send_bytes(&prep.body) { Ok(r) => r, - Err(ureq::Error::Status(code, r)) => r, + Err(ureq::Error::Status(_code, r)) => r, Err(ureq::Error::Transport(t)) => { let mut resp = Response { status_code: 0, diff --git a/rustlibs.dll b/rustlibs.dll index 900266a8d29..9b6bf9f8076 100644 Binary files a/rustlibs.dll and b/rustlibs.dll differ diff --git a/rustlibs_prod.dll b/rustlibs_prod.dll index 8fc5140156d..2f647607d1d 100644 Binary files a/rustlibs_prod.dll and b/rustlibs_prod.dll differ diff --git a/tools/ci/librustlibs_ci.so b/tools/ci/librustlibs_ci.so index e94eda04090..3e8c161f22a 100644 Binary files a/tools/ci/librustlibs_ci.so and b/tools/ci/librustlibs_ci.so differ