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

Feature/secure services #249

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Feature/secure services #249

wants to merge 12 commits into from

Conversation

dopenguin
Copy link
Member

Summary

Add possibility to use services that are authenticated by OIDC by passing a Bearer token when sending requests.

Instructions for local reproduction and review

  • Get the configuration from me through a secure channel
  • npm run diplan:dev
  • Start CORS Plugin
  • Login with credentials
  • Load secure layer

Caveat: If the user tries loading the secure layer while not authenticated, the layer is never shown. This should not be a problem in a proper authentication environment and thus I have not addressed this here.

Pull Request Checklist (for Assignee)

  • Changelogs are maintained
  • Functionality has been tested in Firefox, Chrome, Safari
  • Functionality has been tested on a smartphone
  • Functionality has been tested with 200% screen zoom
  • [ ] Screenreader functionality has been manually tested with NVDA Not needed

UI has been tested in the following tools regarding accessibility (only regarding functionality affected in this PR)

  • Chrome Lighthouse
  • Firefox Accessibility

All urls that fit the regular expression defined by interceptorUrlRegex
have a token set in the cookies of the browser set in the Authorization
header as a Bearer token for all outgoing requests.
URLs to services and authentication server are omitted.
@dopenguin dopenguin added the enhancement New feature or request label Feb 24, 2025
@dopenguin dopenguin self-assigned this Feb 24, 2025
Copy link
Member

@warm-coolguy warm-coolguy left a comment

Choose a reason for hiding this comment

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

🏓 @dopenguin Unfinished review, I'll add more later.

All in all, I do not see how an application is supposed to use us properly. It seems to me that we require both a configuration parameter for the token and an update mechanism to replace it in time that the login-managing application is required to call in time. Or did I just not see the idea here?

Comment on lines +3 to +6
/*
* Functions are based on https://bitbucket.org/geowerkstatt-hamburg/masterportal/src/dev_vue/src/modules/login/js/utilsCookies.js
* and https://bitbucket.org/geowerkstatt-hamburg/masterportal/src/dev_vue/src/modules/login/js/utilsOIDC.js.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Why are we using cookies at all? There seems to be no technical reason to do it, but now we have to worry about cookie laws. Has there been any communication with the customer regarding this? The last state I remember is that we get the token by configuration and thus, it should be fetchable from the store.configuration.token or something like that.

Copy link
Member Author

Choose a reason for hiding this comment

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

The configuration option was something I had in mind initially but scrapped quickly.

There are two options that can solve using the token:

  • Using the token when calling e.g. createMap
  • Requesting the token from the Cookie

If we'd get the token from the call to createMap, we'd have to handle the situation when a token expires and request a new one as the function call is already done. If we'd be doing that with the configuration, the situation would be similar.

By having the token in a cookie, which is usually done by applications that use OIDC tokens for authentication anyway, we can simply request it on the go and not worry about refreshing it on our own.

Do you think an adjustment to the documentation is necessary so that it is clear that interceptorUrlRegex should only be used by integrated clients as we currently do not offer a login module?

Copy link
Member

@warm-coolguy warm-coolguy Feb 26, 2025

Choose a reason for hiding this comment

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

The createMap parameter combined with an action provided to update the token for our application when needed should be the way to go here. This way we initially have a token to start working with and delegate its updates to the leading application that may now refresh and keep it however it desires to – forwarding it to the map is a follow-up step.

This also spares the leading application to be forced to produce a cookie token, which it may not have intended to, or which may also already be in use for another token since that's a very generic word – since the client is supposed to be used in multiple spots eventually, even both may hold true in time.

This way we're also keeping data flow to the etablished channels and don't open up a new one.

Do you think an adjustment to the documentation is necessary so that it is clear that interceptorUrlRegex should only be used by integrated clients as we currently do not offer a login module?

I'd like it to be used in combination with a config parameter and an obligation to refresh it via action, which is a mechanism that should be documented, but is debated atm.

Copy link
Member Author

Choose a reason for hiding this comment

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

The name of the token can now be configured, as mentioned in #249 (comment)

Whether the authentication shall be achieved by retrieving a cookie or giving the JWT value via createMap and handling the refresh in POLAR (as well) is now something I am discussing with the relevant person that will integrate POLAR later on.

Comment on lines 12 to 29
export function addInterceptor(interceptorUrlRegex: string) {
const { fetch: originalFetch } = window

window.fetch = (resource, originalConfig) => {
let config = originalConfig

// @ts-expect-error | Has worked like charm so far. If an error occurs if resource is of type RequestInfo, take another look
if (interceptorUrlRegex && resource?.match(interceptorUrlRegex)) {
config = {
...originalConfig,
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: { Authorization: `Bearer ${getCookie('token')}` },
}
}

return originalFetch(resource, config)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Since we share the window element with the outlying application, we're now potentially changing its behaviour. This is quite dangerous and we may break stuff, so I don't deem this mergable, especially since we're plain overriding any headers sent to the target.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you have a suggestion on how to add an interceptor here for only polar instead of the whole application? I don't seem to have come across a reasonable solution so far.

Copy link
Member

Choose a reason for hiding this comment

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

My first idea was to identify the call source, and only act if it was from within POLAR. Since .caller is deprecated, checking the call stack seems to be a non-solution, and we can't just modify all local requests since OL and the masterportalAPI may start them, too.

The only thing left seems to be the URL itself. We may modify all URLs intended for interception on arrival. Since everything flows through the client creation (either by config or from the services.json), all URLs may run through a check on whether they fit the interceptorUrlRegex. If so, we modify them to hold an appropriate flag.

Ideas on how to mark URLs:

  • ${url}+POLAR_INTERCEPTOR+ or another string that will absolutely not randomly appear in the finite time that remains to the universe – however, if there's any operations on the end of the URL, this may or may not break anything?
  • I wonder whether ?, &, or # may also be made use of, like e.g. in ${url}?polar_interceptor_was_here=true – this may survive further URL modifications since it's technically a correct URL, but I wonder if some URLs are required to not have any appendices like that by e.g. the masterportalAPI?
  • ${scheme}${subdomain}POLAR_INTERCEPTOR.${domain} would hide that as an additional subdomain, and I'm pretty sure it will survive all underlying requests there until fetch is finally used.

Then, in the interceptor, the flag is removed before the real URL is used with the Auth header. This way all fetch calls of the outlying application to the URL are unmodified, and all of our own calls are modified.

That or you don't override all original headers and only add Authorization when it's not there already.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've adjusted the configuration so that the name of the token is also configurable.
The changes to the headers is also implemented. d94a7f8

Is it fine like this now or do you deem the marking of the URLs necessary as well?

Comment on lines 53 to 55
setCookie('name', account?.name)
setCookie('email', account?.email)
setCookie('username', account?.preferred_username)
Copy link
Member

@warm-coolguy warm-coolguy Feb 25, 2025

Choose a reason for hiding this comment

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

Why save this? We seem to just require the token. Probably illegal, too, even though we don't use it anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

Applications typically save these values, similarly to #249 (comment). We can keep the example simple you like

Copy link
Member

@warm-coolguy warm-coolguy Feb 26, 2025

Choose a reason for hiding this comment

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

Yes, please keep the example minimal. It will be a guideline for implementers on how to achieve things, and they may mimic this and later wonder what to do with the variables.

Copy link
Member Author

Choose a reason for hiding this comment

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

'email',
'expiry',
])
window.location.reload()
Copy link
Member

Choose a reason for hiding this comment

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

Is this an F5? Doesn't it completely eradicate all of the user's progress?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. But if a user would log out in an application that includes POLAR, this would be the case in most situation. Or am I on the wrong track?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, the method authenticate is also supposed to do the logout. I expected this to be a token refresh since it calls revokeToken that has grant_type=refresh_token hard-wired. Now that I look at it, the fetch also always uses the URL ''? Is this just unfinished? I guess we come back around to this when the other issues are resolved, the code may have changed until then anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can take another look regarding the grant_type (as in: is it the correct one).
The URL is the same thing as with the rest: I'll add this to the repository once we have one that can be put into OSS. Until then, use the URL I provided you through a save channel.

Copy link
Member

Choose a reason for hiding this comment

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

Oh right, I forgot that it's in the git patch.

Copy link
Member Author

@dopenguin dopenguin Feb 26, 2025

Choose a reason for hiding this comment

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

The url used ends with /revoke and the grant_type is also correct for this case.

@dopenguin
Copy link
Member Author

dopenguin commented Feb 25, 2025

🏓 @dopenguin Unfinished review, I'll add more later.

All in all, I do not see how an application is supposed to use us properly. It seems to me that we require both a configuration parameter for the token and an update mechanism to replace it in time that the login-managing application is required to call in time. Or did I just not see the idea here?

As mentioned in #249 (comment), the token requests should be done by the leading application and not POLAR.

🏓 @warm-coolguy

@warm-coolguy
Copy link
Member

🏓 @dopenguin Unfinished review, I'll add more later.
All in all, I do not see how an application is supposed to use us properly. It seems to me that we require both a configuration parameter for the token and an update mechanism to replace it in time that the login-managing application is required to call in time. Or did I just not see the idea here?

As mentioned in #249 (comment), the token requests should be done by the leading application and not POLAR.

🏓 @warm-coolguy

🏓 @dopenguin

dopenguin and others added 3 commits February 26, 2025 17:32
Also, the Authorization header is only overwritten if it is not present
yet.
@dopenguin
Copy link
Member Author

🏓 @dopenguin Unfinished review, I'll add more later.
All in all, I do not see how an application is supposed to use us properly. It seems to me that we require both a configuration parameter for the token and an update mechanism to replace it in time that the login-managing application is required to call in time. Or did I just not see the idea here?

As mentioned in #249 (comment), the token requests should be done by the leading application and not POLAR.
🏓 @warm-coolguy

🏓 @dopenguin

Except #249 (comment) everything should be g2g now.

🏓 @warm-coolguy

@warm-coolguy
Copy link
Member

🏓 @dopenguin Unfinished review, I'll add more later.
All in all, I do not see how an application is supposed to use us properly. It seems to me that we require both a configuration parameter for the token and an update mechanism to replace it in time that the login-managing application is required to call in time. Or did I just not see the idea here?

As mentioned in #249 (comment), the token requests should be done by the leading application and not POLAR.
🏓 @warm-coolguy

🏓 @dopenguin

Except #249 (comment) everything should be g2g now.

🏓 @warm-coolguy

Alright. Since that's a pretty fundamental thing, I'll wait for the resolution to that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants