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

Trigger Argyle Link #6

Merged
merged 4 commits into from
Apr 26, 2024
Merged

Trigger Argyle Link #6

merged 4 commits into from
Apr 26, 2024

Conversation

allthesignals
Copy link
Contributor

  • Generate Argyle userToken on load; API key is managed in .env.local
  • Load Argyle Link javascript: there is no ES6 module for this. The code here wraps Argyle with a Promise by loading it into a script tag and resolving the promise when it's loaded.
  • Provide support for hoisting the userToken into the head's meta tag
  • CSP updates


def index
res = Net::HTTP.post(URI.parse(USER_TOKEN_ENDPOINT), "", {"Authorization" => "Basic #{ENV['ARGYLE_API_TOKEN']}"})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this here until we have a proper Argyle service

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - so we exchange the API token for a user token. The user token seems like a great thing to store in the database under than flow object we were talking about.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! I think the userToken expires, but we do get a user id for "faster token renewal" (not sure what that really means). So, we'd likely store the id in the database, store the ephemeral token in a cookie.

Argyle Link has a nice little callback for when the token is expired. We could have that supplied with a renewal cal that includes the long-living "user id."....

Comment on lines +50 to +53
// general event for when anything happens in the flow
onArgyleEvent() {
this.element.submit();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a catchall event to move us along in the part of the flow we control

Comment on lines +10 to +17
policy.font_src :self, "https://*.cloudinary.com"
policy.form_action :self
policy.frame_ancestors :none
policy.img_src :self, :data, "https://www.google-analytics.com"
policy.img_src :self, :data, "https://*.cloudinary.com", "http://*.cloudinary.com", "https://www.google-analytics.com"
policy.object_src :none
policy.script_src :self, "https://js-agent.newrelic.com", "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.connect_src :self, "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.script_src :self, "https://*.argyle.com", "https://js-agent.newrelic.com", "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.connect_src :self, "https://*.argyle.com", "https://get.geojs.io", "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.worker_src :self, "blob:"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see an "Argyle plugin" including some kind of CSP configuration mixin so that this is managed more neatly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, yeah, I wonder how this is maintained in large apps with a lot of dependencies. Do large apps really need to incorporate all of their dependency's CSP entries like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure... Login uses their config (env variable) store to manage some of it: https://github.com/18F/identity-idp/blob/main/config/initializers/content_security_policy.rb

Were you expecting the dependencies to be able to configure CSP stuff themselves? I could see us using maybe plugins to manage some of the provider-specific initialization?

@@ -8,3 +8,4 @@

# SHOW_DEMO_BANNER controls whether to show the TEST SITE banner in Rails.env.development
SHOW_DEMO_BANNER=false
ARGYLE_API_TOKEN=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A pattern I've seen at other companies is to put the .env.local file with all the actual secrets in it, into a shared secret store like 1password. Engineers would copy it from there whenever a new secret is added. Do you know if that's how we generally do things here or if there's a better way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I think it's really up to the teams but we could survey some of the other engineers about good patterns for this.

Past projects I've seen have leveraged AWS' secrets storage feature. Login has a fairly complex environment variable validation system that could be nice if we find things become a bit sprawling. I see that being important for projects with lots of teams and lots of turnover. Want to prevent accidentally spilling secrets...

Your idea to use .env.local in 1pass seems fine for now...


def index
res = Net::HTTP.post(URI.parse(USER_TOKEN_ENDPOINT), "", {"Authorization" => "Basic #{ENV['ARGYLE_API_TOKEN']}"})

@userToken = JSON.parse(res.body)["user_token"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: Let's use snake_case for ruby variables. I can get rubocop linting soon so it will do it automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! My b. I always do this when switching between JS and Ruby...


def index
res = Net::HTTP.post(URI.parse(USER_TOKEN_ENDPOINT), "", {"Authorization" => "Basic #{ENV['ARGYLE_API_TOKEN']}"})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - so we exchange the API token for a user token. The user token seems like a great thing to store in the database under than flow object we were talking about.

export function initializeArgyle(Argyle, userToken, callbacks) {
return Argyle.create({
userToken,
sandbox: true, // Set to false for production environment.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should make this an environment variable at some point. I wonder how we could get an environment variable into this JS file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Actually, I have an approach for that (yet another thing I cribbed from Login)...

The line here hoists the variable up to a meta tag.

Then this helper can read it.

It's a little dangerous because someone could accidentally share secrets to the end user, but this is how Login did it.

Comment on lines +10 to +17
policy.font_src :self, "https://*.cloudinary.com"
policy.form_action :self
policy.frame_ancestors :none
policy.img_src :self, :data, "https://www.google-analytics.com"
policy.img_src :self, :data, "https://*.cloudinary.com", "http://*.cloudinary.com", "https://www.google-analytics.com"
policy.object_src :none
policy.script_src :self, "https://js-agent.newrelic.com", "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.connect_src :self, "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.script_src :self, "https://*.argyle.com", "https://js-agent.newrelic.com", "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.connect_src :self, "https://*.argyle.com", "https://get.geojs.io", "https://*.nr-data.net", "https://dap.digitalgov.gov", "https://www.google-analytics.com"
policy.worker_src :self, "blob:"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, yeah, I wonder how this is maintained in large apps with a lot of dependencies. Do large apps really need to incorporate all of their dependency's CSP entries like this?

@allthesignals allthesignals merged commit 7df9fe9 into main Apr 26, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants