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

[Networking] - useAPIClients Init Custom Hook + Auth changes to enable mutations/subscriptions #1068

Open
wants to merge 10 commits into
base: drew-hart--auth-federatedsignin
Choose a base branch
from

Conversation

drewjhart
Copy link
Contributor

@drewjhart drewjhart commented May 1, 2024

This completes the work on the Auth layer. For a full description of this layer, as well as an overview of the new API client and a step-by-step review of the setup process, see the below doc in the Wiki:

https://github.com/rightoneducation/righton-app/wiki/Backend-%E2%80%90-Authentication-and-Authorization

The PRs break down to:
-> [Backend] - Google OAuth Integration #988
->-> [Central_v2] - Container for authorization #1013
->->-> [Authentication/Authorization] - Networking updates #1058
->->->-> [Networking] - Upgrade Amplify v5 to v6 #1059
->->->->-> [Networking] - AuthAPIClient #1060
->->->->->-> [Networking] - useAPIClients Init Custom Hook + Auth changes to enable mutations/subscriptions

Review Cheat Sheet
Hey @maniramezan , this set of PR's is a bit confusing for two reasons:

  1. A lot of the file changes you are seeing will be auto-generated from Amplify (resolvers and config files etc)
  2. A lot of the previous PRs have initial implementations of Auth methods that are refined in later PRs

Therefore:

  1. I'd focus primarily on this PR as the main PR to review. This represents the final AuthAPIClient and the initialization and deployment of it throughout the code (using the custom hook). Implementations in previous PRs are overwritten in this one.
  2. I've added a bunch of comments throughout this PR to explain how this works and the set up of it.
  3. If you want insight into the actual auth setup, I've created a Wiki document here: https://github.com/rightoneducation/righton-app/wiki/Backend-%E2%80%90-Authentication-and-Authorization

Hopefully, this makes your job a bit easier when reviewing this stuff. Definitely let me know if anything is confusing though!

@drewjhart drewjhart changed the title Dev release 05/01/2024 WIP May 1, 2024
@drewjhart drewjhart changed the base branch from main to drew-hart--auth-federatedsignin May 1, 2024 15:17
@drewjhart drewjhart changed the title WIP [Backend] - Auth changes to enable mutations/subscriptions May 8, 2024
@drewjhart drewjhart changed the title [Backend] - Auth changes to enable mutations/subscriptions [Networking] - Auth changes to enable mutations/subscriptions May 8, 2024
@drewjhart drewjhart changed the title [Networking] - Auth changes to enable mutations/subscriptions [Networking] - useAuthAPI Custom Hook + Auth changes to enable mutations/subscriptions May 9, 2024
@drewjhart drewjhart changed the title [Networking] - useAuthAPI Custom Hook + Auth changes to enable mutations/subscriptions [Networking] - useAPIClients Init Custom Hook + Auth changes to enable mutations/subscriptions May 9, 2024
@@ -27,18 +27,20 @@ const theme = createTheme({

function App() {
const [alert, setAlert] = useState<Alert | null>(null);
const apiClients = new APIClients(Environment.Developing);
const alertContext = {
alert,
setAlert,
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

useApiClient custom hook for initialization of AuthAPIClient first, and then other APIClients

@@ -4,7 +4,7 @@ import { styled } from "@mui/system";
import { Link } from "react-router-dom";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

central_v2 will be reworked in a subsequent set of PRs in Q3 (Design is still working on it)

@@ -0,0 +1,5 @@
{
Copy link
Contributor Author

@drewjhart drewjhart May 9, 2024

Choose a reason for hiding this comment

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

this role is required to ensure that the createGame Lambda function is explicitly provided with its Execution Role. See the Wiki article for a link to the AWS docs

@@ -7,12 +7,12 @@ $util.qr($ctx.stash.put("hasAuth", true))
$util.unauthorized()
#end
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 think there is a bug with Amplify that requires us to manually set these resolvers. Pretty annoying so I'll talk to our AWS contact next time. See Wiki for a link to an Amplify issue that confirms this and recommends the below edits to resolve

Copy link
Collaborator

Choose a reason for hiding this comment

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

aww yeah this sounds like a bug as these are auto-gen and we'd need to keep updating them after each code changes

@@ -1,10 +1,18 @@
type Mutation {
createGameSessionFromTemplate(input: CreateGameSessionFromTemplateInput!): String @function(name: "createGame-${env}")
createGameSessionFromTemplate(input: CreateGameSessionFromTemplateInput!): String
Copy link
Contributor Author

@drewjhart drewjhart May 9, 2024

Choose a reason for hiding this comment

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

@auth directive required for custom mutations and subscriptions as well as the @types

@@ -44,11 +45,13 @@ export interface IQueryParameters {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pass AuthAPIClient to other APIClients to provide auth to the API requests

@@ -0,0 +1,25 @@
import { useState, useEffect } from 'react';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

custom Hook to provide proper sequence for APIClient init. First init AuthAPIClients and then pass those clients down into the other APIClients

Copy link
Collaborator

@maniramezan maniramezan left a comment

Choose a reason for hiding this comment

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

Great job on this, this looks great, and I love how you have organized the files and methods ❤️

@@ -7,12 +7,12 @@ $util.qr($ctx.stash.put("hasAuth", true))
$util.unauthorized()
#end
Copy link
Collaborator

Choose a reason for hiding this comment

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

aww yeah this sounds like a bug as these are auto-gen and we'd need to keep updating them after each code changes


async function createAndSignRequest(query, variables) {
const credentials = await defaultProvider()();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two API calls here? If so, makes sense to break it into two lines IMO. But if it's a common pattern in JS, then lets leave it

Comment on lines +38 to +48
private constructor(env: Environment, authClient: IAuthAPIClient) {
this.auth = authClient;
this.gameTemplate = new GameTemplateAPIClient(env, this.auth);
this.questionTemplate = new QuestionTemplateAPIClient(env, this.auth);
this.gameQuestions = new GameQuestionsAPIClient(env, this.auth);
this.gameSession = new GameSessionAPIClient(env, this.auth);
this.question = new QuestionAPIClient(env, this.auth);
this.team = new TeamAPIClient(env, this.auth);
this.teamMember = new TeamMemberAPIClient(env, this.auth);
this.teamAnswer = new TeamAnswerAPIClient(env, this.auth);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this list grows or becomes hard to maintain, we can probably write a script to auto-gen this and this whole file actually. Following the name XXXAPIClient for any API client would help with this as the script could just find all XXXAPIClient files and then generate this file and include one line for each. So lets make sure we follow the same naming pattern for any new api client we might add in future

Comment on lines +49 to +52
static async create(env: Environment): Promise<APIClients> {
const authClient = new AuthAPIClient();
await authClient.init(); // Ensure the auth client is initialized
return new APIClients(env, authClient);
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm, wondering if we should do lazy loading / singleton here instead of create method as create can be called multiple times to create new instances which I doubt it's what we want. This is very minor feedback so we can figure it out later as some also suggest against singleton pattern when possible

@@ -57,7 +60,21 @@ export abstract class BaseAPIClient {
query: any,
options?: GraphQLOptions
): Promise<GraphQLResult<T>> {
const response = client.graphql({query: query, variables: options, authMode: "userPool" as GraphQLAuthMode}) as unknown;
const authMode = this.auth.isUserAuth ? "userPool" : "iam";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Always recommended to use an enum with associated string values, especially in cases like this that same strings are referenced in multiple locations. Other pattern is just creating a static const variable and then use that. This reduces typo issues and also if you need to add any new cases or something, you have one place to add and then compiler would throw an error

Copy link
Collaborator

Choose a reason for hiding this comment

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

Another comment here is that maybe we can create a method that returns authMode, something like:

const getAuthMode => {
    this.auth.isUserAuth ? "userPool" : "iam";
}

I know it's juts a one liner abstraction, but I think it makes the code more readable and moves the focus to what the actual method is doing rather than thinking why authMode is userPool vs. iam etc

configAmplify(awsconfig: any): void {
Amplify.configure(awsconfig);
// change userPools auth storage to cookies so that auth persists across central/host apps for signed-in teachers
cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage());
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm, I need to read on it, but looks like there're some other storages as well like browser storage or session storage. I don't think we've tested our product in cookie-less environment, but worth to think about what would happen if cookie is disabled in browser. I remember back in the day, some were disabling it for security reasons or tracking purposes.

Comment on lines +50 to +51
const authClient = new AuthAPIClient();
await authClient.init(); // Ensure the auth client is initialized
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not combining these together and create a method like AuthAPIClient.create? That way, we can mark the constructor as private so it indicates that `AuthClient can't be instantiated through constructor

@@ -1,3 +1,4 @@
import { Subscription } from 'rxjs'; // this is used in aws-amplify to handle subscriptions
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice find!

): Promise<any> {
) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Intentionally removing the return type?

@@ -1,6 +1,7 @@

export interface IAuthAPIClient {
isUserAuth: boolean;
init(): Promise<void>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

See the comment above regarding combining this with constructor into one method

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