Skip to content

Conversation

@rtavernaea
Copy link
Collaborator

https://edanalytics.atlassian.net/browse/EDFIAL-352 and https://edanalytics.atlassian.net/browse/EDFIAL-353

  • This PR doesn't actually add roles yet, since we will need to separate out login strategies in order to handle this
  • Adds privileges getter to dto that will assign privileges given a role
  • AuthorizedGuard will error if user doesn't have permission for a given route defined in the decorator
    • I copied over all the logging behavior as is from UM. But I know we talked about it being a lot of logging every time no privilege is defined for a route (since most will not have any at first). Thoughts on removing for now and adding it back in once more routes have a privilege defined in auth decorator?

original PR with comments https://github.com/edanalytics/runway_old/pull/107/files

Copy link
Collaborator

@edandylytics edandylytics left a comment

Choose a reason for hiding this comment

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

Tests look good! There's a couple small issues and some cleanup.

@Type(() => GetTenantDto)
tenant: Tenant;
@Expose()
roles: AppRoles[];
Copy link
Collaborator

Choose a reason for hiding this comment

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

When we put this change in, existing sessions won't have a roles property. Let's have the type reflect that. It'll help us avoid runtime errors related to assuming roles exists when it might not.

roles: AppRoles[];
get privileges() {
return new Set<PrivilegeKey>(
...this.roles.flatMap((role) => (role in rolePrivileges ? rolePrivileges[role] : []))
Copy link
Collaborator

@edandylytics edandylytics Jan 12, 2026

Choose a reason for hiding this comment

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

I misled you in an earlier conversation; this doesn't work quite like what we want.

We want to create a set of privileges that is the union of the privileges associated with each role the user has. new Set(...) takes an iterable and adds each value from the iterable to the set. So we want initialization to look like new Set([privilegeA, privilegeB, ...etc]).

But the spread operator on the flatMap instead results in this new Set( Set(<privileges from role 1>), Set(<privileges from role 2>), ...) Values from the first argument are added to the resulting set, which is why it appears to work. But if we had multiple roles, the privileges form other roles would be ignored.

I think if we remove the spread operator from flatMap and convert rolePrivileges[role] to an array (it starts out as a set) it'll work fine.

});
it('should reject requests from user without the PartnerAdmin role', async () => {
const resA = await request(app.getHttpServer())
.post('/partners/assessments/test')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Tests look good, but now that we have the new controller, lets' move them to a separate file

Copy link
Collaborator

@edandylytics edandylytics left a comment

Choose a reason for hiding this comment

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

Overall, looks good. One small change to test cleanup and we should be good. Nice job slimming down the auth logic -- much more straightforward now.


afterEach(async () => {
await sessionStore.destroy(sessA.sid);
await jest.restoreAllMocks();
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should avoid restoreAllMocks. There are some mocks used when we bootstrap the app that are needed in order to prevent the app from relying on external services. restoreAllMocks will remove those and that could lead to flaky tests.

Instead, we can do this:

// when you create the mock, save a reference to a variable initialized outside the beforeEach
mockGetBundles = jest.spyOn(EarthbeamBundlesService.prototype, 'getBundles').mockResolvedValue(allBundles);

// then, in your afterEach, use that reference to restore just the one mock this file touches
mockGetBundles.mockRestore()

FWIW, we use restoreAllMocks in other test files. I did a commit yesterday to move these to the pattern above.

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