From 73cd1f02f33b36e762786421f44c1b700f7464d8 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Thu, 21 Nov 2024 15:04:29 +0100 Subject: [PATCH 01/21] wip: new auth method + prepopulation of upload --- components/app/R/server.R | 7 ++ components/board.upload/R/upload_server.R | 23 ++++- components/board.upload/R/upload_utils.R | 19 ++++ components/modules/AuthenticationModule.R | 106 ++++++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/components/app/R/server.R b/components/app/R/server.R index 568c08d06..c05be43ca 100644 --- a/components/app/R/server.R +++ b/components/app/R/server.R @@ -117,6 +117,13 @@ app_server <- function(input, output, session) { allow_new_users = opt$ALLOW_NEW_USERS, redirect_login = TRUE ) + } else if (authentication == "email-encrypted") { + auth <- EmailEncryptedAuthenticationModule( + id = "auth", + show_modal = TRUE, + # TODO add argument for location of crypto key (probably on ETC) + domain = opt$DOMAIN + ) } else if (authentication == "shinyproxy") { username <- Sys.getenv("SHINYPROXY_USERNAME") auth <- NoAuthenticationModule( diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 878d0bbcb..92310214b 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -945,7 +945,7 @@ UploadBoard <- function(id, wizardR::reset("upload_wizard") # skip upload trigger at first startup - if (new_upload() == 0) { + if (new_upload() == 1) { return(NULL) } @@ -976,6 +976,27 @@ UploadBoard <- function(id, ## compute_info(list( "name" = pgx$name,"description" = pgx$description)) compute_settings$name <- pgx$name compute_settings$description <- pgx$description + } else if (opt$AUTHENTICATION == "email-encrypted") { # Only data preloading on email-encrypted authentication + query_files <- check_query_files() + # TODO unencrypt links!!! to do inside check_query_files + if (!is.null(query_files$counts)) { + file <- read_query_files(query_files$counts) + df <- playbase::read_counts(file) + uploaded$counts.csv <- df + ## if counts file contains annotation + af <- playbase::read_annot(file) + uploaded$annot.csv <- af + } + if (!is.null(query_files$samples)) { + file <- read_query_files(query_files$samples) + df <- playbase::read_samples(file) + uploaded$samples.csv <- df + } + if (!is.null(query_files$contrasts)) { + file <- read_query_files(query_files$contrasts) + df <- playbase::read_contrasts(file) + uploaded$contrasts.csv <- df + } } } else { shinyalert::shinyalert( diff --git a/components/board.upload/R/upload_utils.R b/components/board.upload/R/upload_utils.R index 9b968a73a..10d022d73 100644 --- a/components/board.upload/R/upload_utils.R +++ b/components/board.upload/R/upload_utils.R @@ -308,3 +308,22 @@ write_check_output <- function( write(unlist(lines), file.path(raw_dir, "CHECKS_OUTPUT"), append = TRUE) } } + +# Check if there are counts/samples file links on the query parameters +check_query_files <- function() { + counts <- shiny::getQueryString()$counts + samples <- shiny::getQueryString()$samples + contrasts <- shiny::getQueryString()$contrasts + return(list( + counts = counts, + samples = samples, + contrasts = contrasts + )) +} + +# Read query files +read_query_files <- function(url) { + destination <- tempfile(fileext = ".csv") + download.file(url, destination) + return(destination) +} diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index b49015c3c..5320ed1c7 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1227,6 +1227,112 @@ LoginCodeAuthenticationModule <- function(id, } +EmailEncryptedAuthenticationModule <- function( + id = "auth", + show_modal = TRUE, + # TODO add argument for location of crypto key (probably on ETC) + domain = opt$DOMAIN) { + shiny::moduleServer(id, function(input, output, session) { + message("[AuthenticationModule] >>>> using EmailEncrypted authentication <<<<") + ns <- session$ns + + # TODO add key and uncomment this code + # encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + # if (!file.exists(crypto_key)) { + # ## we continue but email is not working + # warning("[EmailHeaderAuthenticationModule] ERROR : missing crypto_key file!!!") + # } + + USER <- shiny::reactiveValues( + method = "email-encrypted", + logged = FALSE, + username = NA, + email = NA, + level = "", + limit = "", + options = opt, ## global + user_dir = PGX.DIR ## global + ) + + if (show_modal) { + m <- splashLoginModal( + ns = ns, + with.username = FALSE, + with.email = FALSE, + with.password = FALSE, + title = "Sign in", + subtitle = "Ready to explore your data?", + button.text = "Sure I am!" + ) + shiny::showModal(m) + } + + resetUSER <- function() { + dbg("[EmailEncryptedAuthenticationModule] resetUSER called") + USER$logged <- FALSE + USER$username <- NA + USER$email <- NA + USER$password <- NA + USER$level <- "" + USER$limit <- "" + + PLOT_DOWNLOAD_LOGGER <<- reactiveValues(log = list(), str = "") + } + + output$showLogin <- shiny::renderUI({ + resetUSER() + }) + + output$login_warning <- shiny::renderText("") + + ## -------------------------------------- + ## Step 1: Get email query field + ## -------------------------------------- + + query_email <- shiny::reactive({ + # aqui fer que pilli el header enlloc del query string + query_email <- shiny::getQueryString()$email + query_email + }) + + ## -------------------------------------- + ## Step 2: Decrypt email query field + ## -------------------------------------- + + # TODO decrypt email + + ## -------------------------------------- + ## Step 3: Login user + ## -------------------------------------- + # TODO place decrypted email instead of query_email + shiny::observeEvent(input$login_submit_btn, { + if (is.null(query_email())) { + dbg("[EmailEncryptedAuthenticationModule] invalid email") + output$login_warning <- shiny::renderText("invalid email hash") + # TODO show some error on the UI + } else { + USER$email <- query_email() + USER$username <- query_email() + USER$logged <- TRUE + # TODO Use some sort of user_database to see if email allowed?? + # TODO check domain (?) + USER$user_dir <- file.path(PGX.DIR, USER$email) + create_user_dir_if_needed(USER$user_dir, PGX.DIR) + if (!opt$ENABLE_USERDIR) { + USER$user_dir <- file.path(PGX.DIR) + } + dbg("[EmailEncryptedAuthenticationModule] using user OPTIONS") + USER$options <- read_user_options(USER$user_dir) + session$sendCustomMessage("set-user", list(user = USER$email)) + } + }) + + ## export as 'public' functions + USER$resetUSER <- resetUSER + + return(USER) + }) +} ## ================================================================================ ## ================================= END OF FILE ================================== ## ================================================================================ From 7f0dbf63f3a2c05efc6ea3a442dee04de994a8a5 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Thu, 21 Nov 2024 15:29:46 +0100 Subject: [PATCH 02/21] wip: decrypt email --- components/board.upload/R/upload_utils.R | 7 +++++++ components/modules/AuthenticationModule.R | 16 +++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/components/board.upload/R/upload_utils.R b/components/board.upload/R/upload_utils.R index 10d022d73..a140d1d52 100644 --- a/components/board.upload/R/upload_utils.R +++ b/components/board.upload/R/upload_utils.R @@ -327,3 +327,10 @@ read_query_files <- function(url) { download.file(url, destination) return(destination) } + +# Decrypt email +decrypt_email <- function(query_email, encryption_key) { + ciphertext <- base64enc::base64decode(query_email) + email <- rawToChar(sodium::data_decrypt(ciphertext, encryption_key)) + return(email) +} diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index 5320ed1c7..4250c14f3 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1237,11 +1237,12 @@ EmailEncryptedAuthenticationModule <- function( ns <- session$ns # TODO add key and uncomment this code - # encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] - # if (!file.exists(crypto_key)) { - # ## we continue but email is not working - # warning("[EmailHeaderAuthenticationModule] ERROR : missing crypto_key file!!!") - # } + encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + if (!file.exists(encryption_key)) { + ## we continue without decryption, just to test, unsafe + warning("[EmailEncryptedAuthenticationModule] ERROR : missing encryption_key file!!!") + encryption_key <- NULL + } USER <- shiny::reactiveValues( method = "email-encrypted", @@ -1290,7 +1291,6 @@ EmailEncryptedAuthenticationModule <- function( ## -------------------------------------- query_email <- shiny::reactive({ - # aqui fer que pilli el header enlloc del query string query_email <- shiny::getQueryString()$email query_email }) @@ -1299,7 +1299,9 @@ EmailEncryptedAuthenticationModule <- function( ## Step 2: Decrypt email query field ## -------------------------------------- - # TODO decrypt email + if (!is.null(encryption_key)) { + query_email <- decrypt_mail(query_email, encryption_key) + } ## -------------------------------------- ## Step 3: Login user From 0003ae9f16c4fbf1a8c1918403bfe16101608ed2 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Thu, 21 Nov 2024 15:36:17 +0100 Subject: [PATCH 03/21] wip: decrypt url --- components/board.upload/R/upload_server.R | 13 +++++++++---- components/board.upload/R/upload_utils.R | 13 ++++++++----- components/modules/AuthenticationModule.R | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 92310214b..7efd3cb0e 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -978,9 +978,14 @@ UploadBoard <- function(id, compute_settings$description <- pgx$description } else if (opt$AUTHENTICATION == "email-encrypted") { # Only data preloading on email-encrypted authentication query_files <- check_query_files() - # TODO unencrypt links!!! to do inside check_query_files + # Get encryption key + encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + if (!file.exists(encryption_key)) { + encryption_key <- NULL + } + # Populate upload data with available if (!is.null(query_files$counts)) { - file <- read_query_files(query_files$counts) + file <- read_query_files(query_files$counts, encryption_key) df <- playbase::read_counts(file) uploaded$counts.csv <- df ## if counts file contains annotation @@ -988,12 +993,12 @@ UploadBoard <- function(id, uploaded$annot.csv <- af } if (!is.null(query_files$samples)) { - file <- read_query_files(query_files$samples) + file <- read_query_files(query_files$samples, encryption_key) df <- playbase::read_samples(file) uploaded$samples.csv <- df } if (!is.null(query_files$contrasts)) { - file <- read_query_files(query_files$contrasts) + file <- read_query_files(query_files$contrasts, encryption_key) df <- playbase::read_contrasts(file) uploaded$contrasts.csv <- df } diff --git a/components/board.upload/R/upload_utils.R b/components/board.upload/R/upload_utils.R index a140d1d52..3b70983ea 100644 --- a/components/board.upload/R/upload_utils.R +++ b/components/board.upload/R/upload_utils.R @@ -322,15 +322,18 @@ check_query_files <- function() { } # Read query files -read_query_files <- function(url) { +read_query_files <- function(url, encryption_key = NULL) { + if (!is.null(encryption_key)) { + url <- decrypt_util(url, encryption_key) + } destination <- tempfile(fileext = ".csv") download.file(url, destination) return(destination) } # Decrypt email -decrypt_email <- function(query_email, encryption_key) { - ciphertext <- base64enc::base64decode(query_email) - email <- rawToChar(sodium::data_decrypt(ciphertext, encryption_key)) - return(email) +decrypt_util <- function(query, encryption_key) { + ciphertext <- base64enc::base64decode(query) + decrypted_query <- rawToChar(sodium::data_decrypt(ciphertext, encryption_key)) + return(decrypted_query) } diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index 4250c14f3..1fd108f0b 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1236,7 +1236,7 @@ EmailEncryptedAuthenticationModule <- function( message("[AuthenticationModule] >>>> using EmailEncrypted authentication <<<<") ns <- session$ns - # TODO add key and uncomment this code + # Get encryption key encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] if (!file.exists(encryption_key)) { ## we continue without decryption, just to test, unsafe From d579b6b6af23d581eab99f73f355f7c6dd323f55 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Thu, 21 Nov 2024 16:15:19 +0100 Subject: [PATCH 04/21] wip: re-order stuff --- components/modules/AuthenticationModule.R | 30 +++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index 1fd108f0b..90f453bf7 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1237,11 +1237,12 @@ EmailEncryptedAuthenticationModule <- function( ns <- session$ns # Get encryption key - encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] - if (!file.exists(encryption_key)) { + encryption_key <- tryCatch({ + readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + }, error = function(w){NULL}) + if (is.null(encryption_key)) { ## we continue without decryption, just to test, unsafe warning("[EmailEncryptedAuthenticationModule] ERROR : missing encryption_key file!!!") - encryption_key <- NULL } USER <- shiny::reactiveValues( @@ -1296,25 +1297,22 @@ EmailEncryptedAuthenticationModule <- function( }) ## -------------------------------------- - ## Step 2: Decrypt email query field + ## Step 2: Decrypt and Login user ## -------------------------------------- - - if (!is.null(encryption_key)) { - query_email <- decrypt_mail(query_email, encryption_key) - } - - ## -------------------------------------- - ## Step 3: Login user - ## -------------------------------------- - # TODO place decrypted email instead of query_email shiny::observeEvent(input$login_submit_btn, { if (is.null(query_email())) { - dbg("[EmailEncryptedAuthenticationModule] invalid email") + dbg("[EmailEncryptedAuthenticationModule] invalid email hash") output$login_warning <- shiny::renderText("invalid email hash") # TODO show some error on the UI } else { - USER$email <- query_email() - USER$username <- query_email() + # Decrypt + if (!is.null(encryption_key)) { + query_email <- decrypt_util(query_email(), encryption_key) + } else { + query_email <- query_email() + } + USER$email <- query_email + USER$username <- query_email USER$logged <- TRUE # TODO Use some sort of user_database to see if email allowed?? # TODO check domain (?) From 8536905198147de2f4213828b7c5f634da5482b9 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Thu, 21 Nov 2024 16:17:06 +0100 Subject: [PATCH 05/21] add some info --- components/board.upload/R/upload_utils.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/board.upload/R/upload_utils.R b/components/board.upload/R/upload_utils.R index 3b70983ea..b89495d17 100644 --- a/components/board.upload/R/upload_utils.R +++ b/components/board.upload/R/upload_utils.R @@ -331,7 +331,7 @@ read_query_files <- function(url, encryption_key = NULL) { return(destination) } -# Decrypt email +# Decrypt utility, used in email-encrypted authentication + population of data upload (only on that auth) decrypt_util <- function(query, encryption_key) { ciphertext <- base64enc::base64decode(query) decrypted_query <- rawToChar(sodium::data_decrypt(ciphertext, encryption_key)) From bc640e9298c1b8ad2368d830422429397f4f9e33 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Fri, 13 Dec 2024 11:44:50 +0100 Subject: [PATCH 06/21] feat: startup modal as an OPTION --- components/app/R/global.R | 1 + components/app/R/server.R | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/app/R/global.R b/components/app/R/global.R index d0f8af61b..d74e55c64 100644 --- a/components/app/R/global.R +++ b/components/app/R/global.R @@ -169,6 +169,7 @@ opt.default <- list( ENABLE_HEARTBEAT = TRUE, ENABLE_INACTIVITY = TRUE, ENABLE_ANNOT = FALSE, + ENABLE_STARTUP_MODAL = TRUE, MAX_DATASETS = 25, MAX_SAMPLES = 1000, MAX_COMPARISONS = 20, diff --git a/components/app/R/server.R b/components/app/R/server.R index c05be43ca..a066f68eb 100644 --- a/components/app/R/server.R +++ b/components/app/R/server.R @@ -1221,7 +1221,7 @@ app_server <- function(input, output, session) { ## Startup Message dbg("[MAIN] showing startup modal") observeEvent(auth$logged, { - if (auth$logged) { + if (auth$logged && opt$ENABLE_STARTUP_MODAL) { shinyjs::delay(500, { ## read startup messages msg <- readLines(file.path(ETC, "MESSAGES")) From 2661e7ec4136cced3fe4263838ba38a85c6d00ee Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Fri, 13 Dec 2024 11:45:17 +0100 Subject: [PATCH 07/21] feat: redirect to upload/data --- components/modules/AuthenticationModule.R | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index 90f453bf7..ad8f721e4 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1324,6 +1324,14 @@ EmailEncryptedAuthenticationModule <- function( dbg("[EmailEncryptedAuthenticationModule] using user OPTIONS") USER$options <- read_user_options(USER$user_dir) session$sendCustomMessage("set-user", list(user = USER$email)) + # If data is sent, trigger upload, if not, go to datasets + query_files <- check_query_files() |> unlist() + if (!is.null(query_files)) { + bigdash.selectTab(session, "upload-tab") + shinyjs::runjs("$('#upload-start_upload').click();") + } else { + bigdash.selectTab(session, "load-tab") + } } }) From 13775777b4e4ea65cf16a45f930c43f4b78598e9 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Fri, 13 Dec 2024 14:31:10 +0100 Subject: [PATCH 08/21] feat: datatype and organism on url --- components/board.upload/R/upload_server.R | 28 +++++++++++++++++++---- components/board.upload/R/upload_utils.R | 7 +++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 7efd3cb0e..468c0def0 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -684,11 +684,31 @@ UploadBoard <- function(id, # change upload_datatype to selected_datatype observeEvent(input$selected_datatype, { upload_datatype(input$selected_datatype) + query_files <- check_query_files() + if (!is.null(query_files$datatype)) { + # Get encryption key + encryption_key <- NULL# readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + # if (!file.exists(encryption_key)) { + # encryption_key <- NULL + # } + datatype <- decrypt_util(query_files$datatype, encryption_key) + upload_datatype(datatype) + } }) # change upload_organism to selected_organism observeEvent(input$selected_organism, { upload_organism(input$selected_organism) + query_files <- check_query_files() + if (!is.null(query_files$organism)) { + # Get encryption key + encryption_key <- NULL# readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + # if (!file.exists(encryption_key)) { + # encryption_key <- NULL + # } + organism <- decrypt_util(query_files$organism, encryption_key) + upload_organism(organism) + } }) observeEvent(input$start_upload, { @@ -979,10 +999,10 @@ UploadBoard <- function(id, } else if (opt$AUTHENTICATION == "email-encrypted") { # Only data preloading on email-encrypted authentication query_files <- check_query_files() # Get encryption key - encryption_key <- readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] - if (!file.exists(encryption_key)) { - encryption_key <- NULL - } + encryption_key <- NULL# readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + # if (!file.exists(encryption_key)) { + # encryption_key <- NULL + # } # Populate upload data with available if (!is.null(query_files$counts)) { file <- read_query_files(query_files$counts, encryption_key) diff --git a/components/board.upload/R/upload_utils.R b/components/board.upload/R/upload_utils.R index b89495d17..6cef01859 100644 --- a/components/board.upload/R/upload_utils.R +++ b/components/board.upload/R/upload_utils.R @@ -314,10 +314,14 @@ check_query_files <- function() { counts <- shiny::getQueryString()$counts samples <- shiny::getQueryString()$samples contrasts <- shiny::getQueryString()$contrasts + datatype <- shiny::getQueryString()$datatype + organism <- shiny::getQueryString()$organism return(list( counts = counts, samples = samples, - contrasts = contrasts + contrasts = contrasts, + datatype = datatype, + organism = organism )) } @@ -333,6 +337,7 @@ read_query_files <- function(url, encryption_key = NULL) { # Decrypt utility, used in email-encrypted authentication + population of data upload (only on that auth) decrypt_util <- function(query, encryption_key) { + if (is.null(encryption_key)) return(query) ciphertext <- base64enc::base64decode(query) decrypted_query <- rawToChar(sodium::data_decrypt(ciphertext, encryption_key)) return(decrypted_query) From 9528b5fac20dacc5e51073d1799935b5f69a24ea Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Tue, 17 Dec 2024 15:50:09 +0100 Subject: [PATCH 09/21] second dictionary for integration --- components/app/R/global.R | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/app/R/global.R b/components/app/R/global.R index d74e55c64..0b568586d 100644 --- a/components/app/R/global.R +++ b/components/app/R/global.R @@ -170,6 +170,7 @@ opt.default <- list( ENABLE_INACTIVITY = TRUE, ENABLE_ANNOT = FALSE, ENABLE_STARTUP_MODAL = TRUE, + INTEGRATION_TRANSLATION = FALSE, MAX_DATASETS = 25, MAX_SAMPLES = 1000, MAX_COMPARISONS = 20, @@ -277,3 +278,10 @@ library(shiny.i18n) DICTIONARY <- file.path(FILES, "translation.json") i18n <- shiny.i18n::Translator$new(translation_json_path = DICTIONARY) i18n$set_translation_language("RNA-seq") + +## Init integration translator +if (opt$INTEGRATION_TRANSLATION) { + DICTIONARY2 <- file.path(FILES, "translation_integration.json") + i18n_int <- shiny.i18n::Translator$new(translation_json_path = DICTIONARY2) + i18n_int$set_translation_language("integration") +} From 045a7aaf19434d83361b38bc0221dd458651465e Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Tue, 17 Dec 2024 15:50:15 +0100 Subject: [PATCH 10/21] add tspan to steps --- components/board.upload/R/upload_server.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 468c0def0..148b92286 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -522,7 +522,7 @@ UploadBoard <- function(id, ) samples_ui <- wizardR::wizard_step( - step_title = "Step 2: Upload samples", + step_title = tspan("Step 2: Upload samples", js = FALSE), step_id = "step_samples", server = TRUE, upload_table_preview_samples_ui( @@ -531,7 +531,7 @@ UploadBoard <- function(id, ) contrasts_ui <- wizardR::wizard_step( - step_title = "Step 3: Create comparisons", + step_title = tspan("Step 3: Create comparisons", js = FALSE), step_id = "step_comparisons", server = TRUE, upload_table_preview_contrasts_ui( @@ -540,7 +540,7 @@ UploadBoard <- function(id, ) normalization_panel <- wizardR::wizard_step( - step_title = "Step 4: QC/BC", + step_title = tspan("Step 4: QC/BC", js = FALSE), step_id = "step_qc", server = TRUE, upload_module_normalization_ui(ns("checkqc")) From f0b8fb00c708d84cd23bd919a66599561ba9ecd7 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Tue, 17 Dec 2024 15:50:52 +0100 Subject: [PATCH 11/21] new translation on tspan just available when integration_translation is true --- components/ui/ui-utils.R | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/ui/ui-utils.R b/components/ui/ui-utils.R index 790b2e678..22456e70e 100644 --- a/components/ui/ui-utils.R +++ b/components/ui/ui-utils.R @@ -457,7 +457,7 @@ tspan <- function(text, js = TRUE) { if (nchar(text) == 0) { return("") } - if (!grepl("gene|counts|transcriptomics|rna-seq|logcpm", + if (!grepl("gene|counts|transcriptomics|rna-seq|logcpm|step", text, ignore.case = TRUE )) { @@ -481,6 +481,18 @@ tspan <- function(text, js = TRUE) { text <- paste0("", text, "") text <- shiny::HTML(text) } + + # Integration translator (does not accept JS TRUE) + if (opt$INTEGRATION_TRANSLATION) { + keys <- c( + "Step" + ) + i18n.tr <- function(key) as.character(i18n_int$get_translations()[key, "integration"]) + for (k in keys) { + tt <- i18n.tr(k) + if (grepl(k, text)) text <- gsub(k, tt, text, ignore.case = FALSE) + } + } text } From d9aeb99de301db02e069cd778d2e5b1d923a1bfb Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Mon, 23 Dec 2024 11:56:50 +0100 Subject: [PATCH 12/21] feat: skip modal and log in user --- components/app/R/server.R | 1 + components/modules/AuthenticationModule.R | 3 +++ 2 files changed, 4 insertions(+) diff --git a/components/app/R/server.R b/components/app/R/server.R index a066f68eb..4ef741c3b 100644 --- a/components/app/R/server.R +++ b/components/app/R/server.R @@ -121,6 +121,7 @@ app_server <- function(input, output, session) { auth <- EmailEncryptedAuthenticationModule( id = "auth", show_modal = TRUE, + skip_modal = TRUE, # TODO add argument for location of crypto key (probably on ETC) domain = opt$DOMAIN ) diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index ad8f721e4..45756c7b5 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1267,6 +1267,9 @@ EmailEncryptedAuthenticationModule <- function( button.text = "Sure I am!" ) shiny::showModal(m) + if (skip_modal) { + shinyjs::runjs("$('#auth-login_submit_btn').click();") + } } resetUSER <- function() { From d7fe73b0ed4c8dbfd87e6bcc2aa98c0e5f81517a Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Mon, 23 Dec 2024 12:43:09 +0100 Subject: [PATCH 13/21] fix: correct translation --- components/board.upload/R/upload_server.R | 4 ++-- components/ui/ui-utils.R | 2 +- lib/translation_integration.json | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 lib/translation_integration.json diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 148b92286..8a333a0ae 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -531,7 +531,7 @@ UploadBoard <- function(id, ) contrasts_ui <- wizardR::wizard_step( - step_title = tspan("Step 3: Create comparisons", js = FALSE), + step_title = "Step 3: Create comparisons", step_id = "step_comparisons", server = TRUE, upload_table_preview_contrasts_ui( @@ -540,7 +540,7 @@ UploadBoard <- function(id, ) normalization_panel <- wizardR::wizard_step( - step_title = tspan("Step 4: QC/BC", js = FALSE), + step_title = "Step 4: QC/BC", step_id = "step_qc", server = TRUE, upload_module_normalization_ui(ns("checkqc")) diff --git a/components/ui/ui-utils.R b/components/ui/ui-utils.R index 22456e70e..c88c06b9e 100644 --- a/components/ui/ui-utils.R +++ b/components/ui/ui-utils.R @@ -485,7 +485,7 @@ tspan <- function(text, js = TRUE) { # Integration translator (does not accept JS TRUE) if (opt$INTEGRATION_TRANSLATION) { keys <- c( - "Step" + "Upload" ) i18n.tr <- function(key) as.character(i18n_int$get_translations()[key, "integration"]) for (k in keys) { diff --git a/lib/translation_integration.json b/lib/translation_integration.json new file mode 100644 index 000000000..2c1bb6532 --- /dev/null +++ b/lib/translation_integration.json @@ -0,0 +1,13 @@ +{ + "cultural_date_format": "%d-%m-%Y", + "languages": [ + "default", + "integration" + ], + "translation": [ + { + "default": "Upload", + "integration": "Review" + } + ] +} From d3bf5b4d793bc88353a4944fb1779e6bba53afe1 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Thu, 9 Jan 2025 12:19:57 +0100 Subject: [PATCH 14/21] fix: add missing argument `skip_modal` --- components/modules/AuthenticationModule.R | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index 45756c7b5..cd98922f0 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1228,18 +1228,24 @@ LoginCodeAuthenticationModule <- function(id, EmailEncryptedAuthenticationModule <- function( - id = "auth", - show_modal = TRUE, - # TODO add argument for location of crypto key (probably on ETC) - domain = opt$DOMAIN) { + id = "auth", + show_modal = TRUE, + skip_modal = FALSE, + # TODO add argument for location of crypto key (probably on ETC) + domain = opt$DOMAIN) { shiny::moduleServer(id, function(input, output, session) { message("[AuthenticationModule] >>>> using EmailEncrypted authentication <<<<") ns <- session$ns # Get encryption key - encryption_key <- tryCatch({ - readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] - }, error = function(w){NULL}) + encryption_key <- tryCatch( + { + readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + }, + error = function(w) { + NULL + } + ) if (is.null(encryption_key)) { ## we continue without decryption, just to test, unsafe warning("[EmailEncryptedAuthenticationModule] ERROR : missing encryption_key file!!!") From 92bc19b7e6eb7077d3448870ebdb0132d0ab23b0 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Mon, 13 Jan 2025 17:51:00 +0100 Subject: [PATCH 15/21] feat: decryption --- components/board.upload/R/upload_server.R | 60 +++++++- components/board.upload/R/upload_utils.R | 15 +- components/modules/AuthenticationModule.R | 74 +++++---- .../modules/AuthenticationModule_functions.R | 143 +++++++++++------- 4 files changed, 190 insertions(+), 102 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 8a333a0ae..80f6649ca 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -687,7 +687,15 @@ UploadBoard <- function(id, query_files <- check_query_files() if (!is.null(query_files$datatype)) { # Get encryption key - encryption_key <- NULL# readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + encryption_key <- tryCatch( + { + readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + }, + error = function(w) { + warning("[UploadServer] ERROR: missing encryption_key file!!!") + NULL + } + ) # if (!file.exists(encryption_key)) { # encryption_key <- NULL # } @@ -702,7 +710,15 @@ UploadBoard <- function(id, query_files <- check_query_files() if (!is.null(query_files$organism)) { # Get encryption key - encryption_key <- NULL# readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + encryption_key <- tryCatch( + { + readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + }, + error = function(w) { + warning("[UploadServer] ERROR: missing encryption_key file!!!") + NULL + } + ) # if (!file.exists(encryption_key)) { # encryption_key <- NULL # } @@ -999,13 +1015,29 @@ UploadBoard <- function(id, } else if (opt$AUTHENTICATION == "email-encrypted") { # Only data preloading on email-encrypted authentication query_files <- check_query_files() # Get encryption key - encryption_key <- NULL# readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + encryption_key <- tryCatch( + { + readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] + }, + error = function(w) { + warning("[UploadServer] ERROR: missing encryption_key file!!!") + NULL + } + ) # if (!file.exists(encryption_key)) { # encryption_key <- NULL # } # Populate upload data with available if (!is.null(query_files$counts)) { - file <- read_query_files(query_files$counts, encryption_key) + # Decrypt the URL if encryption key is available + counts_url <- if (!is.null(encryption_key)) { + decrypt_util(query_files$counts, encryption_key) + } else { + warning("[UploadServer] No encryption key available, using raw URL") + query_files$counts + } + + file <- read_query_files(counts_url) df <- playbase::read_counts(file) uploaded$counts.csv <- df ## if counts file contains annotation @@ -1013,12 +1045,28 @@ UploadBoard <- function(id, uploaded$annot.csv <- af } if (!is.null(query_files$samples)) { - file <- read_query_files(query_files$samples, encryption_key) + # Decrypt the URL if encryption key is available + samples_url <- if (!is.null(encryption_key)) { + decrypt_util(query_files$samples, encryption_key) + } else { + warning("[UploadServer] No encryption key available, using raw URL") + query_files$samples + } + + file <- read_query_files(samples_url) df <- playbase::read_samples(file) uploaded$samples.csv <- df } if (!is.null(query_files$contrasts)) { - file <- read_query_files(query_files$contrasts, encryption_key) + # Decrypt the URL if encryption key is available + contrasts_url <- if (!is.null(encryption_key)) { + decrypt_util(query_files$contrasts, encryption_key) + } else { + warning("[UploadServer] No encryption key available, using raw URL") + query_files$contrasts + } + + file <- read_query_files(contrasts_url) df <- playbase::read_contrasts(file) uploaded$contrasts.csv <- df } diff --git a/components/board.upload/R/upload_utils.R b/components/board.upload/R/upload_utils.R index 6cef01859..f01356a32 100644 --- a/components/board.upload/R/upload_utils.R +++ b/components/board.upload/R/upload_utils.R @@ -326,19 +326,8 @@ check_query_files <- function() { } # Read query files -read_query_files <- function(url, encryption_key = NULL) { - if (!is.null(encryption_key)) { - url <- decrypt_util(url, encryption_key) - } +read_query_files <- function(url) { destination <- tempfile(fileext = ".csv") download.file(url, destination) return(destination) -} - -# Decrypt utility, used in email-encrypted authentication + population of data upload (only on that auth) -decrypt_util <- function(query, encryption_key) { - if (is.null(encryption_key)) return(query) - ciphertext <- base64enc::base64decode(query) - decrypted_query <- rawToChar(sodium::data_decrypt(ciphertext, encryption_key)) - return(decrypted_query) -} +} \ No newline at end of file diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index cd98922f0..63a91997c 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1226,12 +1226,14 @@ LoginCodeAuthenticationModule <- function(id, }) } +## ================================================================================ +## Utility functions for authentication +## ================================================================================ EmailEncryptedAuthenticationModule <- function( id = "auth", show_modal = TRUE, skip_modal = FALSE, - # TODO add argument for location of crypto key (probably on ETC) domain = opt$DOMAIN) { shiny::moduleServer(id, function(input, output, session) { message("[AuthenticationModule] >>>> using EmailEncrypted authentication <<<<") @@ -1312,35 +1314,49 @@ EmailEncryptedAuthenticationModule <- function( if (is.null(query_email())) { dbg("[EmailEncryptedAuthenticationModule] invalid email hash") output$login_warning <- shiny::renderText("invalid email hash") - # TODO show some error on the UI + return(NULL) + } + + # Decrypt email if encryption key is available + decrypted_email <- if (!is.null(encryption_key)) { + decrypt_util(query_email(), encryption_key) } else { - # Decrypt - if (!is.null(encryption_key)) { - query_email <- decrypt_util(query_email(), encryption_key) - } else { - query_email <- query_email() - } - USER$email <- query_email - USER$username <- query_email - USER$logged <- TRUE - # TODO Use some sort of user_database to see if email allowed?? - # TODO check domain (?) - USER$user_dir <- file.path(PGX.DIR, USER$email) - create_user_dir_if_needed(USER$user_dir, PGX.DIR) - if (!opt$ENABLE_USERDIR) { - USER$user_dir <- file.path(PGX.DIR) - } - dbg("[EmailEncryptedAuthenticationModule] using user OPTIONS") - USER$options <- read_user_options(USER$user_dir) - session$sendCustomMessage("set-user", list(user = USER$email)) - # If data is sent, trigger upload, if not, go to datasets - query_files <- check_query_files() |> unlist() - if (!is.null(query_files)) { - bigdash.selectTab(session, "upload-tab") - shinyjs::runjs("$('#upload-start_upload').click();") - } else { - bigdash.selectTab(session, "load-tab") - } + warning("[EmailEncryptedAuthenticationModule] No encryption key available, using raw email") + query_email() + } + + # Validate decrypted email + if (is.null(decrypted_email)) { + dbg("[EmailEncryptedAuthenticationModule] failed to decrypt email") + output$login_warning <- shiny::renderText("invalid email format") + return(NULL) + } + + USER$email <- decrypted_email + USER$username <- decrypted_email + USER$logged <- TRUE + + # TODO Use some sort of user_database to see if email allowed?? + # TODO check domain (?) + + # Set up user directory + USER$user_dir <- file.path(PGX.DIR, USER$email) + create_user_dir_if_needed(USER$user_dir, PGX.DIR) + if (!opt$ENABLE_USERDIR) { + USER$user_dir <- file.path(PGX.DIR) + } + + dbg("[EmailEncryptedAuthenticationModule] using user OPTIONS") + USER$options <- read_user_options(USER$user_dir) + session$sendCustomMessage("set-user", list(user = USER$email)) + + # Handle query files and navigation + query_files <- check_query_files() |> unlist() + if (!is.null(query_files)) { + bigdash.selectTab(session, "upload-tab") + shinyjs::runjs("$('#upload-start_upload').click();") + } else { + bigdash.selectTab(session, "load-tab") } }) diff --git a/components/modules/AuthenticationModule_functions.R b/components/modules/AuthenticationModule_functions.R index be54cc9c4..9d0ec4bc8 100644 --- a/components/modules/AuthenticationModule_functions.R +++ b/components/modules/AuthenticationModule_functions.R @@ -18,7 +18,7 @@ check_user_options_db <- function(email, user_database = NULL) { if (is.null(user_database)) { return(FALSE) } - connection <- connect_db(user_database) + connection <- DBI::dbConnect(RSQLite::SQLite(), dbname = user_database) user_opt <- query_by_email(email, connection) if (is.null(user_opt)) { return(FALSE) # user NOT in db @@ -47,16 +47,14 @@ read_user_options <- function(user_dir) { new_opt[[opt_name]] <- user_opt[[opt_name]] } } - # add user dir to opt file (IK: this is not an option!) - ## new_opt$user_dir <- user_dir new_opt } read_user_options_db <- function(email, user_database = NULL) { - connection <- connect_db(user_database) + connection <- DBI::dbConnect(RSQLite::SQLite(), dbname = user_database) user_opt <- query_by_email(email, connection) new_opt <- opt ## opt from global - disconnect_db(connection) + DBI::dbDisconnect(connection) if (!is.null(user_opt)) { ## restrict user options only to these options. ALLOWED_USER_OPTS <- c( @@ -93,71 +91,71 @@ upgrade.dialog <- function(ns, current.plan) { if (current.plan == "starter") btn_starter <- "Current Plan" if (current.plan == "premium") btn_premium <- "Current Plan" - modalDialog( - title = h3("Find the right OmicsPlayground plan for you"), + shiny::modalDialog( + title = shiny::h3("Find the right OmicsPlayground plan for you"), size = "m", - div( + shiny::div( class = "row", style = "padding-left:4rem;padding-right:4rem;text-align:center;", - div( + shiny::div( class = "col-md-4", style = "background:#F2FAFF;", - HTML("

Basic

"), - p("Try for free"), - h3("Free!"), - tags$ul( + shiny::HTML("

Basic

"), + shiny::p("Try for free"), + shiny::h3("Free!"), + shiny::tags$ul( class = "list-unstyled", - tags$li("Host up to 3 datasets"), - tags$li("45 minutes time limit"), - tags$li("Up to 25 samples / dataset"), - tags$li("Up to 5 comparisons") + shiny::tags$li("Host up to 3 datasets"), + shiny::tags$li("45 minutes time limit"), + shiny::tags$li("Up to 25 samples / dataset"), + shiny::tags$li("Up to 5 comparisons") ), shiny::actionButton(ns("get_basic"), btn_basic), - br() + shiny::br() ), - div( + shiny::div( class = "col-md-4", style = "background:#E8F8FF;", - h4(HTML("Starter")), - p("Great to start"), - h3("Soon!"), - tags$ul( + shiny::h4(shiny::HTML("Starter")), + shiny::p("Great to start"), + shiny::h3("Soon!"), + shiny::tags$ul( class = "list-unstyled", - tags$li("Host up to 10 datasets"), - tags$li("3 hours time limit"), - tags$li("Up to 100 samples / dataset"), - tags$li("Up to 10 comparisons") + shiny::tags$li("Host up to 10 datasets"), + shiny::tags$li("3 hours time limit"), + shiny::tags$li("Up to 100 samples / dataset"), + shiny::tags$li("Up to 10 comparisons") ), shiny::actionButton(ns("get_starter"), btn_starter), - br() + shiny::br() ), - div( + shiny::div( class = "col-md-4", style = "background:#E2F4FF;", - HTML("

Premium

"), - p("For power users or small groups"), - h3("Soon!"), - tags$ul( + shiny::HTML("

Premium

"), + shiny::p("For power users or small groups"), + shiny::h3("Soon!"), + shiny::tags$ul( class = "list-unstyled", - tags$li("Host up to 100 datasets"), - tags$li("8 hours time limit"), - tags$li("Up to 2000 samples / dataset"), - tags$li("Up to 100 comparisons") + shiny::tags$li("Host up to 100 datasets"), + shiny::tags$li("8 hours time limit"), + shiny::tags$li("Up to 2000 samples / dataset"), + shiny::tags$li("Up to 100 comparisons") ), shiny::actionButton(ns("get_premium"), btn_premium), - br() + shiny::br() ) ), ## content div - div( + shiny::div( style = "margin-top:3rem;text-align:center;", - HTML("Looking for OmicsPlayground for Enterprise? Contact sales for info and pricing.") + shiny::HTML("Looking for OmicsPlayground for Enterprise? Contact sales for info and pricing.") ), - footer = tagList( - fillRow( + footer = shiny::tagList( + shiny::fillRow( flex = c(NA, 0.03, NA, 1, NA, NA), - tags$label( + shiny::tags$label( class = "radio-inline", - tags$input( + shiny::tags$input( id = "yearlyCheck", type = "radio", name = "yearly", @@ -166,10 +164,10 @@ upgrade.dialog <- function(ns, current.plan) { ), "Billed yearly" ), - br(), - tags$label( + shiny::br(), + shiny::tags$label( class = "radio-inline", - tags$input( + shiny::tags$input( id = "monthlyCheck", type = "radio", name = "monthly", @@ -177,9 +175,9 @@ upgrade.dialog <- function(ns, current.plan) { ), "Billed monthly" ), - br(), + shiny::br(), shiny::actionButton(ns("manage"), "Manage Subscription"), - modalButton("Dismiss") + shiny::modalButton("Dismiss") ) ) ) ## modalDialog @@ -227,7 +225,7 @@ checkAuthorizedUser <- function(email, credentials_file = NULL) { if (!file.exists(credentials_file)) { return(TRUE) } - CREDENTIALS <- read.csv(credentials_file, colClasses = "character") + CREDENTIALS <- utils::read.csv(credentials_file, colClasses = "character") valid_user <- tolower(email) %in% tolower(CREDENTIALS$email) if (!valid_user) { return(FALSE) @@ -245,15 +243,13 @@ checkExpiredUser <- function(email, user_database) { if (!file.exists(user_database)) { return(TRUE) } - connection <- connect_db(user_database) - # connection <- DBI::dbConnect(RSQLite::SQLite(), dbname = user_database) + connection <- DBI::dbConnect(RSQLite::SQLite(), dbname = user_database) query_result <- DBI::dbGetQuery(connection, paste0(" SELECT expiry FROM users WHERE email = '", email, "' ")) - # DBI::dbDisconnect(connection) - disconnect_db(connection) + DBI::dbDisconnect(connection) if (nrow(query_result) == 0) { return(TRUE) } else { @@ -332,6 +328,45 @@ chueckHubspotData <- function(user_email) { } } +## ----------------------------------------------------------------- +## Encryption/Decryption functions +## ----------------------------------------------------------------- + +#' Decrypt text using AES-256-CBC +#' @param encrypted_text The encrypted text to decrypt +#' @param key The encryption key as a string +#' @return The decrypted text +decrypt_util <- function(encrypted_text, key) { + if (is.null(encrypted_text) || is.null(key)) return(NULL) + + # Convert key to raw bytes + key_raw <- charToRaw(key) + + # URL decode the encrypted text + encrypted_text <- utils::URLdecode(encrypted_text) + + # Split the IV and encrypted data + parts <- strsplit(encrypted_text, ":")[[1]] + if (length(parts) != 2) return(NULL) + + # Decode base64 components + iv <- openssl::base64_decode(parts[1]) + encrypted_data <- openssl::base64_decode(parts[2]) + + # Decrypt using AES-256-CBC + tryCatch({ + decrypted <- openssl::aes_cbc_decrypt( + encrypted_data, + key = key_raw, + iv = iv + ) + rawToChar(decrypted) + }, error = function(e) { + warning("[decrypt_util] Decryption failed: ", e$message) + NULL + }) +} + ## ================================================================================ ## ================================= END OF FILE ================================== ## ================================================================================ From dc870369387f9bc1c7f1b18992ffed0ba245f631 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Wed, 15 Jan 2025 09:45:48 +0100 Subject: [PATCH 16/21] feat: datatype and organism not encrypted --- components/board.upload/R/upload_server.R | 30 ++--------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 80f6649ca..98a2dc431 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -686,20 +686,7 @@ UploadBoard <- function(id, upload_datatype(input$selected_datatype) query_files <- check_query_files() if (!is.null(query_files$datatype)) { - # Get encryption key - encryption_key <- tryCatch( - { - readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] - }, - error = function(w) { - warning("[UploadServer] ERROR: missing encryption_key file!!!") - NULL - } - ) - # if (!file.exists(encryption_key)) { - # encryption_key <- NULL - # } - datatype <- decrypt_util(query_files$datatype, encryption_key) + datatype <- query_files$datatype upload_datatype(datatype) } }) @@ -709,20 +696,7 @@ UploadBoard <- function(id, upload_organism(input$selected_organism) query_files <- check_query_files() if (!is.null(query_files$organism)) { - # Get encryption key - encryption_key <- tryCatch( - { - readLines(file.path(OPG, "etc/keys/encryption.txt"))[1] - }, - error = function(w) { - warning("[UploadServer] ERROR: missing encryption_key file!!!") - NULL - } - ) - # if (!file.exists(encryption_key)) { - # encryption_key <- NULL - # } - organism <- decrypt_util(query_files$organism, encryption_key) + organism <- query_files$organism upload_organism(organism) } }) From 3a1ab6d2551739ab4b46f5fa4b3dcbe1a294531a Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Tue, 21 Jan 2025 14:01:33 +0100 Subject: [PATCH 17/21] feat: remove trailin 8 characters from email decryption --- components/modules/AuthenticationModule.R | 2 +- components/modules/AuthenticationModule_functions.R | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/modules/AuthenticationModule.R b/components/modules/AuthenticationModule.R index 63a91997c..a26505024 100644 --- a/components/modules/AuthenticationModule.R +++ b/components/modules/AuthenticationModule.R @@ -1319,7 +1319,7 @@ EmailEncryptedAuthenticationModule <- function( # Decrypt email if encryption key is available decrypted_email <- if (!is.null(encryption_key)) { - decrypt_util(query_email(), encryption_key) + decrypt_util(query_email(), encryption_key, remove_suffix = TRUE) } else { warning("[EmailEncryptedAuthenticationModule] No encryption key available, using raw email") query_email() diff --git a/components/modules/AuthenticationModule_functions.R b/components/modules/AuthenticationModule_functions.R index 9d0ec4bc8..3877f0ca6 100644 --- a/components/modules/AuthenticationModule_functions.R +++ b/components/modules/AuthenticationModule_functions.R @@ -336,7 +336,7 @@ chueckHubspotData <- function(user_email) { #' @param encrypted_text The encrypted text to decrypt #' @param key The encryption key as a string #' @return The decrypted text -decrypt_util <- function(encrypted_text, key) { +decrypt_util <- function(encrypted_text, key, remove_suffix = FALSE) { if (is.null(encrypted_text) || is.null(key)) return(NULL) # Convert key to raw bytes @@ -360,7 +360,13 @@ decrypt_util <- function(encrypted_text, key) { key = key_raw, iv = iv ) - rawToChar(decrypted) + decrypted_text <- rawToChar(decrypted) + + if (remove_suffix) { + decrypted_text <- substr(decrypted_text, 1, nchar(decrypted_text) - 8) + } + + decrypted_text }, error = function(e) { warning("[decrypt_util] Decryption failed: ", e$message) NULL From 05d0086240810226912fa65a75fb2f69ea0a7f48 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Tue, 21 Jan 2025 14:15:38 +0100 Subject: [PATCH 18/21] feat: blacklist email suffix --- components/modules/AuthenticationModule_functions.R | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/modules/AuthenticationModule_functions.R b/components/modules/AuthenticationModule_functions.R index 3877f0ca6..a2bebdc96 100644 --- a/components/modules/AuthenticationModule_functions.R +++ b/components/modules/AuthenticationModule_functions.R @@ -363,9 +363,20 @@ decrypt_util <- function(encrypted_text, key, remove_suffix = FALSE) { decrypted_text <- rawToChar(decrypted) if (remove_suffix) { + BLACKLIST_FILE <- file.path(ETC, "blacklisted_suffixes.txt") + suffix <- substr(decrypted_text, nchar(decrypted_text) - 7, nchar(decrypted_text)) + if (!file.exists(BLACKLIST_FILE)) { + dir.create(dirname(BLACKLIST_FILE), recursive = TRUE, showWarnings = FALSE) + file.create(BLACKLIST_FILE) + } + if (system(paste("grep -q", shQuote(suffix), shQuote(BLACKLIST_FILE)), + ignore.stdout = TRUE, ignore.stderr = TRUE) == 0) { + warning("[decrypt_util] Blacklisted suffix detected") + return(NULL) + } decrypted_text <- substr(decrypted_text, 1, nchar(decrypted_text) - 8) + write(suffix, file = BLACKLIST_FILE, append = TRUE) } - decrypted_text }, error = function(e) { warning("[decrypt_util] Decryption failed: ", e$message) From c6ecb3d93888e2ee72b6ae464cf98acc82eecea3 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Tue, 21 Jan 2025 17:33:11 +0100 Subject: [PATCH 19/21] feat: do not populate on second upload --- components/board.upload/R/upload_server.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 98a2dc431..db419163a 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -685,7 +685,7 @@ UploadBoard <- function(id, observeEvent(input$selected_datatype, { upload_datatype(input$selected_datatype) query_files <- check_query_files() - if (!is.null(query_files$datatype)) { + if (!is.null(query_files$datatype) && new_upload() == 2) { datatype <- query_files$datatype upload_datatype(datatype) } @@ -695,7 +695,7 @@ UploadBoard <- function(id, observeEvent(input$selected_organism, { upload_organism(input$selected_organism) query_files <- check_query_files() - if (!is.null(query_files$organism)) { + if (!is.null(query_files$organism) && new_upload() == 2) { organism <- query_files$organism upload_organism(organism) } @@ -1002,7 +1002,7 @@ UploadBoard <- function(id, # encryption_key <- NULL # } # Populate upload data with available - if (!is.null(query_files$counts)) { + if (!is.null(query_files$counts) && new_upload() == 2) { # Decrypt the URL if encryption key is available counts_url <- if (!is.null(encryption_key)) { decrypt_util(query_files$counts, encryption_key) @@ -1018,8 +1018,8 @@ UploadBoard <- function(id, af <- playbase::read_annot(file) uploaded$annot.csv <- af } - if (!is.null(query_files$samples)) { - # Decrypt the URL if encryption key is available + if (!is.null(query_files$samples) && new_upload() == 2) { + # Decrypt the URL if encryption key is available samples_url <- if (!is.null(encryption_key)) { decrypt_util(query_files$samples, encryption_key) } else { @@ -1031,7 +1031,7 @@ UploadBoard <- function(id, df <- playbase::read_samples(file) uploaded$samples.csv <- df } - if (!is.null(query_files$contrasts)) { + if (!is.null(query_files$contrasts) && new_upload() == 2) { # Decrypt the URL if encryption key is available contrasts_url <- if (!is.null(encryption_key)) { decrypt_util(query_files$contrasts, encryption_key) From b4371496d9197f00d68999e70f7b5a601b275265 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Wed, 22 Jan 2025 10:01:05 +0100 Subject: [PATCH 20/21] wip --- components/board.upload/R/upload_server.R | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index db419163a..067dea5c9 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -1003,7 +1003,6 @@ UploadBoard <- function(id, # } # Populate upload data with available if (!is.null(query_files$counts) && new_upload() == 2) { - # Decrypt the URL if encryption key is available counts_url <- if (!is.null(encryption_key)) { decrypt_util(query_files$counts, encryption_key) } else { @@ -1011,12 +1010,21 @@ UploadBoard <- function(id, query_files$counts } - file <- read_query_files(counts_url) - df <- playbase::read_counts(file) - uploaded$counts.csv <- df - ## if counts file contains annotation - af <- playbase::read_annot(file) - uploaded$annot.csv <- af + future_promise({ + file <- read_query_files(counts_url) + df <- playbase::read_counts(file) + af <- playbase::read_annot(file) + list(counts = df, annot = af) + }) %...>% + (function(result) { + message("[UploadServer] Future completed successfully") + uploaded$counts.csv <- result$counts + uploaded$annot.csv <- result$annot + }) %...!% + (function(error) { + message("[UploadServer] Future failed with error: ", error$message) + warning("[UploadServer] Error processing counts file: ", error) + }) } if (!is.null(query_files$samples) && new_upload() == 2) { # Decrypt the URL if encryption key is available From 2bf5d142b958426acba67aab157504ae87d036e3 Mon Sep 17 00:00:00 2001 From: ESCRI11 Date: Wed, 22 Jan 2025 11:00:31 +0100 Subject: [PATCH 21/21] feat: async+loader while data is not there --- .../R/upload_module_preview_counts.R | 16 ++++++++++++++++ components/board.upload/R/upload_server.R | 3 +++ 2 files changed, 19 insertions(+) diff --git a/components/board.upload/R/upload_module_preview_counts.R b/components/board.upload/R/upload_module_preview_counts.R index 4ff9b8384..d976cd787 100644 --- a/components/board.upload/R/upload_module_preview_counts.R +++ b/components/board.upload/R/upload_module_preview_counts.R @@ -128,6 +128,22 @@ upload_table_preview_counts_server <- function( action_buttons ) }, + if (is.matrix(uploaded$counts.csv) && nrow(uploaded$counts.csv) == 1 && ncol(uploaded$counts.csv) == 1) { + bslib::layout_columns( + col_widths = 12, + div( + style = "display: flex; justify-content: center; align-items: center; position: absolute; top: 21px; left: 21px; right: 21px; bottom: 21px; z-index: 9999; background: white;", + shiny::tags$div( + style = "width: 100%; display: flex; justify-content: center; align-items: center;", + shiny::tags$div( + class = "spinner-border text-primary", + style = "width: 5rem; height: 5rem;", + shiny::tags$span(class = "visually-hidden", "Loading...") + ) + ) + ) + ) + }, if (!is.null(uploaded$counts.csv)) { bslib::layout_columns( col_widths = 12, diff --git a/components/board.upload/R/upload_server.R b/components/board.upload/R/upload_server.R index 067dea5c9..a2cfacc55 100644 --- a/components/board.upload/R/upload_server.R +++ b/components/board.upload/R/upload_server.R @@ -1010,6 +1010,9 @@ UploadBoard <- function(id, query_files$counts } + # Set initial loading state + uploaded$counts.csv <- matrix(data = 1, nrow = 1, ncol = 1) + future_promise({ file <- read_query_files(counts_url) df <- playbase::read_counts(file)