Skip to content

task(settings): Fix type safety on data.integration #18671

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

dschom
Copy link
Contributor

@dschom dschom commented Apr 4, 2025

Because

  • data.integration was not typesafe!
  • We'd like this to be typesafe.

This pull request

  • Cleans up inheritance hierarchy. The integration-base class was redundant.
  • Adds generic TData, so child classes can specify the data they have
  • Updates tests accordingly
  • Relaxes validation check on access, in general we don't need to run this on each access.

Issue that this pull request solves

Closes: FXA-8696

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

It's important that integration data is strongly typed. Without this it's pretty easy for mistakes to occur. It could also explain why don't have a consistent access layer. Note, this came up while working on FXA-11297, and I'm still not entirely sure what the best approach here is.

@dschom dschom requested review from a team as code owners April 4, 2025 00:22
@dschom dschom force-pushed the settings-make-data-integration-typesafe branch 3 times, most recently from d68b434 to 4d5a565 Compare April 8, 2025 02:11
@dschom dschom force-pushed the settings-make-data-integration-typesafe branch 10 times, most recently from 21feaab to 50ca9d5 Compare April 15, 2025 16:50

await expect(page).toHaveURL(/settings/);
await expect(page).toHaveURL(/signin/);
await expect(signin.badRequestHeading).toBeVisible();
Copy link
Contributor

Choose a reason for hiding this comment

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

Just confirming — are we intentionally changing this to throw an error, versus:

  • The previous behavior where the invalid param was ignored, or

  • Stripping out the invalid param?

If so, the test description should probably be updated to reflect that expectation more clearly.

Some additional testing observations:

  • This param check currently doesn't trigger an error until the confirm signup_code page in the signup flow. Should we consider validating earlier — e.g., at the entry point like email-first? Ideally, we'd catch invalid redirectTo values as early as possible and either error immediately or sanitize them.

  • In the previous test case, adding redirect_to=https://evil.com/ during the signup flow triggers a banner error after code submission on the confirm_signup_code page. That suggests a difference in validation layers — it looks like the param passed general validation but failed webRedirectCheck. + outcome is different depending on the flow:

    • during signin the invalid param is ignored but the user can continue
    • during signup there is an error message and the user cannot continue

image

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 param check currently doesn't trigger an error until the confirm signup_code page in the signup flow. Should we consider validating earlier — e.g., at the entry point like email-first? Ideally, we'd catch invalid redirectTo values as early as possible and either error immediately or sanitize them.

Yes, I think we should shift catching / checking required parameters to the 'entry points' as we have done with email first and a couple other pages. I'm not 100% sure this approach is always viable, but I think we should at least strive to support it. IMO we should be able to determine whats required, because after all if we can't specify and validate the query parameters, how would an RP know what they need to pass us?

I'm not sure if we should do these shifts in this PR. They seem like good follow up tasks though.

In the previous test case, adding redirect_to=https://evil.com/ during the signup flow triggers a banner error after code submission on the confirm_signup_code page

Yep, I think we should probably beef up the validation on redirect_to. Let me look into this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree to this being a follow-up/clean-up task.

@@ -114,16 +114,6 @@ describe('bind-decorator', function () {
}).toThrow();
});

it('throws on access of a invalid state', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the new expectation on access of an invalid state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at how the code ended up being used, I think throwing on access was causing problems, and there were artifacts in the code of trying to work around this. i.e. There were usages of getData on the data model directly instead of going through a property with @bind()... Also validating on every read is kind of heavy handed and might cause a lot of overhead... hindsight being 20/20.

I think we are generally making a shift towards validating early and failing hard / fast strategically at the entry points of our application. This is at odds with initial approach which was more optimistic about data being passed correctly, and a bit more laissez-faire about throwing errors on data access.

The new approach feels more rigid to me, but probably results in a better user experience in the end. Therefore, as long as we can be consistent validating at the start of the flow, I'm think the more pragmatic way to handle validation is:

  • Validate after a model is first created. ie Create the new model instance bound to URL or whatever underlying data source, and then call model.validate() immediately after instantiation. This would be the case regardless of if we are initializing integration data model, or query parameter models. Doing this insures the initial state of the model is valid.
  • Validation will still run when data is set on the model. Writing data happens much less frequently, and doing this allows us to be confident the model state is valid. The only caveat is if someone goes behind the models back and manipulates the data source directly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation, that sounds good to me. I was wondering if we needed to replace the test with a new expectation but sounds like no 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One thing to keep an eye on now is our entry point validation. With these changes it becomes more important that we get those entry point validations right. In addition to not checking on read, the integration model validation no longer has 'required' fields. This is in response to the comment in FXA-8696 that states this expectation:

We should be able to redefine the validation criteria (for example changing from optional to required) on a per-page basis without encountering an uncaught error.

@bind(T.snakeCase)
redirectTo: string | undefined;

// TODO: Double check context. This might be sync data!
Copy link
Contributor

Choose a reason for hiding this comment

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

Context is required for sync (at least, for oauth_desktop_v1). If signing in to sync and context is omitted, I see this popup:

Screenshot 2025-04-15 at 12 11 52 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we add this logic to query param model?

@IsOptional()
@IsIn(['offline', 'online'])
@bind(T.snakeCase)
accessType: string | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is omitted for sync sign in, browser sign in is not complete:
image

Sync provides a response_type param as well, though we don't seem to use it in fxa-settings.

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 guess same question again, should we add it to our entry point query parameter validation?

Copy link
Contributor Author

@dschom dschom Apr 17, 2025

Choose a reason for hiding this comment

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

Concerning response_type, is that an oversight in settings? Seems like auth-client could take it as option here...

https://github.com/mozilla/fxa/blob/main/packages/fxa-settings/src/lib/oauth/hooks.tsx#L130

https://github.com/mozilla/fxa/blob/main/packages/fxa-auth-client/lib/client.ts#L2005

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably a good idea to validate on entrypoint in case we're provided with nonsense/invalid values for either of these params? Not sure if we should error of fallback to the defaults though.

response_type: Joi.string()
  .valid('code')
  .default('code')
  .description(DESCRIPTION.responseType),
  responseType:
    "Determines the format of the response. 
    Since we only support the authorization-code grant flow, 
    the only permitted value is 'code'.",
accessType:
    'If specified, a value of `offline` will cause the connecting client 
    to be granted a refresh token alongside its access token.',
access_type: Joi.string()
  .valid('offline', 'online')
  .default('online')
  .description(DESCRIPTION.accessType),

@@ -131,13 +130,15 @@ export const App = ({
let isValidSession = false;

// Request and update account data/state to match the browser state.
// If there is a user actively signed into the browser,
// When we are accessing FxA from the browser menu or the user is going through
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this comment from a merge conflict resolution? Currently there is no isWebChannelIntegration flag used here

@@ -180,16 +180,22 @@ export function tryAgainError() {
*/
export function useFinishOAuthFlowHandler(
authClient: AuthClient,
integration: Pick<Integration, 'clientInfo' | 'type' | 'wantsKeys' | 'data'>
// TODO: Make this type safe by requiring an OAuthIntegration be provided.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a follow-up todo?

@dschom dschom force-pushed the settings-make-data-integration-typesafe branch 3 times, most recently from cbc8058 to fa138e6 Compare April 17, 2025 23:27
Because:
- We want to make sure integration.data is typesafe

This Commit:
- Adds a generic type, TData, so integration.data is now strongly typed.
- Cleans up messy class inheritance. We no longer have the 'base-integration' class.
- Cleans up mocks in mocks.tsx and makes sure they semantically aligned with the mocking function's name.
- Moves integration data to a single file
- Removes unused classes
  - channel-info
  - client-info
- Makes all data model fields optional. Required fields are now controlled by query param models that are checked on specific entrypoint pages.
@dschom dschom force-pushed the settings-make-data-integration-typesafe branch from fa138e6 to 91ae90c Compare April 17, 2025 23:50
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.

3 participants