Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial fully customized sign in and registration example #92

Merged
merged 12 commits into from
Jul 10, 2020
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
node_modules/
config.yml
rsconnect/
inst/doc
4 changes: 4 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ Imports:
uuid,
xts
RoxygenNote: 7.1.1
Suggests:
knitr,
rmarkdown
VignetteBuilder: knitr
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ export(email_input)
export(firebase_dependencies)
export(firebase_init)
export(global_sessions_config)
export(password_input)
export(profile_module)
export(profile_module_ui)
export(providers_ui)
export(secure_server)
export(secure_static)
export(secure_ui)
export(set_config_env)
export(sign_in_check_jwt)
export(sign_in_js)
export(sign_in_module_ui)
export(sign_in_no_invite_module_ui)
export(sign_in_ui_default)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# v0.1.0.9000

- only use the "email" sign in provider by default rather than c("google", "email")
- standarized and documented process for using fully customized sign in and registration pages.
- currently, the user must use `sign_in` as the ID for the cutom UI module

# v0.1.0

Expand Down
12 changes: 9 additions & 3 deletions R/email_input.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
#'
#' @export
#'
#' @importFrom htmltools tags
#' @importFrom shiny restoreInput
#' @importFrom htmltools tags tagList
#' @importFrom shiny restoreInput icon
#'
email_input <- function (inputId, label, value = "", width = NULL, placeholder = NULL) {
email_input <- function (
inputId,
label = tagList(icon("envelope"), "Email"),
value = "",
width = NULL,
placeholder = NULL
) {
value <- shiny::restoreInput(id = inputId, default = value)

tags$div(
Expand Down
44 changes: 44 additions & 0 deletions R/password_input.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#' A modification of 'shiny::passwordInput'
#'
#' This modified version of shiny's passwordInput() does not actual send the password
#' to our shiny server. It is just a regular password input that always keeps your
#' user's password on the client. The password is used to sign the user in and then
#' converted to a JWT by Firebase, all on the client, before it is sent to your shiny
#' server.
#'
#' @param input_id The input slot that will be used to access the value.
#' @param label Display label for the control, or NULL for no label.
#' @param value Initial value.
#' @param style Character string of in-line css to style the input.
#' @param placeholder A character string giving the user a hint as to what can
#' be entered into the control. Internet Explorer 8 and 9 do not support this option.
#'
#' @importFrom htmltools tags
#'
#' @export
#'
password_input <- function(
input_id,
label = tagList(icon("unlock-alt"), "Password"),
value = "",
style = "",
placeholder = NULL
) {
tags$div(
class = "form-group",
style = style,
tags$label(
label,
class = "control-label",
class = if (is.null(label)) "shiny-label-null",
`for` = input_id
),
tags$input(
id = input_id,
type = "password",
class = "form-control",
value = value,
placeholder = placeholder
)
)
}
91 changes: 91 additions & 0 deletions R/sign_in_components.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@


#' Sign in and register pages JavaScript dependencies
#'
#' This function should be called at the bottom of your custom sign in and registration
#' pages UI. It loads in all the javascript dependencies to handle polished sign
#' in and registration. See the vignette for details.
#'
#' @param ns the ns function from the Shiny module that this function is called
#' within.
#'
#' @importFrom htmltools tagList
#' @importFrom shinyFeedback useShinyFeedback
#'
#' @export
#'
#'
sign_in_js <- function(ns) {

firebase_config <- .global_sessions$firebase_config

htmltools::tagList(
shinyFeedback::useShinyFeedback(feedback = FALSE),

firebase_dependencies(),
firebase_init(firebase_config),
tags$script(src = "polish/js/toast_options.js"),
tags$script(src = "polish/js/auth_all.js?version=1"),
tags$script(paste0("auth_all('", ns(''), "')")),
tags$script(src = "https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"),
tags$script(src = "polish/js/auth_firebase.js?version=6"),
tags$script(paste0("auth_firebase('", ns(''), "')"))
)
}

#' Check the JWT from the user sign in
#'
#' This function retreives the JWT created by the JavaScript from \code{\link{sign_in_js}}
#' and signs the user in as long as the token can be verified.
#' This function should be called in the server function of a shiny module. Make sure
#' to call \code{\link{sign_in_js}} in the UI function of this module.
#'
#' @param jwt a reactive returning a Firebase JSON web token for the signed in user.
#' @param session the shiny session.
#'
#' @importFrom shinyFeedback resetLoadingButton showToast
#' @importFrom shinyWidgets sendSweetAlert
#' @importFrom shiny getDefaultReactiveDomain
#'
#' @export
#'
sign_in_check_jwt <- function(jwt, session = shiny::getDefaultReactiveDomain()) {


observeEvent(jwt(), {
hold_jwt <- jwt()

tryCatch({

# user is invited, so attempt sign in
new_user <- .global_sessions$sign_in(
hold_jwt$jwt,
digest::digest(hold_jwt$cookie)
)

if (is.null(new_user)) {
shinyFeedback::resetLoadingButton('submit_sign_in')
# show unable to sign in message
shinyFeedback::showToast('error', 'sign in error')
stop('sign_in_module: sign in error', call. = FALSE)

} else {
# sign in success
remove_query_string()
session$reload()
}

}, error = function(e) {
shinyFeedback::resetLoadingButton('submit_sign_in')
print(e)
shinyWidgets::sendSweetAlert(
session,
title = "Not Authorized",
text = "You must have an invite to access this app",
type = "error"
)

})

})
}
56 changes: 5 additions & 51 deletions R/sign_in_module.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,8 @@ sign_in_module_ui <- function(
) {
ns <- shiny::NS(id)

firebase_config <- .global_sessions$firebase_config
providers <- .global_sessions$sign_in_providers





email_ui <- tags$div(
id = ns("email_ui"),
tags$div(
Expand Down Expand Up @@ -214,19 +209,11 @@ sign_in_module_ui <- function(

htmltools::tagList(
shinyjs::useShinyjs(),
shinyFeedback::useShinyFeedback(feedback = FALSE),
tags$div(
class = "auth_panel",
ui_out
),
firebase_dependencies(),
firebase_init(firebase_config),
tags$script(src = "polish/js/toast_options.js"),
tags$script(src = "polish/js/auth_all.js?version=1"),
tags$script(paste0("auth_all('", ns(''), "')")),
tags$script(src = "https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"),
tags$script(src = "polish/js/auth_firebase.js?version=6"),
tags$script(paste0("auth_firebase('", ns(''), "')"))
sign_in_js(ns)
)
}

Expand All @@ -239,7 +226,6 @@ sign_in_module_ui <- function(
#' @param session the Shiny session
#'
#' @importFrom shiny observeEvent observe getQueryString
#' @importFrom shinyFeedback showToast resetLoadingButton
#' @importFrom shinyjs show hide
#' @importFrom shinyWidgets sendSweetAlert
#' @importFrom digest digest
Expand Down Expand Up @@ -436,42 +422,10 @@ sign_in_module <- function(input, output, session) {

}, ignoreInit = TRUE)

observeEvent(input$check_jwt, {
email <- tolower(input$email)

tryCatch({

# user is invited, so attempt sign in
new_user <- .global_sessions$sign_in(
input$check_jwt$jwt,
digest::digest(input$check_jwt$cookie)
)

if (is.null(new_user)) {
shinyFeedback::resetLoadingButton('submit_sign_in')
# show unable to sign in message
shinyFeedback::showToast('error', 'sign in error')
stop('sign_in_module: sign in error', call. = FALSE)

} else {
# sign in success
remove_query_string()
session$reload()
}

}, error = function(e) {
shinyFeedback::resetLoadingButton('submit_sign_in')
print(e)
shinyWidgets::sendSweetAlert(
session,
title = "Not Authorized",
text = "You must have an invite to access this app",
type = "error"
)

})


sign_in_check_jwt(
jwt = shiny::reactive({input$check_jwt})
)

})
invisible()
}
57 changes: 6 additions & 51 deletions R/sign_in_no_invite_module.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#'
#' @inheritParams sign_in_module_ui
#'
#' @importFrom shiny textInput actionButton NS actionLink
#' @importFrom shiny NS actionLink
#' @importFrom htmltools tagList tags div h1 br hr
#' @importFrom shinyjs useShinyjs hidden
#' @importFrom shinyFeedback loadingButton useShinyFeedback
Expand All @@ -19,7 +19,6 @@
sign_in_no_invite_module_ui <- function(id) {
ns <- shiny::NS(id)

firebase_config <- .global_sessions$firebase_config
providers <- .global_sessions$sign_in_providers

email_ui <- tags$div(
Expand Down Expand Up @@ -175,21 +174,13 @@ sign_in_no_invite_module_ui <- function(id) {
fluidPage(
fluidRow(
shinyjs::useShinyjs(),
shinyFeedback::useShinyFeedback(feedback = FALSE),
tags$div(
class = "auth_panel",
ui_out
)
),

firebase_dependencies(),
firebase_init(firebase_config),
tags$script(src = "polish/js/toast_options.js"),
tags$script(src = "polish/js/auth_all_no_invite.js?version=2"),
tags$script(paste0("auth_all_no_invite('", ns(''), "')")),
tags$script(src = "https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"),
tags$script(src = "polish/js/auth_firebase.js?version=6"),
tags$script(paste0("auth_firebase('", ns(''), "')"))
sign_in_js(ns)
)
}

Expand All @@ -200,9 +191,7 @@ sign_in_no_invite_module_ui <- function(id) {
#' @param session the Shiny session
#'
#' @importFrom shiny observeEvent getQueryString observe
#' @importFrom shinyFeedback showToast resetLoadingButton
#' @importFrom shinyjs show hide
#' @importFrom shinyWidgets sendSweetAlert
#' @importFrom digest digest
#'
sign_in_no_invite_module <- function(input, output, session) {
Expand Down Expand Up @@ -241,43 +230,9 @@ sign_in_no_invite_module <- function(input, output, session) {
shinyjs::show("sign_in_panel_bottom")
})

sign_in_check_jwt(
jwt = shiny::reactive({input$check_jwt})
)

observeEvent(input$check_jwt, {

tryCatch({

# user is invited, so attempt sign in
new_user <- .global_sessions$sign_in(
input$check_jwt$jwt,
digest::digest(input$check_jwt$cookie)
)

if (is.null(new_user)) {
shinyFeedback::resetLoadingButton('submit_sign_in')
# show unable to sign in message
shinyFeedback::showToast('error', 'sign in error')
stop('sign_in_module: sign in error', call. = FALSE)

} else {
# sign in success
remove_query_string()
session$reload()
}



}, error = function(e) {
shinyFeedback::resetLoadingButton('submit_sign_in')
# user is not invited
print(e)
shinyWidgets::sendSweetAlert(
session,
title = "Not Authorized",
text = "You must have an invite to access this app",
type = "error"
)

})

})
invisible()
}
8 changes: 7 additions & 1 deletion man/email_input.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading