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

fix(api): ApiKey auth guard performance #4972

Merged
merged 41 commits into from
Dec 13, 2023

Conversation

rifont
Copy link
Contributor

@rifont rifont commented Dec 11, 2023

What change does this PR introduce?

  • Update auth service to return expected auth validation User entity
  • Authentication strategy
    • Add passport apikey strategy
    • Remove redundant logger assign in jwt strategy
  • Guards/decorator tidy-up - removing redundant JWT check
    • Roles guard
    • RootEnvironment guard
    • UserSession decorator
  • Use req.user payload in:
    • throttler guard
    • idempotency interceptors
  • Remove redundant logger.assign in trigger use-case

Why was this change needed?

API requests using ApiKey authentication currently sign and decode a JWT token every request. The signing introduces considerable latency whilst the decoding adds unnecessary memory consumption.

We also needed an interface to retrieve User context from the request, regardless of auth scheme.

Other information (Screenshots)

Auth Guard Tests
image

ApiKey Auth Log
image

Bearer Auth Log
image

No Auth Log
image

Copy link

linear bot commented Dec 11, 2023

@rifont rifont requested review from djabarovgeorge and LetItRock and removed request for djabarovgeorge December 11, 2023 13:04
@rifont rifont changed the title Nv 3055 fix apikey auth guard performance fix(api): ApiKey auth guard performance Dec 11, 2023

const AUTH_STRATEGIES: Provider[] = [];
const AUTH_STRATEGIES: Provider[] = [JwtStrategy, ApiKeyStrategy, JwtSubscriberStrategy];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving the passport strategies out to where they belong.

const user = jwt.decode(token) as IJwtPayload;
if (!user) return false;
}

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 handled in the Authentication flow, and shouldn't be part of Authorization.


const user = jwt.decode(token) as IJwtPayload;
if (!user) return false;
if (!user.environmentId) return false;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As above, this is handled in the Authentication flow, and shouldn't be part of Authorization.

environmentId: payload.environmentId,
organizationId: payload.organizationId,
});

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 now handled in a single place in the Auth Guard, for all Auth strategies.


return jwt.decode(token);
return req.user;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

req.user is always present now.

const { environment, user, key, error } = await this.getUserData({
apiKey,
});
public async validateApiKey(apiKey: string): Promise<IJwtPayload> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making sure the ApiKey validation returns the same interface as Bearer authentication.

}
);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not used.

})
private async getEnvironment({ apiKey }: { apiKey: string }) {
return await this.environmentRepository.findByApiKey(apiKey);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not used.


async verifyJwt(jwt: string) {
return this.jwtService.verify(jwt);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not used.


if (error) throw new UnauthorizedException(error);
if (!environment) throw new UnauthorizedException('API Key not found');
if (!user) throw new UnauthorizedException('User not found');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The if(error) check above already handles these exceptions.

Copy link
Contributor

@LetItRock LetItRock left a comment

Choose a reason for hiding this comment

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

looks good to me 💎 just the comment from Dima

Copy link
Contributor

@djabarovgeorge djabarovgeorge left a comment

Choose a reason for hiding this comment

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

Love how much the code became cleaner and optimized 🤩

The main concern i had was regarding auth.guard.ts file and the defaultStrategy

apps/api/src/app/auth/services/passport/apikey.strategy.ts Outdated Show resolved Hide resolved
});
}
/**
* This helps with sentry and other tools that need to know who the user is based on `id` property.
Copy link
Contributor

Choose a reason for hiding this comment

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

🚀

@rifont
Copy link
Contributor Author

rifont commented Dec 12, 2023

These changes broke a bunch of api-ee tests, they are all returning a 422 error.

It may be a result of the Idempotency changes, but could be something else. Further investigation needed.

@rifont
Copy link
Contributor Author

rifont commented Dec 13, 2023

These changes broke a bunch of api-ee tests, they are all returning a 422 error.

It may be a result of the Idempotency changes, but could be something else. Further investigation needed.

I have ruled out Idempotency issues.

It appears that the throttler guard is processing a different environmentId than the one present in the User Test session context. I suspect this may be related to changes in the ApiKey part of the Auth service. Continuing investigation there
image

@rifont
Copy link
Contributor Author

rifont commented Dec 13, 2023

These changes broke a bunch of api-ee tests, they are all returning a 422 error.
It may be a result of the Idempotency changes, but could be something else. Further investigation needed.

I have ruled out Idempotency issues.

It appears that the throttler guard is processing a different environmentId than the one present in the User Test session context. I suspect this may be related to changes in the ApiKey part of the Auth service. Continuing investigation there image

Turns out this was a simple fix. I'd changed the signature of the getApiKeyUser method and hadn't updated the CachedEntity signature to match (see the commit diff). This resulted in an undefined apiKey being passed to the CachedEntity builder, and all subsequent cache reads produced the same API key.
Making them match fixed all the test issues. cc @djabarovgeorge , related to what we'd discussed but unfortunately Typescript doesn't support decorator typings right now in the way we need to, for us to make this typing strict.

@rifont
Copy link
Contributor Author

rifont commented Dec 13, 2023

Added tests for both Bearer and ApiKey auth to make sure the auth logic and strategies are working as expected - 6ba5265

@rifont rifont merged commit e6fcfbf into next Dec 13, 2023
29 checks passed
@rifont rifont deleted the nv-3055-fix-apikey-auth-guard-performance branch December 13, 2023 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants