Skip to content

Commit

Permalink
add organization-id to login process (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
mahmoudmoravej authored Feb 23, 2024
1 parent a0350c0 commit d9db563
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 119 deletions.
12 changes: 11 additions & 1 deletion app/@types/graphql/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Scalars['String']['output']>;
userId?: Maybe<Scalars['Int']['output']>;
};

/** Information about pagination in a connection. */
export type PageInfo = {
__typename?: 'PageInfo';
Expand Down Expand Up @@ -783,6 +791,7 @@ export type ReportUpdatePayload = {
export type UserInfo = {
__typename?: 'UserInfo';
Individual?: Maybe<Individual>;
Organization?: Maybe<Organization>;
UserId: Scalars['Int']['output'];
};

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -2117,6 +2126,7 @@ export const GetLoggedInUserInfoDocument = gql`
Individual {
id
isManager
organizationId
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 5 additions & 1 deletion app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
}
1 change: 1 addition & 0 deletions app/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export type User = {
name: string;
user_id: number;
individual_id?: number;
organization_id: number;
is_manager?: boolean;
};
26 changes: 25 additions & 1 deletion app/routes/_auth.login.tsx → app/routes/_auth.login.($id).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<section className="m-8 flex gap-4">
Expand Down Expand Up @@ -66,7 +90,7 @@ export function SignIn() {
</Typography>
</div>
<div className="mt-8 space-y-4">
<Form action="/auth/google" method="post">
<Form action={`/auth/google`} method="post">
<Button
size="lg"
color="white"
Expand Down
12 changes: 10 additions & 2 deletions app/routes/_dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,16 @@ export default function Dashboard() {
useEffect(() => {
//TODO: it is a temporary solution, we need to remove token at logout and also store token at login.

if (user) sessionStorage.setItem("token", user.jwt_token);
else sessionStorage.removeItem("token");
if (user) {
sessionStorage.setItem("token", user.jwt_token);
sessionStorage.setItem(
"organization_id",
user.organization_id?.toString() ?? "",
);
} else {
sessionStorage.removeItem("token");
sessionStorage.removeItem("organization_id");
}
});

return (
Expand Down
104 changes: 0 additions & 104 deletions app/routes/_site._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,107 +229,3 @@ export function Home() {
}

export default Home;
// import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
// import { Form, Link, useLoaderData } from "@remix-run/react";
// import { useEffect } from "react";
// import type { User } from "~/models/user";

// import { authenticator } from "~/services/auth.server";

// export const meta: MetaFunction = () => {
// return [
// { title: "New Remix App" },
// { name: "description", content: "Welcome to Remix!" },
// ];
// };

// export async function loader({ request }: LoaderFunctionArgs) {
// let user = await authenticator.isAuthenticated(request);
// return { user };
// }

// function useUser() {
// const data = useLoaderData<{ user?: User }>();
// return data.user;
// }

// export default function Index() {
// const user = useUser();

// useEffect(() => {
// //TODO: it is a temporary solution, we need to remove token at logout and also store token at login.

// if (user) sessionStorage.setItem("token", user.jwt_token);
// else sessionStorage.removeItem("token");
// });

// return (
// <main className="relative min-h-screen bg-white sm:flex sm:items-center sm:justify-center">
// <div className="relative sm:pb-16 sm:pt-8">
// <div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
// <div className="relative shadow-xl sm:overflow-hidden sm:rounded-2xl">
// <div className="absolute inset-0">
// <img
// className="h-full w-full object-cover"
// src="https://user-images.githubusercontent.com/1500684/157774694-99820c51-8165-4908-a031-34fc371ac0d6.jpg"
// alt="Sonic Youth On Stage"
// />
// <div className="absolute inset-0 bg-[color:rgba(254,204,27,0.5)] mix-blend-multiply" />
// </div>
// <div className="relative px-4 pb-8 pt-16 sm:px-6 sm:pb-14 sm:pt-24 lg:px-8 lg:pb-20 lg:pt-32">
// <h1 className="text-center text-6xl font-extrabold tracking-tight sm:text-8xl lg:text-9xl">
// <span className="block uppercase text-yellow-500 drop-shadow-md">
// Performa
// </span>
// </h1>
// <p className="mx-auto mt-6 max-w-lg text-center text-xl text-white sm:max-w-3xl">
// Check the README.md file for instructions on how to get this
// project deployed.
// </p>
// <div className="mx-auto mt-10 max-w-sm sm:flex sm:max-w-none sm:justify-center">
// {user ? (
// <>
// <p className="mx-auto mt-6 max-w-lg text-center text-xl text-white sm:max-w-3xl">
// Welcome {user.name}!
// </p>
// <div>
// <Link
// to="/managers"
// className="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-3 text-base font-medium text-yellow-700 shadow-sm hover:bg-yellow-50 sm:px-8"
// >
// Managers
// </Link>
// <Link
// to="/logout"
// className="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-3 text-base font-medium text-yellow-700 shadow-sm hover:bg-yellow-50 sm:px-8"
// >
// Sign out
// </Link>
// </div>
// </>
// ) : (
// <div className="mx-auto mt-6 max-w-lg text-center text-xl text-white sm:max-w-3xl">
// <Link
// to="/login"
// className="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-3 text-base font-medium text-yellow-700 shadow-sm hover:bg-yellow-50 sm:px-8"
// >
// Log in
// </Link>
// <Form action="/auth/google" method="post">
// <button>
// <img
// src="/images/btn_google_signin_light_normal_web.png"
// alt="login with google"
// />
// </button>
// </Form>
// </div>
// )}
// </div>{" "}
// </div>
// </div>
// </div>
// </div>
// </main>
// );
// }
2 changes: 0 additions & 2 deletions app/routes/auth.google.tsx
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
30 changes: 24 additions & 6 deletions app/services/auth.strategies/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,59 @@ 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(
{
clientID: process.env.GOOGLE_CLIENT_ID ?? "",
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<GetLoggedInUserInfoQuery>({
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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ query getLoggedInUserInfo {
Individual {
id
isManager
organizationId
}
}
}
11 changes: 10 additions & 1 deletion app/utils/graphql.ts
Original file line number Diff line number Diff line change
@@ -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
};
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit d9db563

Please sign in to comment.