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

add organization-id to login process #55

Merged
merged 1 commit into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { 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 @@ -62,11 +86,11 @@

<div className="mt-6 flex justify-between gap-2">
<Typography variant="small" className="font-medium text-gray-900">
<a href="#">Forgot Password?</a>

Check warning on line 89 in app/routes/_auth.login.($id).tsx

View workflow job for this annotation

GitHub Actions / run

The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value. If you cannot provide a valid href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md
</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 Expand Up @@ -140,7 +164,7 @@
</div>
</div>
<div className="hidden h-full w-2/5 lg:block">
<img

Check warning on line 167 in app/routes/_auth.login.($id).tsx

View workflow job for this annotation

GitHub Actions / run

img elements must have an alt prop, either with meaningful text, or an empty string for decorative images
src="/images/pattern.png"
className="h-full w-full rounded-3xl object-cover"
/>
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 @@ -204,7 +204,7 @@
className="flex items-center font-normal"
>
I agree the
<a

Check warning on line 207 in app/routes/_site._index.tsx

View workflow job for this annotation

GitHub Actions / run

The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value. If you cannot provide a valid href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md
href="#"
className="font-medium transition-colors hover:text-gray-900"
>
Expand All @@ -229,107 +229,3 @@
}

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
Loading