chi2labs / cognitoR

CognitoR provides easy integration of Shiny with AWS Cognito Authentication.
22 stars 9 forks source link

Support for ui <- function(request) {} #41

Open JerePlum99 opened 4 months ago

JerePlum99 commented 4 months ago

@ppagnone @dietrichson Appreciate this package as a whole! Have you ever built an app with a ui <- function(request) {} function that would support shiny bookmarking or know of any reason this wouldn't work?

I've ran into an issue deploying an update to my previously working shiny app that would support bookmarking. The app deploys fine without the request or with shinymanager around the request, but once integrated with cognitoR it seems to trigger initialization of the app on the EC2 instance in a loop that crashes it. I'm having trouble figuring out what is causing this - happy to provide a reprex of some sort but figured I'd see if you had any ideas first.

mrismailt commented 4 months ago

@ppagnone @dietrichson below is a simple reprex that @JerePlum99 put together showcasing the issue. Shiny's server bookmarking does not work with the current {cognitoR} package. Any insight into this would be extremely helpful. Thanks!

library(cognitoR)
options(shiny.port = 5000)

# Custom Cognito Logout Link UI
cognito_logout_link_ui <- function(id, label = NULL, icon_name = "arrow-right-from-bracket") {
  ns <- NS(id) 
  tagList(
    # uiOutput(ns("who"),inline = TRUE),
    shinyjs::hidden(
      actionLink(ns("logout"),
                 label = label,
                 icon = icon(icon_name),
                 style = "color: white;")
    )
  )
}

# UI Request as Function
ui <- function(request) {
  bslib::page_navbar(
    title = "Cognito Reprex", 
    # Cognito Login UI
    cognito_ui("cognito"),
    bslib::nav_panel(
      "Iris Data", 
      bslib::layout_sidebar(
        sidebar = bslib::sidebar(shiny::selectizeInput("select_species", "Select Species", choices = c("setosa", "versicolor", "virginica"), selected = "setosa")),
        DT::DTOutput("iris_display")
      )
    ),
    # Cognito Logout as Icon
    bslib::nav_item(cognito_logout_link_ui("logout"))
  )
}

# Server Function
server <- function(input, output, session) {

  ### Cognito Management ####
  # Call Cognito module. #
  cognitomod <- callModule(cognito_server, "cognito")

  # Call Logout module #
  logoutmod <- callModule(logout_server,
                          "logout",
                          reactive(cognitomod$isLogged),
                          sprintf("You are logged in as '%s'", cognitomod$userdata$email))

  # To Click on button logout of logout module, call logout in cognito module. #
  observeEvent(logoutmod(),{
    cognitomod$logout()
  })

  # Check if user is already logged, and show a content. #
  observeEvent(cognitomod$isLogged, {
    if (cognitomod$isLogged) {
      # User is logged
      userdata <- cognitomod$userdata
      # Render user logged.
      output$content <- renderUI({
        p(paste("User: ", unlist(userdata$username)))
      })
    }
  })

  #### Bookmarking Management ####
  # Exclude all other booksmarks except "input$select_species"
  shiny::setBookmarkExclude(c("iris_display_rows_selected",
                              "iris_display_columns_selected",
                              "iris_display_cells_selected", 
                              "iris_display_rows_current",
                              "iris_display_rows_all", 
                              "iris_display_state",
                              "iris_display_search", 
                              "iris_display_cell_clicked",
                              "logout-logout"
  ))

  # Observe bookmark change
  shiny::observe({
    session$doBookmark()
  }) |>
    bindEvent(input$select_species)

  # Update query string on bookmark
  shiny::onBookmarked(updateQueryString)

  #### Data Management ####
  iris_data <- shiny::reactive({
    iris |>
      dplyr::filter(Species %in% input$select_species)
  })

  output$iris_display <- DT::renderDT({
    DT::datatable({
      iris_data()
    })
  })
}

shinyApp(ui = ui, server = server, enableBookmarking = "server")
dietrichson commented 4 months ago

@mrismailt I get this when running the reprex:

Listening on http://127.0.0.1:5000 Warning: Navigation containers expect a collection of bslib::nav_panel()/shiny::tabPanel()s and/or bslib::nav_menu()/shiny::navbarMenu()s. Consider using header or footer if you wish to place content above (or below) every panel's contents. Warning: Error in module: Your configuration for Cognito Service is not correct. 45: stop 44: module 39: callModule 38: server [/cloud/project/app.R#41] 1: shiny::runApp Error in module(childScope$input, childScope$output, childScope, ...) : Your configuration for Cognito Service is not correct.

mrismailt commented 4 months ago

I believe you will need a config.yml with the following content (@JerePlum99 please confirm):

default: 
  cognito:
    group_id: ""
    group_name: ""
    oauth_flow: "code"
    base_cognito_url: ""
    app_client_id: ""
    app_client_secret: ""
    redirect_uri: "http://localhost:5000/"
    redirect_uri_logout: "http://localhost:5000/"  

You'll need to fill this out with your own Cognito info

JerePlum99 commented 4 months ago

@mrismailt @dietrichson That is correct, I don't have a non-internal EC2 server running that I could exhibit the reprex on, but it should work with any base cognitoR implementation.

Sidenote, the bslib::nav_panel() error you'll see isn't fatal. bslib expects all items in the bslib::page_navbar() to be bslib::nav_panel or passed to the header or footer parameters. If passing cognito in the correct format it overrides some of the bslib styling (separate issue that has to do with the cognito_ui structure).

JerePlum99 commented 4 months ago

@dietrichson As added context, my hunch is that bookmarking is not working due to the behavior of get_url_auth_redirect.

I don't fully understand the flow of session[["clientData"]] but I think the extra shiny bookmarking URL values, state_id=* and URL bookmarking inputs aren't being passed through the params within the redirect.

Here's the relevant cognitoR code:

# #' Get Cognito URL for authentication redirect
# #'
# #' Return Url where user is redirect if is not logged yet.
# #'
# #' @param cognito_config list Obtained with get_cognito_config()
# #' @param session Shiny session Will be used to keep the params in urls when redirection is done.
# #' @return character|FALSE
# #' @author Pablo Pagnone
get_url_auth_redirect <- function(cognito_config, session = getDefaultReactiveDomain()) {

  if(!is.list(cognito_config) ||
     is.null(cognito_config$base_cognito_url) ||
     is.null(cognito_config$oauth_flow) ||
     is.null(cognito_config$app_client_id) ||
     is.null(cognito_config$redirect_uri)) {
    return(FALSE)
  }

  # Take params from url and encode for send to Cognito as param.
  # These params will be returned in "status" param from Cognitor redirection.
  params = ""

  # params = ""
  # if(!is.null(session) && !is.null(session[["clientData"]]$url_pathname) && session[["clientData"]]$url_pathname != "") {
  #   params <- session[["clientData"]]$url_pathname
  # }

  if(!is.null(session) && session[["clientData"]]$url_search != "") {
    params <- paste0(params, utils::URLencode(session[["clientData"]]$url_search, TRUE))
  }
  if(!is.null(session) && session[["clientData"]]$url_hash != "") {
    params <- paste0(params, utils::URLencode(session[["clientData"]]$url_hash, TRUE))
  }

  aws_auth_redirect <- paste0(cognito_config$base_cognito_url,
                              "/oauth2/authorize?", "scope=openid", "&",
                              "response_type=",cognito_config$oauth_flow,"&",
                              "client_id=", cognito_config$app_client_id, "&",
                              "redirect_uri=", cognito_config$redirect_uri, "&",
                              paste0("state=", params)
  )
}
mrismailt commented 4 months ago

I don't believe that is the issue because even if you update that function to manual insert a state id for an existing bookmark, it doesn't load it.

There is something happening where the session gets loaded twice for some reason. I believe the state id is lost during the second load.

@dietrichson @ppagnone would you happen to know why Shiny loads the session twice in a row when cognitoR in used in the app? I'm happy to do the work of updating any of the code and putting in a PR. If you could provide any direction here that would be great.

Let us know if you need anything else from us. If you are not able to test the reprex above due to not having a sample cognito service set up, we can set one up for you. Our priority is getting this solved asap as it has very really impact on our business.

Thanks!

dietrichson commented 3 months ago

@mrismailt I am unable to reproduce this issue, so I probably can't provide any meaningful guidance. The best path forward is probably a PR to cover your use-case.