diff --git a/app/@types/graphql/schema.ts b/app/@types/graphql/schema.ts index 5774b36..af22601 100644 --- a/app/@types/graphql/schema.ts +++ b/app/@types/graphql/schema.ts @@ -574,6 +574,14 @@ export type Order = { field: Scalars['String']['input']; }; +export type Organization = { + __typename?: 'Organization'; + id: Scalars['Int']['output']; + isPersonal: Scalars['Boolean']['output']; + name?: Maybe; + userId?: Maybe; +}; + /** Information about pagination in a connection. */ export type PageInfo = { __typename?: 'PageInfo'; @@ -783,6 +791,7 @@ export type ReportUpdatePayload = { export type UserInfo = { __typename?: 'UserInfo'; Individual?: Maybe; + Organization?: Maybe; UserId: Scalars['Int']['output']; }; @@ -1097,7 +1106,7 @@ export type VisionFragmentFragment = { __typename?: 'Vision', id: number, vision export type GetLoggedInUserInfoQueryVariables = Exact<{ [key: string]: never; }>; -export type GetLoggedInUserInfoQuery = { __typename?: 'Query', myInfo: { __typename?: 'UserInfo', UserId: number, Individual?: { __typename?: 'Individual', id: number, isManager: boolean } | null } }; +export type GetLoggedInUserInfoQuery = { __typename?: 'Query', myInfo: { __typename?: 'UserInfo', UserId: number, Individual?: { __typename?: 'Individual', id: number, isManager: boolean, organizationId: number } | null } }; export const ActivityFragmentFragmentDoc = gql` fragment ActivityFragment on Activity { @@ -2117,6 +2126,7 @@ export const GetLoggedInUserInfoDocument = gql` Individual { id isManager + organizationId } } } diff --git a/app/entry.client.tsx b/app/entry.client.tsx index fecdebe..3af593d 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -21,17 +21,27 @@ function getToken() { const token = sessionStorage.getItem("token"); if (token == null) { - // throw new Error("Not Authorized!"); we should manage it. Throughing an error affects the whole page rendering process. + // throw new Error("Not Authorized!"); we should manage it. throwing an error affects the whole page rendering process. } return token; } +function getOrganizationId(): string { + const org_id = sessionStorage.getItem("organization_id"); + if (org_id == null) { + // throw new Error("Not Authorized!"); we should manage it. throwing an error affects the whole page rendering process. + return ""; + } + return org_id; +} + startTransition(() => { const client = new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), uri: process.env.GRAPHQL_SCHEMA_URL || "GRAPHQL_SCHEMA_URL IS NOT SET", // the same uri in our entry.server file headers: { Authorization: `Bearer ${getToken()}`, + "X-Org-Id": getOrganizationId(), }, defaultOptions: { query: { diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 8f0816f..b8cbc25 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -177,5 +177,9 @@ async function wrapRemixServerWithApollo( async function getApolloClient(request: Request) { let user = await authenticator.isAuthenticated(request); - return utils.getApolloClient(request, user?.jwt_token); + return utils.getApolloClient( + request, + user?.jwt_token, + user?.organization_id.toString(), + ); } diff --git a/app/models/user.ts b/app/models/user.ts index 8545005..5f43fb4 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -4,5 +4,6 @@ export type User = { name: string; user_id: number; individual_id?: number; + organization_id: number; is_manager?: boolean; }; diff --git a/app/routes/_auth.login.tsx b/app/routes/_auth.login.($id).tsx similarity index 89% rename from app/routes/_auth.login.tsx rename to app/routes/_auth.login.($id).tsx index 36d2a2e..702afe8 100644 --- a/app/routes/_auth.login.tsx +++ b/app/routes/_auth.login.($id).tsx @@ -3,6 +3,30 @@ import { Form } from "@remix-run/react"; import { Input, Button, Typography } from "@material-tailwind/react"; import { Link } from "react-router-dom"; +import { LoaderFunctionArgs, json } from "@remix-run/node"; + +export let loader = ({ params }: LoaderFunctionArgs) => { + const organization_id = params.id; + + return json( + {}, + { + headers: { + ...getAuthenticationOrganizationCookie(organization_id), + }, + }, + ); +}; + +function getAuthenticationOrganizationCookie(organization_id?: string) { + // TODO: we later remove this as we get the org by subdomain or even the email addrees + return { + "Set-Cookie": `auth_organization_id=${organization_id}; Path=/; ${ + organization_id ? "" : "Max-Age=0" + }`, + }; +} + export function SignIn() { return (
@@ -66,7 +90,7 @@ export function SignIn() {
-
+ -//
-//
-// )} -// {" "} -// -// -// -// -// -// ); -// } diff --git a/app/routes/auth.google.tsx b/app/routes/auth.google.tsx index 21f50e4..0e818a4 100644 --- a/app/routes/auth.google.tsx +++ b/app/routes/auth.google.tsx @@ -1,8 +1,6 @@ import { type ActionFunctionArgs } from "@remix-run/node"; import { authenticator } from "~/services/auth.server"; -// export let loader = () => redirect("/login"); - export async function action({ request }: ActionFunctionArgs) { return authenticator.authenticate("google", request, { successRedirect: "/individuals", diff --git a/app/services/auth.strategies/google.ts b/app/services/auth.strategies/google.ts index 476005f..c812442 100644 --- a/app/services/auth.strategies/google.ts +++ b/app/services/auth.strategies/google.ts @@ -2,10 +2,12 @@ import { GetLoggedInUserInfoDocument, GetLoggedInUserInfoQuery, } from "@app-types/graphql"; + import { AuthorizationError } from "remix-auth"; import { GoogleStrategy } from "remix-auth-google"; import type { User } from "~/models/user"; import { getApolloClient } from "~/utils"; +import cookie from "cookie"; export let googleStrategy = new GoogleStrategy( { @@ -13,30 +15,46 @@ export let googleStrategy = new GoogleStrategy( clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "", callbackURL: "/auth/google/callback", }, - async ({ accessToken, refreshToken, extraParams, profile, request }) => { + async ({ + accessToken, + refreshToken, + extraParams, + profile, + request, + context, + }) => { // Get the user data from your DB or API using the tokens and profile const jwt_token = extraParams.id_token; + const cookieString = request.headers.get("cookie") ?? ""; + const organization_id = cookie.parse(cookieString).auth_organization_id; //we should not use this temporary cookie anywhere else + try { - const client = getApolloClient(request, jwt_token); + const client = getApolloClient(request, jwt_token, organization_id); const result = await client.query({ query: GetLoggedInUserInfoDocument, fetchPolicy: "network-only", }); const myInfo = result.data?.myInfo; - if (myInfo === undefined || result.error || result.errors) + if ( + myInfo === undefined || + result.error || + result.errors || + myInfo.Individual == null + ) throw new Error("No user info found"); - const individual = myInfo?.Individual; + const individual = myInfo.Individual; return { email: profile.emails[0].value, jwt_token: jwt_token, name: profile.displayName, - individual_id: individual?.id, + individual_id: individual.id, user_id: myInfo.UserId, - is_manager: individual?.isManager, + is_manager: individual.isManager, + organization_id: individual.organizationId, } as User; } catch (error: any) { const msg = diff --git a/app/services/auth.strategies/graphql/getLoggedInUserInfo.gql b/app/services/auth.strategies/graphql/getLoggedInUserInfo.gql index f3a62fc..236e9b4 100644 --- a/app/services/auth.strategies/graphql/getLoggedInUserInfo.gql +++ b/app/services/auth.strategies/graphql/getLoggedInUserInfo.gql @@ -4,6 +4,7 @@ query getLoggedInUserInfo { Individual { id isManager + organizationId } } } diff --git a/app/utils/graphql.ts b/app/utils/graphql.ts index 2f273be..8423dc8 100644 --- a/app/utils/graphql.ts +++ b/app/utils/graphql.ts @@ -1,11 +1,20 @@ import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client"; -export function getApolloClient(request: Request, token: string | undefined) { +export function getApolloClient( + request: Request, + token: string | undefined, + organization_id: string | undefined, +) { + const organization_header = organization_id + ? { "X-Org-Id": organization_id } + : null; + const linkSettings = { uri: process.env.GRAPHQL_SCHEMA_URL || "GRAPHQL_SCHEMA_URL IS NOT SET", headers: { // ...Object.fromEntries(request.headers), it is not a good way. It will cause in deployment on render.com. Authorization: `Bearer ${token ?? "ERROR TOKEN!"}`, + ...organization_header, }, credentials: "include", // or "same-origin" if your backend server is the same domain }; diff --git a/package.json b/package.json index 87d86d8..991893b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@remix-run/node": "2.2.0", "@remix-run/react": "2.2.0", "@remix-run/serve": "2.2.0", + "cookie": "^0.6.0", "date-fns": "^3.0.6", "dotenv": "^16.3.1", "graphql": "^16.8.1", diff --git a/yarn.lock b/yarn.lock index 703733d..1bcdb2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3435,6 +3435,11 @@ cookie@^0.4.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz"