From a93521fd4e8d0b53961b7a77c8ba4556fd0a3937 Mon Sep 17 00:00:00 2001 From: kosin Date: Sun, 3 Apr 2022 22:01:40 +0200 Subject: [PATCH 01/11] mongo_create and RegLogMongoConnector init --- DESCRIPTION | 2 +- NAMESPACE | 2 + R/DBI_connector.R | 8 +- R/RegLogConnector.R | 14 +-- R/database_create.R | 12 ++- R/gsheet_connector.R | 13 ++- R/mongo_connector.R | 199 +++++++++++++++++++++++++++++++++++ man/DBI_tables_create.Rd | 9 +- man/RegLogConnector.Rd | 14 +-- man/RegLogDBIConnector.Rd | 6 +- man/RegLogGsheetConnector.Rd | 8 +- man/RegLogMongoConnector.Rd | 92 ++++++++++++++++ man/gsheet_tables_create.Rd | 3 +- man/mongo_tables_create.Rd | 85 +++++++++++++++ 14 files changed, 426 insertions(+), 41 deletions(-) create mode 100644 R/mongo_connector.R create mode 100644 man/RegLogMongoConnector.Rd create mode 100644 man/mongo_tables_create.Rd diff --git a/DESCRIPTION b/DESCRIPTION index b3d86d7..fbf7356 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: shiny.reglog Title: Optional Login and Registration Module System for ShinyApps -Version: 0.5.0 +Version: 0.5.1.0001 Authors@R: person(given = "Michal", family = "Kosinski", diff --git a/NAMESPACE b/NAMESPACE index 2f8deb0..ac14c43 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,7 @@ export(RegLogDemo) export(RegLogEmayiliConnector) export(RegLogGmailrConnector) export(RegLogGsheetConnector) +export(RegLogMongoConnector) export(RegLogServer) export(RegLog_credsEdit_UI) export(RegLog_login_UI) @@ -23,6 +24,7 @@ export(login_UI) export(login_server) export(logout_button) export(mailMessageAttachment) +export(mongo_tables_create) export(password_reset_UI) export(register_UI) export(reglog_txt) diff --git a/R/DBI_connector.R b/R/DBI_connector.R index bc54b99..165e5ab 100644 --- a/R/DBI_connector.R +++ b/R/DBI_connector.R @@ -92,9 +92,9 @@ RegLogDBIConnector = R6::R6Class( #' database: first containing user data, second - reset codes information, #' third (optional) - logs from the object. For more info check documentation #' of `DBI_database_create`. - #' @param custom_handlers named list of custom handler functions. Custom handler - #' should take arguments: `self` and `private` - relating to the R6 object - #' and `message` of class `RegLogConnectorMessage`. It should return + #' @param custom_handlers named list of custom handler functions. Every + #' custom handler should take arguments: `self` and `private` - relating to + #' the R6 object and `message` of class `RegLogConnectorMessage`. It should #' return `RegLogConnectorMessage` object. #' @return object of `RegLogDBIConnector` class #' @@ -123,8 +123,6 @@ RegLogDBIConnector = R6::R6Class( private$db_tables <- table_names # initial connection to the database, checking if everything is all right private$db_connect() - # assign the unique ID for the module - self$module_id <- uuid::UUIDgenerate() # disconnect fron the database when not used private$db_disconnect() } diff --git a/R/RegLogConnector.R b/R/RegLogConnector.R index ba69346..192155a 100644 --- a/R/RegLogConnector.R +++ b/R/RegLogConnector.R @@ -26,10 +26,10 @@ RegLogConnector = R6::R6Class( #' types of `RegLogConnectorMessage`. Name of the element corresponds to #' the 'type' that is should handle. #' @details You can specify custom handler functions as a named list passed - #' to `custom_handlers` arguments during object initialization. Custom handler - #' should take arguments: `self` and `private` - relating to the R6 object - #' and `message` of class `RegLogConnectorMessage`. It should return - #' return `RegLogConnectorMessage` object + #' to `custom_handlers` arguments during object initialization. Every custom + #' handler should take arguments: `self` and `private` - relating to the R6 + #' object and `message` of class `RegLogConnectorMessage`. It should return + #' `RegLogConnectorMessage` object. handlers = list( ping = function(self, private, message) { @@ -64,9 +64,9 @@ RegLogConnector = R6::R6Class( #' @description Initialization of the object. Sets up listener reactiveVal #' and initializes listening server module - #' @param custom_handlers named list of custom handler functions. Custom handler - #' should take arguments: `self` and `private` - relating to the R6 object - #' and `message` of class `RegLogConnectorMessage`. It should return + #' @param custom_handlers named list of custom handler functions. Every + #' custom handler should take arguments: `self` and `private` - relating to + #' the R6 object and `message` of class `RegLogConnectorMessage`. It should #' return `RegLogConnectorMessage` object. #' #' @return object of `RegLogConnector` class diff --git a/R/database_create.R b/R/database_create.R index a7fd826..32aeb2b 100644 --- a/R/database_create.R +++ b/R/database_create.R @@ -12,8 +12,8 @@ check_user_data <- function(user_data) { if (class(user_data) != "data.frame") { stop(call. = F, "User data need to be in form of 'data.frame' object.") } - if (!all(c("username", "password", "email") %in% names(user_data))) { - stop(call. = F, "Data.frame containing user data needs to contain columns: 'username', 'password' and 'email'.") + if (!all(names(user_data)) %in% c("username", "password", "email", "create_time")) { + stop(call. = F, "Data.frame containing user data needs to contain columns: 'username', 'password', 'email' and (optionally) 'create_time'.") } if (sum(is.na(user_data$username), is.na(user_data$password), is.na(user_data$email)) > 0) { stop(call. = F, "Provided user data can't contain any NA values.") @@ -51,14 +51,14 @@ check_user_data <- function(user_data) { #' #' - account (default name) #' - id: integer, primary key, auto-increment -#' - username: varchar(255), NOT NULL, unique +#' - username: varchar(255), NOT NULL, unique key #' - password: varchar(255), NOT NULL -#' - email: varchar(255), NOT NULL, unique +#' - email: varchar(255), NOT NULL, unique key #' - create_time: datetime, NOT NULL #' - update_time: datetime, NOT NULL #' - reset_code (default name) #' - id: integer, primary key, auto-increment -#' - user_id: integer, NOT NULL +#' - user_id: integer, NOT NULL, key #' - reset_code: varchar(10), NOT NULL #' - used: tinyint, NOT NULL #' - create_time: datetime, NOT NULL @@ -154,6 +154,8 @@ DBI_tables_create <- function( warning = function(w) w ) + DBI::dbExecute(conn, paste0("CREATE INDEX user_id_reset_code ON ", reset_code_name, " (user_id);")) + if (isTRUE(verbose)) { writeLines(paste0(output$reset_code$table_name, " creation result: ", output$reset_code$result)) } diff --git a/R/gsheet_connector.R b/R/gsheet_connector.R index 231459a..ff31bca 100644 --- a/R/gsheet_connector.R +++ b/R/gsheet_connector.R @@ -56,6 +56,11 @@ RegLogGsheetConnector <- R6::R6Class( ) } ) + }, + # method to clear the sheets data from memory + clear_sheets = function() { + private$data_user <- NULL + private$data_reset_code <- NULL } ), # public elements #### @@ -69,10 +74,10 @@ RegLogGsheetConnector <- R6::R6Class( #' googlesheet: first containing user data, second - reset codes information, #' third (optional) - logs from the object. For more info check documentation #' of `gsheet_database_create`. - #' @param custom_handlers named list of custom handler functions. Custom handler - #' should take arguments: `self` and `private` - relating to the R6 object - #' and `message` of class `RegLogConnectorMessage`. It should return - #' `RegLogConnectorMessage` object. + #' @param custom_handlers named list of custom handler functions. Every + #' custom handler should take arguments: `self` and `private` - relating + #' to the R6 object and `message` of class `RegLogConnectorMessage`. It + #' should return `RegLogConnectorMessage` object. #' #' @return object of `RegLogDBIConnector` class #' diff --git a/R/mongo_connector.R b/R/mongo_connector.R new file mode 100644 index 0000000..dc37ae9 --- /dev/null +++ b/R/mongo_connector.R @@ -0,0 +1,199 @@ +#' Create RegLog-valid database collections for MongoDB +#' +#' @param mongo_url URI to the MongoDB cluster +#' @param mongo_db name of the MongoDB database +#' @param mongo_options additional connection options such as SSL keys/certs +#' @param account_name Name of the collection for storing user accounts credentials. +#' Defaults to 'account'. Mandatory collection. +#' @param reset_code_name Name of the collector for storing generated password reset +#' codes. Defaults to 'reset_code'. Mandatory collection. +#' @param user_data If you wish to import existing user database, you can input +#' data.frame with that table in this argument. It should contain columns: +#' username, password, email (ond optionally: create_time). Defaults to NULL. +#' @param hash_passwords If you are importing table of users upon tables creation, +#' you can also specify if the password should be hashed using `scrypt::hashPassword`. +#' Defaults to `FALSE`. If you have unhashed passwords in imported table, set +#' this option to `TRUE`. +#' @param verbose Boolean specific if the actions made by function should be +#' printed back to the console. +#' +#' @details +#' MongoDB database don't enforce a structure to the documents contained within. +#' It's best to use this function explicitly, as after creation of collections +#' it also create appropriate indexes for quick querying of the +#' Every document in created collections will have following structure: +#' +#' - account (default name) +#' - username: character **(index)** +#' - password: character +#' - email: character **(index)** +#' - create_time: timestamp +#' - update_time: timestamp +#' - reset_code (default name) +#' - user_id: character **(index)** +#' - reset_code: character +#' - used: numeric +#' - create_time: timestamp +#' - update_time: timestamp +#' - logs (default name, optional) - this collection isn't created with this +#' function, as there is no need for that - MongoDB collections don't have a +#' set structure, and no additional index is created there. +#' - time: timestamp +#' - session: character +#' - direction: character +#' - type: character +#' - note: character +#' +# @example examples/mongo_tables_create.R +#' @export +#' @family RegLog databases + +mongo_tables_create <- function( + mongo_url, + mongo_db, + mongo_options = mongolite::ssl_options(), + account_name = "account", + reset_code_name = "reset_code", + user_data = NULL, + hash_passwords = FALSE, + verbose = TRUE +){ + + check_namespace("mongolite") + + # if user data is provided, check its validity + if (!is.null(user_data)) { + check_user_data(user_data) + } + + # parse tables to write + tables <- list() + + # table with user data + if (!is.null(user_data)) { + if (isTRUE(hash_passwords)) { + if (isTRUE(verbose)) { + writeLines(paste0("Hashing passwords from existing data.")) + hash_progress <- utils::txtProgressBar(min = 0, max = nrow(user_data), initial = 0, + style = 3) + } + # iteratively hash passwords + for (i in seq_along(user_data$password)) { + user_data$password[i] <- scrypt::hashPassword(user_data$password[i]) + if (isTRUE(verbose)) { + utils::setTxtProgressBar(hash_progress, value = i) + } + } + if (isTRUE(verbose)) close(hash_progress) + } + db_time <- lubridate::as_datetime(db_timestamp()) + user_data$create_time <- if (is.null(user_data$create_time)) db_time + else lubridate::as_datetime(user_data$create_time) + user_data$update_time <- db_time + + # insert parsed data + mongo_conn <- mongolite::mongo( + url = mongo_url, + db = mongo_db, + options = mongo_options, + collection = account_name + ) + + mongo_conn$insert(user_data) + + } else { + # create collection + mongo_conn <- mongolite::mongo( + url = mongo_url, + db = mongo_db, + options = mongo_options, + collection = account_name + ) + } + # create indexes and disconnect from collection + mongo_conn$index("username") + mongo_conn$index("email") + mongo_conn$disconnect() + if (isTRUE(verbose)) { + writeLines(paste0(account_name, " collection created")) + } + + # create idexes for reset_codes + mongo_conn <- mongolite::mongo( + url = mongo_url, + db = mongo_db, + options = mongo_options, + collection = reset_code_name + ) + mongo_conn$index("user_id") + mongo_conn$disconnect() + if (isTRUE(verbose)) { + writeLines(paste0(reset_code_name, " collection created")) + } +} + + +#' @docType class +#' @title COnnector to MongoDB database +#' @description +#' Object of this class handles all connections for the RegLogServer object to +#' the database. It is created to handle MongoDB database compatible drivers. +#' Provides methods than will be used by RegLogServer to get and send data. +#' +#' Requires `mongolite` package to be installed. +#' @export + +RegLogMongoConnector <- R6::R6Class( + "RegLogMongoConnector", + inherit = RegLogConnector, + + public = list( + + #' @description Initialization of the object + #' @param mongo_url URI to the MongoDB cluster + #' @param mongo_db name of the MongoDB database + #' @param mongo_options additional connection options such as SSL keys/certs + #' @param collections names of the collections + #' @param table_names character vector. Contains names of the collections in the + #' database: first containing user data, second - reset codes information, + #' third (optional) - logs from the object. For more info check documentation + #' of `mongo_database_create`. + #' @param custom_handlers named list of custom handler functions. Every + #' custom handler should take arguments: `self` and `private` - relating to + #' the R6 object and `message` of class `RegLogConnectorMessage`. It should + #' return `RegLogConnectorMessage` object. + #' @return Object of `RegLogMongoConnector` class + + initialize = function( + mongo_url, + mongo_db, + mongo_options = mongolite::ssl_options(), + collections = c("account", "reset_code", "logs"), + custom_handlers = NULL + ) { + check_namespace("mongolite") + + # self$handlers[["login"]] <- mongo_login_handler + # self$handlers[["register"]] <- mongo_register_handler + # self$handlers[["credsEdit"]] <- mongo_credsEdit_handler + # self$handlers[["resetPass_generate"]] <- mongo_resetPass_generation_handler + # self$handlers[["resetPass_confirm"]] <- mongo_resetPass_confirmation_handler + + super$initialize(custom_handlers = custom_handlers) + + # store the arguments internally + private$mongo_url <- mongo_url + private$mongo_db <- mongo_db + private$mongo_options <- mongo_options + private$collections <- collections + + } + + ), + + private = list( + url = NULL, + db = NULL, + collections = NULL + ) +) \ No newline at end of file diff --git a/man/DBI_tables_create.Rd b/man/DBI_tables_create.Rd index 2841830..0fdc38a 100644 --- a/man/DBI_tables_create.Rd +++ b/man/DBI_tables_create.Rd @@ -58,16 +58,16 @@ Created tables should have following structure: \item account (default name) \itemize{ \item id: integer, primary key, auto-increment -\item username: varchar(255), NOT NULL, unique +\item username: varchar(255), NOT NULL, unique key \item password: varchar(255), NOT NULL -\item email: varchar(255), NOT NULL, unique +\item email: varchar(255), NOT NULL, unique key \item create_time: datetime, NOT NULL \item update_time: datetime, NOT NULL } \item reset_code (default name) \itemize{ \item id: integer, primary key, auto-increment -\item user_id: integer, NOT NULL +\item user_id: integer, NOT NULL, key \item reset_code: varchar(10), NOT NULL \item used: tinyint, NOT NULL \item create_time: datetime, NOT NULL @@ -118,6 +118,7 @@ DBI::dbDisconnect(conn = conn) } \seealso{ Other RegLog databases: -\code{\link{gsheet_tables_create}()} +\code{\link{gsheet_tables_create}()}, +\code{\link{mongo_tables_create}()} } \concept{RegLog databases} diff --git a/man/RegLogConnector.Rd b/man/RegLogConnector.Rd index e2fc71d..7d69054 100644 --- a/man/RegLogConnector.Rd +++ b/man/RegLogConnector.Rd @@ -47,10 +47,10 @@ of single data.frame \subsection{Details}{ You can specify custom handler functions as a named list passed -to \code{custom_handlers} arguments during object initialization. Custom handler -should take arguments: \code{self} and \code{private} - relating to the R6 object -and \code{message} of class \code{RegLogConnectorMessage}. It should return -return \code{RegLogConnectorMessage} object +to \code{custom_handlers} arguments during object initialization. Every custom +handler should take arguments: \code{self} and \code{private} - relating to the R6 +object and \code{message} of class \code{RegLogConnectorMessage}. It should return +\code{RegLogConnectorMessage} object. } \subsection{Returns}{ @@ -70,9 +70,9 @@ and initializes listening server module \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{custom_handlers}}{named list of custom handler functions. Custom handler -should take arguments: \code{self} and \code{private} - relating to the R6 object -and \code{message} of class \code{RegLogConnectorMessage}. It should return +\item{\code{custom_handlers}}{named list of custom handler functions. Every +custom handler should take arguments: \code{self} and \code{private} - relating to +the R6 object and \code{message} of class \code{RegLogConnectorMessage}. It should return \code{RegLogConnectorMessage} object.} } \if{html}{\out{
}} diff --git a/man/RegLogDBIConnector.Rd b/man/RegLogDBIConnector.Rd index aaa25de..4b33d48 100644 --- a/man/RegLogDBIConnector.Rd +++ b/man/RegLogDBIConnector.Rd @@ -60,9 +60,9 @@ database: first containing user data, second - reset codes information, third (optional) - logs from the object. For more info check documentation of \code{DBI_database_create}.} -\item{\code{custom_handlers}}{named list of custom handler functions. Custom handler -should take arguments: \code{self} and \code{private} - relating to the R6 object -and \code{message} of class \code{RegLogConnectorMessage}. It should return +\item{\code{custom_handlers}}{named list of custom handler functions. Every +custom handler should take arguments: \code{self} and \code{private} - relating to +the R6 object and \code{message} of class \code{RegLogConnectorMessage}. It should return \code{RegLogConnectorMessage} object.} } \if{html}{\out{}} diff --git a/man/RegLogGsheetConnector.Rd b/man/RegLogGsheetConnector.Rd index bd60c31..82ebde5 100644 --- a/man/RegLogGsheetConnector.Rd +++ b/man/RegLogGsheetConnector.Rd @@ -57,10 +57,10 @@ googlesheet: first containing user data, second - reset codes information, third (optional) - logs from the object. For more info check documentation of \code{gsheet_database_create}.} -\item{\code{custom_handlers}}{named list of custom handler functions. Custom handler -should take arguments: \code{self} and \code{private} - relating to the R6 object -and \code{message} of class \code{RegLogConnectorMessage}. It should return -\code{RegLogConnectorMessage} object.} +\item{\code{custom_handlers}}{named list of custom handler functions. Every +custom handler should take arguments: \code{self} and \code{private} - relating +to the R6 object and \code{message} of class \code{RegLogConnectorMessage}. It +should return \code{RegLogConnectorMessage} object.} } \if{html}{\out{}} } diff --git a/man/RegLogMongoConnector.Rd b/man/RegLogMongoConnector.Rd new file mode 100644 index 0000000..89d4824 --- /dev/null +++ b/man/RegLogMongoConnector.Rd @@ -0,0 +1,92 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\docType{class} +\name{RegLogMongoConnector} +\alias{RegLogMongoConnector} +\title{COnnector to MongoDB database} +\description{ +Object of this class handles all connections for the RegLogServer object to +the database. It is created to handle MongoDB database compatible drivers. +Provides methods than will be used by RegLogServer to get and send data. + +Requires \code{mongolite} package to be installed. +} +\section{Super class}{ +\code{\link[shiny.reglog:RegLogConnector]{shiny.reglog::RegLogConnector}} -> \code{RegLogMongoConnector} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-new}{\code{RegLogMongoConnector$new()}} +\item \href{#method-clone}{\code{RegLogMongoConnector$clone()}} +} +} +\if{html}{ +\out{
Inherited methods} +\itemize{ +\item \out{}\href{../../shiny.reglog/html/RegLogConnector.html#method-get_logs}{\code{shiny.reglog::RegLogConnector$get_logs()}}\out{} +\item \out{}\href{../../shiny.reglog/html/RegLogConnector.html#method-resume}{\code{shiny.reglog::RegLogConnector$resume()}}\out{} +\item \out{}\href{../../shiny.reglog/html/RegLogConnector.html#method-suspend}{\code{shiny.reglog::RegLogConnector$suspend()}}\out{} +} +\out{
} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-new}{}}} +\subsection{Method \code{new()}}{ +Initialization of the object +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{RegLogMongoConnector$new( + mongo_url, + mongo_db, + mongo_options = mongolite::ssl_options(), + collections = c("account", "reset_code", "logs"), + custom_handlers = NULL +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{mongo_url}}{URI to the MongoDB cluster} + +\item{\code{mongo_db}}{name of the MongoDB database} + +\item{\code{mongo_options}}{additional connection options such as SSL keys/certs} + +\item{\code{collections}}{names of the collections} + +\item{\code{custom_handlers}}{named list of custom handler functions. Every +custom handler should take arguments: \code{self} and \code{private} - relating to +the R6 object and \code{message} of class \code{RegLogConnectorMessage}. It should +return \code{RegLogConnectorMessage} object.} + +\item{\code{table_names}}{character vector. Contains names of the collections in the +database: first containing user data, second - reset codes information, +third (optional) - logs from the object. For more info check documentation +of \code{mongo_database_create}.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +Object of \code{RegLogMongoConnector} class +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{RegLogMongoConnector$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/gsheet_tables_create.Rd b/man/gsheet_tables_create.Rd index 36c146a..3e72421 100644 --- a/man/gsheet_tables_create.Rd +++ b/man/gsheet_tables_create.Rd @@ -113,6 +113,7 @@ if (googlesheets4::gs4_has_token()) { } \seealso{ Other RegLog databases: -\code{\link{DBI_tables_create}()} +\code{\link{DBI_tables_create}()}, +\code{\link{mongo_tables_create}()} } \concept{RegLog databases} diff --git a/man/mongo_tables_create.Rd b/man/mongo_tables_create.Rd new file mode 100644 index 0000000..fbe54d5 --- /dev/null +++ b/man/mongo_tables_create.Rd @@ -0,0 +1,85 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\name{mongo_tables_create} +\alias{mongo_tables_create} +\title{Create RegLog-valid database collections for MongoDB} +\usage{ +mongo_tables_create( + mongo_url, + mongo_db, + mongo_options = mongolite::ssl_options(), + account_name = "account", + reset_code_name = "reset_code", + user_data = NULL, + hash_passwords = FALSE, + verbose = TRUE +) +} +\arguments{ +\item{mongo_url}{URI to the MongoDB cluster} + +\item{mongo_db}{name of the MongoDB database} + +\item{mongo_options}{additional connection options such as SSL keys/certs} + +\item{account_name}{Name of the collection for storing user accounts credentials. +Defaults to 'account'. Mandatory collection.} + +\item{reset_code_name}{Name of the collector for storing generated password reset +codes. Defaults to 'reset_code'. Mandatory collection.} + +\item{user_data}{If you wish to import existing user database, you can input +data.frame with that table in this argument. It should contain columns: +username, password, email (ond optionally: create_time). Defaults to NULL.} + +\item{hash_passwords}{If you are importing table of users upon tables creation, +you can also specify if the password should be hashed using \code{scrypt::hashPassword}. +Defaults to \code{FALSE}. If you have unhashed passwords in imported table, set +this option to \code{TRUE}.} + +\item{verbose}{Boolean specific if the actions made by function should be +printed back to the console.} +} +\description{ +Create RegLog-valid database collections for MongoDB +} +\details{ +MongoDB database don't enforce a structure to the documents contained within. +It's best to use this function explicitly, as after creation of collections +it also create appropriate indexes for quick querying of the +Every document in created collections will have following structure: +\itemize{ +\item account (default name) +\itemize{ +\item username: character \strong{(index)} +\item password: character +\item email: character \strong{(index)} +\item create_time: timestamp +\item update_time: timestamp +} +\item reset_code (default name) +\itemize{ +\item user_id: character \strong{(index)} +\item reset_code: character +\item used: numeric +\item create_time: timestamp +\item update_time: timestamp +} +\item logs (default name, optional) - this collection isn't created with this +function, as there is no need for that - MongoDB collections don't have a +set structure, and no additional index is created there. +\itemize{ +\item time: timestamp +\item session: character +\item direction: character +\item type: character +\item note: character +} +} +} +\seealso{ +Other RegLog databases: +\code{\link{DBI_tables_create}()}, +\code{\link{gsheet_tables_create}()} +} +\concept{RegLog databases} From 65ffa2e4449953f3cdcbbbc5be2bee1a61410726 Mon Sep 17 00:00:00 2001 From: kosin Date: Sun, 3 Apr 2022 22:15:01 +0200 Subject: [PATCH 02/11] mongo handlers init --- DESCRIPTION | 2 +- R/DBI_connector.R | 2 + R/gsheet_connector.R | 2 + R/mongo_connector.R | 144 +++++++++++++++++++- man/RegLogDBIConnector.Rd | 2 + man/RegLogGsheetConnector.Rd | 2 + man/RegLogMongoConnector.Rd | 4 +- man/mongo_credsEdit_handler.Rd | 43 ++++++ man/mongo_login_handler.Rd | 35 +++++ man/mongo_register_handler.Rd | 36 +++++ man/mongo_resetPass_confirmation_handler.Rd | 37 +++++ man/mongo_resetPass_generation_handler.Rd | 35 +++++ man/mongo_tables_create.Rd | 10 +- 13 files changed, 342 insertions(+), 12 deletions(-) create mode 100644 man/mongo_credsEdit_handler.Rd create mode 100644 man/mongo_login_handler.Rd create mode 100644 man/mongo_register_handler.Rd create mode 100644 man/mongo_resetPass_confirmation_handler.Rd create mode 100644 man/mongo_resetPass_generation_handler.Rd diff --git a/DESCRIPTION b/DESCRIPTION index fbf7356..c468c31 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: shiny.reglog Title: Optional Login and Registration Module System for ShinyApps -Version: 0.5.1.0001 +Version: 0.5.1.0002 Authors@R: person(given = "Michal", family = "Kosinski", diff --git a/R/DBI_connector.R b/R/DBI_connector.R index 165e5ab..0d0ab57 100644 --- a/R/DBI_connector.R +++ b/R/DBI_connector.R @@ -86,6 +86,8 @@ RegLogDBIConnector = R6::R6Class( #' @description Initialization of the object. Creates initial connection #' to the database. #' + #' Requires `DBI` package to be installed. + #' #' @param driver Call that specifies the driver to be used during all queries #' @param ... other arguments used in `DBI::dbConnect()` call #' @param table_names character vector. Contains names of the tables in the diff --git a/R/gsheet_connector.R b/R/gsheet_connector.R index ff31bca..655099a 100644 --- a/R/gsheet_connector.R +++ b/R/gsheet_connector.R @@ -5,6 +5,8 @@ #' object to the database. It is created to handle googlesheet database. #' Provides methods than will be used by RegLogServer to get and send data. #' +#' Requires `googlesheets4` package to be installed. +#' #' @family dbConnectors #' @import R6 #' @export diff --git a/R/mongo_connector.R b/R/mongo_connector.R index dc37ae9..2db8a31 100644 --- a/R/mongo_connector.R +++ b/R/mongo_connector.R @@ -1,4 +1,12 @@ -#' Create RegLog-valid database collections for MongoDB +#' @title Create RegLog-valid database collections for MongoDB +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' MongoDB database don't enforce a structure to the documents contained within. +#' Even though this is true, it's best to use this function explicitly, +#' as after creation of collections it also create appropriate indexes for quick +#' querying of the database by RegLogMongoConnector. #' #' @param mongo_url URI to the MongoDB cluster #' @param mongo_db name of the MongoDB database @@ -18,9 +26,6 @@ #' printed back to the console. #' #' @details -#' MongoDB database don't enforce a structure to the documents contained within. -#' It's best to use this function explicitly, as after creation of collections -#' it also create appropriate indexes for quick querying of the #' Every document in created collections will have following structure: #' #' - account (default name) @@ -134,8 +139,10 @@ mongo_tables_create <- function( #' @docType class -#' @title COnnector to MongoDB database +#' @title Connector to MongoDB database #' @description +#' `r lifecycle::badge("experimental")` +#' #' Object of this class handles all connections for the RegLogServer object to #' the database. It is created to handle MongoDB database compatible drivers. #' Provides methods than will be used by RegLogServer to get and send data. @@ -196,4 +203,129 @@ RegLogMongoConnector <- R6::R6Class( db = NULL, collections = NULL ) -) \ No newline at end of file +) + +### RegLogMongoConnector handler functions #### + +#' MongoDB login handler +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' Default handler function querying database to confirm login +#' procedure. Used within object of `RegLogMongoConnector` class internally. +#' +#' @param self R6 object element +#' @param private R6 object element +#' @param message RegLogConnectorMessage which should contain within its data: +#' - username +#' - password +#' @family MongoDB handler functions +#' @concept mongo_handler +#' @keywords internal + +mongo_login_handler <- function(self, private, message) { + + check_namespace("mongolite") + +} + +#' MongoDB register handler +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' Default handler function querying database to confirm registration +#' validity and input new data. Used within object of `RegLogMongoConnector` class internally. +#' +#' @param self R6 object element +#' @param private R6 object element +#' @param message RegLogConnectorMessage which should contain within its data: +#' - username +#' - password +#' - email +#' @family MongoDB handler functions +#' @concept mongo_handler +#' @keywords internal + +mongo_register_handler = function(self, private, message) { + + check_namespace("mongolite") + +} + +#' MongoDB edit to the database handler +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' Default handler function querying database to confirm credentials +#' edit procedure and update values saved within database. Used within object of +#' `RegLogMongoConnector` class internally. +#' @param self R6 object element +#' @param private R6 object element +#' @param message RegLogConnectorMessage which need to contain within its data: +#' - username +#' - password +#' +#' It can also contain elements for change: +#' - new_username +#' - new_email +#' - new_password +#' @family MongoDB handler functions +#' @concept mongo_handler +#' @keywords internal + +mongo_credsEdit_handler <- function(self, private, message) { + + check_namespace("mongolite") + +} + +#' MongoDB resetpass code generation handler +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' Default handler function querying database to confirm credentials +#' edit procedure and update values saved within database. Used within object of +#' `RegLogMongoConnector` class internally. +#' @param self R6 object element +#' @param private R6 object element +#' @param message RegLogConnectorMessage which need to contain within its data: +#' - username +#' +#' @family MongoDB handler functions +#' @concept mongo_handler +#' @keywords internal + +mongo_resetPass_generation_handler <- function(self, private, message) { + + check_namespace("mongolite") + +} + +#' MongoDB resetpass code confirmation handler +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' Default handler function querying database to confirm credentials +#' edit procedure and update values saved within database. Used within object of +#' `RegLogMongoConnector` class internally. +#' @param self R6 object element +#' @param private R6 object element +#' @param message RegLogConnectorMessage which need to contain within its data: +#' - username +#' - reset_code +#' - password +#' +#' @family MongoDB handler functions +#' @concept mongo_handler +#' @keywords internal + +mongo_resetPass_confirmation_handler <- function(self, private, message) { + + check_namespace("mongolite") + +} \ No newline at end of file diff --git a/man/RegLogDBIConnector.Rd b/man/RegLogDBIConnector.Rd index 4b33d48..1422fcd 100644 --- a/man/RegLogDBIConnector.Rd +++ b/man/RegLogDBIConnector.Rd @@ -39,6 +39,8 @@ Other dbConnectors: \subsection{Method \code{new()}}{ Initialization of the object. Creates initial connection to the database. + +Requires \code{DBI} package to be installed. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{RegLogDBIConnector$new( driver, diff --git a/man/RegLogGsheetConnector.Rd b/man/RegLogGsheetConnector.Rd index 82ebde5..3fc7da3 100644 --- a/man/RegLogGsheetConnector.Rd +++ b/man/RegLogGsheetConnector.Rd @@ -8,6 +8,8 @@ Object of this class handles all connections for the RegLogServer object to the database. It is created to handle googlesheet database. Provides methods than will be used by RegLogServer to get and send data. + +Requires \code{googlesheets4} package to be installed. } \seealso{ Other dbConnectors: diff --git a/man/RegLogMongoConnector.Rd b/man/RegLogMongoConnector.Rd index 89d4824..f079bba 100644 --- a/man/RegLogMongoConnector.Rd +++ b/man/RegLogMongoConnector.Rd @@ -3,8 +3,10 @@ \docType{class} \name{RegLogMongoConnector} \alias{RegLogMongoConnector} -\title{COnnector to MongoDB database} +\title{Connector to MongoDB database} \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + Object of this class handles all connections for the RegLogServer object to the database. It is created to handle MongoDB database compatible drivers. Provides methods than will be used by RegLogServer to get and send data. diff --git a/man/mongo_credsEdit_handler.Rd b/man/mongo_credsEdit_handler.Rd new file mode 100644 index 0000000..c5740db --- /dev/null +++ b/man/mongo_credsEdit_handler.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\name{mongo_credsEdit_handler} +\alias{mongo_credsEdit_handler} +\title{MongoDB edit to the database handler} +\usage{ +mongo_credsEdit_handler(self, private, message) +} +\arguments{ +\item{self}{R6 object element} + +\item{private}{R6 object element} + +\item{message}{RegLogConnectorMessage which need to contain within its data: +\itemize{ +\item username +\item password +} + +It can also contain elements for change: +\itemize{ +\item new_username +\item new_email +\item new_password +}} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Default handler function querying database to confirm credentials +edit procedure and update values saved within database. Used within object of +\code{RegLogMongoConnector} class internally. +} +\seealso{ +Other MongoDB handler functions: +\code{\link{mongo_login_handler}()}, +\code{\link{mongo_register_handler}()}, +\code{\link{mongo_resetPass_confirmation_handler}()}, +\code{\link{mongo_resetPass_generation_handler}()} +} +\concept{MongoDB handler functions} +\concept{mongo_handler} +\keyword{internal} diff --git a/man/mongo_login_handler.Rd b/man/mongo_login_handler.Rd new file mode 100644 index 0000000..1f137cd --- /dev/null +++ b/man/mongo_login_handler.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\name{mongo_login_handler} +\alias{mongo_login_handler} +\title{MongoDB login handler} +\usage{ +mongo_login_handler(self, private, message) +} +\arguments{ +\item{self}{R6 object element} + +\item{private}{R6 object element} + +\item{message}{RegLogConnectorMessage which should contain within its data: +\itemize{ +\item username +\item password +}} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Default handler function querying database to confirm login +procedure. Used within object of \code{RegLogMongoConnector} class internally. +} +\seealso{ +Other MongoDB handler functions: +\code{\link{mongo_credsEdit_handler}()}, +\code{\link{mongo_register_handler}()}, +\code{\link{mongo_resetPass_confirmation_handler}()}, +\code{\link{mongo_resetPass_generation_handler}()} +} +\concept{MongoDB handler functions} +\concept{mongo_handler} +\keyword{internal} diff --git a/man/mongo_register_handler.Rd b/man/mongo_register_handler.Rd new file mode 100644 index 0000000..1f214e3 --- /dev/null +++ b/man/mongo_register_handler.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\name{mongo_register_handler} +\alias{mongo_register_handler} +\title{MongoDB register handler} +\usage{ +mongo_register_handler(self, private, message) +} +\arguments{ +\item{self}{R6 object element} + +\item{private}{R6 object element} + +\item{message}{RegLogConnectorMessage which should contain within its data: +\itemize{ +\item username +\item password +\item email +}} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Default handler function querying database to confirm registration +validity and input new data. Used within object of \code{RegLogMongoConnector} class internally. +} +\seealso{ +Other MongoDB handler functions: +\code{\link{mongo_credsEdit_handler}()}, +\code{\link{mongo_login_handler}()}, +\code{\link{mongo_resetPass_confirmation_handler}()}, +\code{\link{mongo_resetPass_generation_handler}()} +} +\concept{MongoDB handler functions} +\concept{mongo_handler} +\keyword{internal} diff --git a/man/mongo_resetPass_confirmation_handler.Rd b/man/mongo_resetPass_confirmation_handler.Rd new file mode 100644 index 0000000..2403c3f --- /dev/null +++ b/man/mongo_resetPass_confirmation_handler.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\name{mongo_resetPass_confirmation_handler} +\alias{mongo_resetPass_confirmation_handler} +\title{MongoDB resetpass code confirmation handler} +\usage{ +mongo_resetPass_confirmation_handler(self, private, message) +} +\arguments{ +\item{self}{R6 object element} + +\item{private}{R6 object element} + +\item{message}{RegLogConnectorMessage which need to contain within its data: +\itemize{ +\item username +\item reset_code +\item password +}} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Default handler function querying database to confirm credentials +edit procedure and update values saved within database. Used within object of +\code{RegLogMongoConnector} class internally. +} +\seealso{ +Other MongoDB handler functions: +\code{\link{mongo_credsEdit_handler}()}, +\code{\link{mongo_login_handler}()}, +\code{\link{mongo_register_handler}()}, +\code{\link{mongo_resetPass_generation_handler}()} +} +\concept{MongoDB handler functions} +\concept{mongo_handler} +\keyword{internal} diff --git a/man/mongo_resetPass_generation_handler.Rd b/man/mongo_resetPass_generation_handler.Rd new file mode 100644 index 0000000..e0a8f2e --- /dev/null +++ b/man/mongo_resetPass_generation_handler.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mongo_connector.R +\name{mongo_resetPass_generation_handler} +\alias{mongo_resetPass_generation_handler} +\title{MongoDB resetpass code generation handler} +\usage{ +mongo_resetPass_generation_handler(self, private, message) +} +\arguments{ +\item{self}{R6 object element} + +\item{private}{R6 object element} + +\item{message}{RegLogConnectorMessage which need to contain within its data: +\itemize{ +\item username +}} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Default handler function querying database to confirm credentials +edit procedure and update values saved within database. Used within object of +\code{RegLogMongoConnector} class internally. +} +\seealso{ +Other MongoDB handler functions: +\code{\link{mongo_credsEdit_handler}()}, +\code{\link{mongo_login_handler}()}, +\code{\link{mongo_register_handler}()}, +\code{\link{mongo_resetPass_confirmation_handler}()} +} +\concept{MongoDB handler functions} +\concept{mongo_handler} +\keyword{internal} diff --git a/man/mongo_tables_create.Rd b/man/mongo_tables_create.Rd index fbe54d5..24fa493 100644 --- a/man/mongo_tables_create.Rd +++ b/man/mongo_tables_create.Rd @@ -41,12 +41,14 @@ this option to \code{TRUE}.} printed back to the console.} } \description{ -Create RegLog-valid database collections for MongoDB +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +MongoDB database don't enforce a structure to the documents contained within. +Even though this is true, it's best to use this function explicitly, +as after creation of collections it also create appropriate indexes for quick +querying of the database by RegLogMongoConnector. } \details{ -MongoDB database don't enforce a structure to the documents contained within. -It's best to use this function explicitly, as after creation of collections -it also create appropriate indexes for quick querying of the Every document in created collections will have following structure: \itemize{ \item account (default name) From be4aab7331ed9ed14b87d8ee1409ce3a4f1261b3 Mon Sep 17 00:00:00 2001 From: kosin Date: Tue, 5 Apr 2022 18:39:13 +0200 Subject: [PATCH 03/11] initial mongo handlers --- .github/workflows/test-coverage.yaml | 6 + DESCRIPTION | 3 +- R/DBI_handlers.R | 3 +- R/database_create.R | 2 +- R/gsheet_handlers.R | 2 +- R/mongo_connector.R | 314 ++++++++++++++++++++++++++- _pkgdown.yml | 3 + vignettes/create_custom_handlers.Rmd | 5 +- 8 files changed, 322 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 3e747f4..a2e8565 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -23,6 +23,12 @@ jobs: steps: - uses: actions/checkout@v2 + + - if: runner.os == 'macos' + run: brew services start mongodb-community + + - if: runner.os == 'linux' + run: sudo systemctl restart mongod - uses: r-lib/actions/setup-r@v1 with: diff --git a/DESCRIPTION b/DESCRIPTION index c468c31..00745f7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: shiny.reglog Title: Optional Login and Registration Module System for ShinyApps -Version: 0.5.1.0002 +Version: 0.5.1.0003 Authors@R: person(given = "Michal", family = "Kosinski", @@ -40,6 +40,7 @@ Suggests: googledrive, googlesheets4, knitr, + mongolite, rmarkdown, RSQLite, testthat (>= 3.0.0) diff --git a/R/DBI_handlers.R b/R/DBI_handlers.R index 1057f93..897ee26 100644 --- a/R/DBI_handlers.R +++ b/R/DBI_handlers.R @@ -120,7 +120,6 @@ DBI_register_handler = function(self, private, message) { create_time = db_timestamp()) DBI::dbExecute(private$db_conn, query) - # DBI::dbSendQuery(private$db_conn, query) return( RegLogConnectorMessage( @@ -142,6 +141,7 @@ DBI_register_handler = function(self, private, message) { #' @param self R6 object element #' @param private R6 object element #' @param message RegLogConnectorMessage which need to contain within its data: +#' - account_id #' - password #' #' It can also contain elements for change: @@ -271,7 +271,6 @@ DBI_credsEdit_handler <- function(self, private, message) { ) } } - return(message_to_send) } diff --git a/R/database_create.R b/R/database_create.R index 32aeb2b..8daab0a 100644 --- a/R/database_create.R +++ b/R/database_create.R @@ -12,7 +12,7 @@ check_user_data <- function(user_data) { if (class(user_data) != "data.frame") { stop(call. = F, "User data need to be in form of 'data.frame' object.") } - if (!all(names(user_data)) %in% c("username", "password", "email", "create_time")) { + if (!all(names(user_data) %in% c("username", "password", "email", "create_time"))) { stop(call. = F, "Data.frame containing user data needs to contain columns: 'username', 'password', 'email' and (optionally) 'create_time'.") } if (sum(is.na(user_data$username), is.na(user_data$password), is.na(user_data$email)) > 0) { diff --git a/R/gsheet_handlers.R b/R/gsheet_handlers.R index 27a4935..bba5265 100644 --- a/R/gsheet_handlers.R +++ b/R/gsheet_handlers.R @@ -134,7 +134,7 @@ gsheet_register_handler = function(self, private, message) { #' @param self R6 object element #' @param private R6 object element #' @param message RegLogConnectorMessage which need to contain within its data: -#' - username +#' - account_id #' - password #' #' It can also contain elements for change: diff --git a/R/mongo_connector.R b/R/mongo_connector.R index 2db8a31..f0f39ef 100644 --- a/R/mongo_connector.R +++ b/R/mongo_connector.R @@ -180,11 +180,11 @@ RegLogMongoConnector <- R6::R6Class( ) { check_namespace("mongolite") - # self$handlers[["login"]] <- mongo_login_handler - # self$handlers[["register"]] <- mongo_register_handler - # self$handlers[["credsEdit"]] <- mongo_credsEdit_handler - # self$handlers[["resetPass_generate"]] <- mongo_resetPass_generation_handler - # self$handlers[["resetPass_confirm"]] <- mongo_resetPass_confirmation_handler + self$handlers[["login"]] <- mongo_login_handler + self$handlers[["register"]] <- mongo_register_handler + self$handlers[["credsEdit"]] <- mongo_credsEdit_handler + self$handlers[["resetPass_generate"]] <- mongo_resetPass_generation_handler + self$handlers[["resetPass_confirm"]] <- mongo_resetPass_confirmation_handler super$initialize(custom_handlers = custom_handlers) @@ -199,9 +199,17 @@ RegLogMongoConnector <- R6::R6Class( ), private = list( - url = NULL, - db = NULL, - collections = NULL + mongo_url = NULL, + mongo_db = NULL, + mongo_options = NULL, + collections = NULL, + connect = function(collection) { + + mongolite::mongo(url = private$mongo_url, + db = private$mongo_db, + options = private$mongo_options, + collection = collection) + } ) ) @@ -228,6 +236,47 @@ mongo_login_handler <- function(self, private, message) { check_namespace("mongolite") + account <- private$connect(private$collections[1]) + on.exit(account$disconnect()) + + # search for user + user_data <- account$find( + query = jsonlite::toJSON(list(username = message$data$username), auto_unbox = T), + fields = '{}' + ) + + if (nrow(user_data) == 0) { + # if don't return any, then nothing happened + + RegLogConnectorMessage( + "login", success = FALSE, username = FALSE, password = FALSE, + logcontent = paste(message$data$username, "don't exist") + ) + + } else { + # if there is a row present, check password + + if (scrypt::verifyPassword(user_data$password, message$data$password)) { + # if success: user logged in + + RegLogConnectorMessage( + "login", success = TRUE, username = TRUE, password = TRUE, + user_id = user_data$username, + user_mail = user_data$email, + account_id = user_data$`_id`, + logcontent = paste(message$data$username, "logged in") + ) + + } else { + # if else: the password didn't match + + RegLogConnectorMessage( + "login", success = FALSE, username = TRUE, password = FALSE, + logcontent = paste(message$data$username, "bad pass") + ) + } + } + } #' MongoDB register handler @@ -252,6 +301,53 @@ mongo_register_handler = function(self, private, message) { check_namespace("mongolite") + account <- private$connect(private$collections[1]) + on.exit(account$disconnect()) + + # firstly check if user or email exists + user_data <- account$find( + jsonlite::toJSON(list( + "$or" = list(list(username = message$data$username), list(email = message$data$email))), + auto_unbox = T)) + + if (nrow(user_data) > 0) { + # if query returns data don't register new + message_to_send <- RegLogConnectorMessage( + "register", + success = FALSE, + username = !message$data$username %in% user_data$username, + email = !message$data$email %in% user_data$email) + + if (!message_to_send$data$username && !message_to_send$data$email) { + message_to_send$logcontent <- paste0(message$data$username, "/", message$data$email, " conflict") + } else if (!message_to_send$data$username) { + message_to_send$logcontent <- paste(message$data$username, "conflict") + } else if (!message_to_send$data$email) { + message_to_send$logcontent <- paste(message$data$email, "conflict") + } + return(message_to_send) + } else { + # if query returns no data register new + account$insert( + data.frame( + username = message$data$username, + password = scrypt::hashPassword(message$data$password), + email = message$data$email, + create_time = Sys.time(), + update_time = Sys.time() + ) + ) + + return( + RegLogConnectorMessage( + "register", + success = TRUE, username = TRUE, email = TRUE, + user_id = message$data$username, + user_mail = message$data$email, + logcontent = paste(message$data$username, message$data$email, sep = "/") + ) + ) + } } #' MongoDB edit to the database handler @@ -265,7 +361,7 @@ mongo_register_handler = function(self, private, message) { #' @param self R6 object element #' @param private R6 object element #' @param message RegLogConnectorMessage which need to contain within its data: -#' - username +#' - account_id #' - password #' #' It can also contain elements for change: @@ -280,6 +376,86 @@ mongo_credsEdit_handler <- function(self, private, message) { check_namespace("mongolite") + account <- private$connect(private$collections[1]) + on.exit(account$disconnect()) + + # firstly check login credentials + user_data <- account$find( + query = jsonlite::toJSON(list( + "_id" = list("$oid" = message$data$account_id) + ), auto_unbox = T)) + + if (isFALSE(scrypt::verifyPassword(user_data$password, message$data$password))) { + # if FALSE: don't allow changes + + message_to_send <- RegLogConnectorMessage( + "credsEdit", success = FALSE, password = FALSE, + logcontent = paste(user_data$username, "bad pass") + ) + } else { + # if TRUE: allow changes + + ## Additional checks: if unique values (username, email) that are to be changed + ## are already present in the database + conflicting <- account$find( + list("$or" = list(list(username = message$data$username), + list(email = message$data$email))) + ) + + if (nrow(conflicting) > 0) { + message_to_send <- RegLogConnectorMessage( + "credsEdit", success = FALSE, + password = TRUE, + # if there is a conflict, these returns FALSE + new_username = !isTRUE(message$data$new_username %in% conflicting$username), + new_email = !isTRUE(message$data$new_email %in% conflicting$email)) + + message_to_send$logcontent <- + paste0(user_data$username, " conflict:", + if (!message_to_send$data$new_username) paste(" username:", message$data$new_username), + if (!message_to_send$data$new_email) paste(" email:", message$data$new_email), "." ) + } else { + # if nothing is returned, update can be made! + to_update <- list() + + if (!is.null(message$data$new_username)) { + to_update[["username"]] = message$data$new_username + } + if (!is.null(message$data$new_email)) { + to_update[["email"]] = message$data$new_email + } + if (!is.null(message$data$new_password)) { + to_update[["password"]] = scrypt::hashPassword(message$data$new_password) + } + + account$update( + query = jsonlite::toJSON(list( + "_id" = list("$oid" = message$data$account_id) + ), auto_unbox = T), + update = jsonlite::toJSON(list("$set" = to_update), auto_unbox = T) + ) + + message_to_send <- RegLogConnectorMessage( + "credsEdit", success = TRUE, + password = TRUE, + new_user_id = message$data$new_username, + new_user_mail = message$data$new_email, + new_user_pass = if(!is.null(message$data$new_password)) TRUE else NULL) + + info_to_log <- + c(message_to_send$data$new_user_id, + message_to_send$data$new_user_mail, + if (!is.null(message_to_send$new_user_pass)) "pass_change") + + message_to_send$logcontent <- + paste(user_data$username, "updated", + paste(info_to_log, + collapse = "/") + ) + } + } + + return(message_to_send) } #' MongoDB resetpass code generation handler @@ -302,7 +478,56 @@ mongo_credsEdit_handler <- function(self, private, message) { mongo_resetPass_generation_handler <- function(self, private, message) { check_namespace("mongolite") + + account <- private$connect(private$collections[1]) + on.exit(account$disconnect()) + # firstly check login credentials + user_data <- account$find( + query = jsonlite::toJSON(list("username" = message$data$username), + auto_unbox = T), + fields = '{}') + + # check condition and create output message accordingly + + if (nrow(user_data) == 0) { + # if don't return any, then nothing happened + + message_to_send <- RegLogConnectorMessage( + "resetPass_generate", success = FALSE, + logcontent = paste(message$data$username, "don't exist") + ) + + # if username exists, generate new resetpass code + } else { + + resetCode <- private$connect(private$collections[2]) + on.exit(resetCode$disconnect(), add = T) + + # get the user id + user_id <- user_data$`_id` + reset_code <- paste(floor(stats::runif(10, min = 0, max = 9.9)), collapse = "") + + data_to_append <- data.frame( + user_id = user_id, + reset_code = reset_code, + user = 0, + create_time = Sys.time(), + update_time = Sys.time() + ) + + resetCode$insert(data_to_append) + + message_to_send <- RegLogConnectorMessage( + "resetPass_generate", success = TRUE, + user_id = message$data$username, + user_mail = user_data$email, + reset_code = reset_code, + logcontent = paste(message$data$username, "code generated") + ) + } + + return(message_to_send) } #' MongoDB resetpass code confirmation handler @@ -328,4 +553,75 @@ mongo_resetPass_confirmation_handler <- function(self, private, message) { check_namespace("mongolite") + account <- private$connect(private$collections[1]) + on.exit(account$disconnect()) + + user_data <- account$find( + query = jsonlite::toJSON(list("username" = message$data$username), + auto_unbox = T), + fields = '{}') + + # check condition and create output message accordingly + if (nrow(user_data) == 0) { + # if don't return any, then nothing happened + + message_to_send <- RegLogConnectorMessage( + "resetPass_confirm", success = FALSE, username = FALSE, code_valid = FALSE, + logcontent = paste(message$data$username, "don't exist") + ) + + # if username exists, check for the resetcode + } else { + + resetCode <- private$connect(private$collections[2]) + on.exit(resetCode$disconnect(), add = T) + + user_id <- user_data$`_id` + reset_code_data <- resetCode$find( + query = jsonlite::toJSON(list("user_id" = user_id), + auto_unbox = T), + fields = '{}') + + not_expired <- + (lubridate::as_datetime(reset_code_data$create_time) + lubridate::period(4, "hours")) > Sys.time() + + # if not used reset code matches and isn't expired, update the database + if (nrow(reset_code_data) > 0 && not_expired) { + + # update user data + account$update( + query = jsonlite::toJSON(list( + "_id" = list("$oid" = user_id) + ), auto_unbox = T), + update = jsonlite::toJSON(list("$set" = list( + list(password = scrypt::hashPassword(message$data$password)), + list(update_time = Sys.time()) + )), auto_unbox = T)) + + # update reset code + resetCode$update( + query = jsonlite::toJSON(list( + "_id" = list("$oid" = reset_code_data$`_id`) + ), auto_unbox = T), + update = jsonlite::toJSON(list("$set" = list( + list(used = 1), + list(update_time = Sys.time()) + )), auto_unbox = T)) + + message_to_send <- RegLogConnectorMessage( + "resetPass_confirm", success = TRUE, username = TRUE, code_valid = TRUE, + logcontent = paste(message$data$username, "changed") + ) + # if reset code wasn't valid + } else { + + message_to_send <- RegLogConnectorMessage( + "resetPass_confirm", success = FALSE, username = TRUE, code_valid = FALSE, + logcontent = paste(message$data$username, "invalid code") + ) + } + } + + return(message_to_send) + } \ No newline at end of file diff --git a/_pkgdown.yml b/_pkgdown.yml index 9478ebd..570068a 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -19,6 +19,7 @@ reference: - RegLogServer - RegLogDBIConnector - RegLogGsheetConnector + - RegLogMongoConnector - RegLogEmayiliConnector - RegLogGmailrConnector - title: UI components of RegLogSystem @@ -32,6 +33,7 @@ reference: - contents: - DBI_tables_create - gsheet_tables_create + - mongo_tables_create - title: Advanced functions and objects - subtitle: Customizing texts desc: >- @@ -59,4 +61,5 @@ reference: - contents: - has_concept("gsheet_handler", internal = TRUE) - has_concept("DBI_handler", internal = TRUE) + - has_conecpt("mongo_handler", internal = TRUE) - has_concept("mail_handler", internal = TRUE) diff --git a/vignettes/create_custom_handlers.Rmd b/vignettes/create_custom_handlers.Rmd index 89a9db5..36dff33 100644 --- a/vignettes/create_custom_handlers.Rmd +++ b/vignettes/create_custom_handlers.Rmd @@ -132,8 +132,8 @@ str(register_message) - *RegLogDBIConnector*: `DBI_credsEdit_handler` - *RegLogGsheetConnector*: `gsheet_credsEdit_handler` -These functions are querying the database to search for the specified username -and password. After confirming user identity, it can update the database row +These functions are querying the database to search for the specified account ID +and verify password. After confirming user identity, it can update the database row for this user with any or all of: new username, new email and new password. Message structure: @@ -141,6 +141,7 @@ Message structure: credsEdit_message <- RegLogConnectorMessage( type = "credsEdit", + account_id = 1, password = "&f5*MSYj^niDt=V'3.[dyEX.C/", new_username = "Whenever", new_email = "edited@email.com", From 7c9c75c903f08148e20735adb7b249dce1726375 Mon Sep 17 00:00:00 2001 From: kosin Date: Tue, 5 Apr 2022 20:46:21 +0200 Subject: [PATCH 04/11] working handlers --- R/demo.R | 14 ++- R/mongo_connector.R | 28 ++--- tests/shinyTest_mongo/app.R | 15 +++ tests/shinyTest_mongo/tests/shinytest.R | 3 + .../shinytest/credsEdit-expected/001.json | 39 +++++++ .../shinytest/credsEdit-expected/001.png | Bin 0 -> 129776 bytes .../shinytest/credsEdit-expected/002.json | 39 +++++++ .../shinytest/credsEdit-expected/002.png | Bin 0 -> 129776 bytes .../shinytest/credsEdit-expected/003.json | 48 ++++++++ .../shinytest/credsEdit-expected/003.png | Bin 0 -> 134780 bytes .../shinytest/credsEdit-expected/004.json | 48 ++++++++ .../shinytest/credsEdit-expected/004.png | Bin 0 -> 134810 bytes .../shinytest/credsEdit-expected/005.json | 48 ++++++++ .../shinytest/credsEdit-expected/005.png | Bin 0 -> 141447 bytes .../shinytest/credsEdit-expected/006.json | 48 ++++++++ .../shinytest/credsEdit-expected/006.png | Bin 0 -> 141435 bytes .../shinytest/credsEdit-expected/007.json | 48 ++++++++ .../shinytest/credsEdit-expected/007.png | Bin 0 -> 137972 bytes .../shinytest/credsEdit-expected/008.json | 48 ++++++++ .../shinytest/credsEdit-expected/008.png | Bin 0 -> 139950 bytes .../shinytest/credsEdit-expected/009.json | 48 ++++++++ .../shinytest/credsEdit-expected/009.png | Bin 0 -> 137489 bytes .../shinytest/credsEdit-expected/010.json | 48 ++++++++ .../shinytest/credsEdit-expected/010.png | Bin 0 -> 140490 bytes .../shinytest/credsEdit-expected/011.json | 48 ++++++++ .../shinytest/credsEdit-expected/011.png | Bin 0 -> 138447 bytes .../shinytest/credsEdit-expected/012.json | 48 ++++++++ .../shinytest/credsEdit-expected/012.png | Bin 0 -> 142133 bytes .../shinytest/credsEdit-expected/013.json | 48 ++++++++ .../shinytest/credsEdit-expected/013.png | Bin 0 -> 144139 bytes .../tests/shinytest/credsEdit.R | 55 +++++++++ .../tests/shinytest/login-expected/001.json | 35 ++++++ .../tests/shinytest/login-expected/001.png | Bin 0 -> 65711 bytes .../tests/shinytest/login-expected/002.json | 35 ++++++ .../tests/shinytest/login-expected/002.png | Bin 0 -> 70319 bytes .../tests/shinytest/login-expected/003.json | 35 ++++++ .../tests/shinytest/login-expected/003.png | Bin 0 -> 70186 bytes .../tests/shinytest/login-expected/004.json | 35 ++++++ .../tests/shinytest/login-expected/004.png | Bin 0 -> 80268 bytes .../tests/shinytest/login-expected/005.json | 35 ++++++ .../tests/shinytest/login-expected/005.png | Bin 0 -> 63719 bytes tests/shinyTest_mongo/tests/shinytest/login.R | 20 ++++ .../shinytest/register-expected/001.json | 26 +++++ .../tests/shinytest/register-expected/001.png | Bin 0 -> 126322 bytes .../shinytest/register-expected/002.json | 26 +++++ .../tests/shinytest/register-expected/002.png | Bin 0 -> 142859 bytes .../shinytest/register-expected/003.json | 26 +++++ .../tests/shinytest/register-expected/003.png | Bin 0 -> 142645 bytes .../shinytest/register-expected/004.json | 26 +++++ .../tests/shinytest/register-expected/004.png | Bin 0 -> 142933 bytes .../shinytest/register-expected/005.json | 26 +++++ .../tests/shinytest/register-expected/005.png | Bin 0 -> 142912 bytes .../shinytest/register-expected/006.json | 26 +++++ .../tests/shinytest/register-expected/006.png | Bin 0 -> 139436 bytes .../shinytest/register-expected/007.json | 26 +++++ .../tests/shinytest/register-expected/007.png | Bin 0 -> 130097 bytes .../shinytest/register-expected/008.json | 26 +++++ .../tests/shinytest/register-expected/008.png | Bin 0 -> 130251 bytes .../tests/shinytest/register.R | 41 +++++++ .../shinytest/resetPass-expected/001.json | 38 ++++++ .../shinytest/resetPass-expected/001.png | Bin 0 -> 109495 bytes .../shinytest/resetPass-expected/002.json | 38 ++++++ .../shinytest/resetPass-expected/002.png | Bin 0 -> 105367 bytes .../shinytest/resetPass-expected/003.json | 38 ++++++ .../shinytest/resetPass-expected/003.png | Bin 0 -> 117697 bytes .../shinytest/resetPass-expected/004.json | 38 ++++++ .../shinytest/resetPass-expected/004.png | Bin 0 -> 109257 bytes .../shinytest/resetPass-expected/005.json | 38 ++++++ .../shinytest/resetPass-expected/005.png | Bin 0 -> 119231 bytes .../shinytest/resetPass-expected/006.json | 38 ++++++ .../shinytest/resetPass-expected/006.png | Bin 0 -> 119231 bytes .../shinytest/resetPass-expected/007.json | 38 ++++++ .../shinytest/resetPass-expected/007.png | Bin 0 -> 111445 bytes .../shinytest/resetPass-expected/008.json | 38 ++++++ .../shinytest/resetPass-expected/008.png | Bin 0 -> 111050 bytes .../tests/shinytest/resetPass.R | 34 ++++++ tests/testthat/test-mongo.R | 109 ++++++++++++++++++ tests/testthat/test-mongo_connector.R | 63 ++++++++++ 78 files changed, 1659 insertions(+), 16 deletions(-) create mode 100644 tests/shinyTest_mongo/app.R create mode 100644 tests/shinyTest_mongo/tests/shinytest.R create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/001.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/001.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/002.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/002.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/003.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/003.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/004.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/004.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/005.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/005.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/006.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/006.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/007.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/007.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/008.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/008.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/009.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/009.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/010.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/010.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/011.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/011.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/012.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/012.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/013.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/013.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/credsEdit.R create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/001.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/001.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/002.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/002.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/003.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/003.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/004.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/004.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/005.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/login-expected/005.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/login.R create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/001.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/001.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/002.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/002.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/003.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/003.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/004.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/004.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/005.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/005.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/006.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/006.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/007.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/007.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/008.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/register-expected/008.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/register.R create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/001.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/001.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/002.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/002.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/003.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/003.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/004.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/004.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/005.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/005.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/006.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/006.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/007.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/007.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/008.json create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass-expected/008.png create mode 100644 tests/shinyTest_mongo/tests/shinytest/resetPass.R create mode 100644 tests/testthat/test-mongo.R create mode 100644 tests/testthat/test-mongo_connector.R diff --git a/R/demo.R b/R/demo.R index 2ccbe5d..a837d58 100644 --- a/R/demo.R +++ b/R/demo.R @@ -165,11 +165,14 @@ RegLogDemo <- function(emayili_smtp = NULL, #' @param dbConnector unevaluated dbConnector #' @param mailConnector unevaluated mailConnector #' @param onStart unevaluated expression to run before initializing session +#' @param hide_account_id should account ID be hidden? Useful when document ID +#' is randomized. #' @noRd RegLogTest <- function(dbConnector, mailConnector, - onStart = NULL) { + onStart = NULL, + hide_account_id = FALSE) { # create an UI @@ -209,12 +212,17 @@ RegLogTest <- function(dbConnector, list("is_logged" = RegLog$is_logged(), "user_id" = if (isTRUE(RegLog$is_logged())) RegLog$user_id() else "not_logged", "user_mail" = RegLog$user_mail(), - "account_id" = RegLog$account_id()) + "account_id" = if(isTRUE(hide_account_id)) "hidden" else RegLog$account_id()) ) output$reglog_message <- renderPrint({ req(RegLog$message()$type != "ping") - RegLog$message()[-1] + + message <- RegLog$message()[-1] + if (isTRUE(hide_account_id)) { + message$data <- message$data[sapply(names(message$data), \(x) x != "account_id")] + } + message }) observeEvent(input$logs, RegLog$get_logs()) diff --git a/R/mongo_connector.R b/R/mongo_connector.R index f0f39ef..fd3900c 100644 --- a/R/mongo_connector.R +++ b/R/mongo_connector.R @@ -91,7 +91,7 @@ mongo_tables_create <- function( } if (isTRUE(verbose)) close(hash_progress) } - db_time <- lubridate::as_datetime(db_timestamp()) + db_time <- db_timestamp() user_data$create_time <- if (is.null(user_data$create_time)) db_time else lubridate::as_datetime(user_data$create_time) user_data$update_time <- db_time @@ -333,8 +333,8 @@ mongo_register_handler = function(self, private, message) { username = message$data$username, password = scrypt::hashPassword(message$data$password), email = message$data$email, - create_time = Sys.time(), - update_time = Sys.time() + create_time = db_timestamp(), + update_time = db_timestamp() ) ) @@ -398,9 +398,9 @@ mongo_credsEdit_handler <- function(self, private, message) { ## Additional checks: if unique values (username, email) that are to be changed ## are already present in the database conflicting <- account$find( - list("$or" = list(list(username = message$data$username), - list(email = message$data$email))) - ) + jsonlite::toJSON(list("$or" = list(list(username = message$data$new_username), + list(email = message$data$new_email))), + auto_unbox = T)) if (nrow(conflicting) > 0) { message_to_send <- RegLogConnectorMessage( @@ -512,8 +512,8 @@ mongo_resetPass_generation_handler <- function(self, private, message) { user_id = user_id, reset_code = reset_code, user = 0, - create_time = Sys.time(), - update_time = Sys.time() + create_time = db_timestamp(), + update_time = db_timestamp() ) resetCode$insert(data_to_append) @@ -553,6 +553,8 @@ mongo_resetPass_confirmation_handler <- function(self, private, message) { check_namespace("mongolite") + browser() + account <- private$connect(private$collections[1]) on.exit(account$disconnect()) @@ -578,7 +580,7 @@ mongo_resetPass_confirmation_handler <- function(self, private, message) { user_id <- user_data$`_id` reset_code_data <- resetCode$find( - query = jsonlite::toJSON(list("user_id" = user_id), + query = jsonlite::toJSON(list(user_id = user_id, reset_code = message$data$reset_code), auto_unbox = T), fields = '{}') @@ -594,8 +596,8 @@ mongo_resetPass_confirmation_handler <- function(self, private, message) { "_id" = list("$oid" = user_id) ), auto_unbox = T), update = jsonlite::toJSON(list("$set" = list( - list(password = scrypt::hashPassword(message$data$password)), - list(update_time = Sys.time()) + password = scrypt::hashPassword(message$data$password), + update_time = db_timestamp() )), auto_unbox = T)) # update reset code @@ -604,8 +606,8 @@ mongo_resetPass_confirmation_handler <- function(self, private, message) { "_id" = list("$oid" = reset_code_data$`_id`) ), auto_unbox = T), update = jsonlite::toJSON(list("$set" = list( - list(used = 1), - list(update_time = Sys.time()) + used = 1, + update_time = db_timestamp() )), auto_unbox = T)) message_to_send <- RegLogConnectorMessage( diff --git a/tests/shinyTest_mongo/app.R b/tests/shinyTest_mongo/app.R new file mode 100644 index 0000000..fa465a7 --- /dev/null +++ b/tests/shinyTest_mongo/app.R @@ -0,0 +1,15 @@ +# auth +library(shiny.reglog) + +uneval_dbConnector <- quote(RegLogMongoConnector$new( + mongo_url = "mongodb://localhost", + mongo_db = "reglog" +)) + +uneval_mailConnector <- quote(RegLogConnector$new()) + +shiny.reglog:::RegLogTest( + dbConnector = uneval_dbConnector, + mailConnector = uneval_mailConnector, + hide_account_id = T +) diff --git a/tests/shinyTest_mongo/tests/shinytest.R b/tests/shinyTest_mongo/tests/shinytest.R new file mode 100644 index 0000000..7021f8e --- /dev/null +++ b/tests/shinyTest_mongo/tests/shinytest.R @@ -0,0 +1,3 @@ +library(shinytest) +shinytest::testApp("../") + diff --git a/tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/001.json b/tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/001.json new file mode 100644 index 0000000..0486514 --- /dev/null +++ b/tests/shinyTest_mongo/tests/shinytest/credsEdit-expected/001.json @@ -0,0 +1,39 @@ +{ + "input": { + "browser": 0, + "login_system-cred_edit_new_ID": "", + "login_system-cred_edit_new_mail": "", + "login_system-cred_edit_new_pass1": "", + "login_system-cred_edit_new_pass2": "", + "login_system-cred_edit_old_pass": "", + "login_system-cred_edit_other_change": 0, + "login_system-cred_edit_pass_change": 1, + "login_system-register_bttn": 0, + "login_system-register_email": "", + "login_system-register_pass1": "", + "login_system-register_pass2": "", + "login_system-register_user_ID": "", + "logout": 0, + "logs": 0, + "reglogtabset": "Credentials edit" + }, + "output": { + "login_system-creds_edit_ui": { + "html": "

Edit your information<\/h1>\n

Your current data<\/h2>\n

As you are going to edit your information, please provide your current password below to confirm your identity before trying to trigger any changes.<\/p>\n

\n