Skip to content

Commit

Permalink
feat: add cookies based session
Browse files Browse the repository at this point in the history
  • Loading branch information
raczu committed Jun 22, 2024
1 parent 30e857b commit d04da8a
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 4 deletions.
71 changes: 71 additions & 0 deletions Client/reasn-client/apps/web/lib/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import ApiAuthorizationError from "@reasn/common/src/errors/ApiAuthorizationError";
import ApiConnectionError from "@reasn/common/src/errors/ApiConnectionError";
import fetch from "cross-fetch";
import { getToken } from "@/lib/token";

export enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
}

export type ProblemDetails = {
type?: string;
title: string;
status: number;
details?: string;
instance?: string;
};

export const sendRequest = async <T>(
url: string,
httpMethod: HttpMethod,
body: Object = {},
authRequired: boolean = false,
): Promise<T> => {
try {
let headers: HeadersInit = new Headers();
if (authRequired) {
const token = getToken();
if (!token) {
throw new ApiAuthorizationError(
"Unauthorized access. No token found in cookies",
);
}
headers.set("Authorization", `Bearer ${token}`);
}

const fetchOptions: RequestInit = {
method: httpMethod,
headers,
};

if (httpMethod == HttpMethod.POST || httpMethod == HttpMethod.PUT) {
headers.set("Content-Type", "application/json");
fetchOptions.body = JSON.stringify(body);
}

const response = await fetch(url, fetchOptions);
if (!response.ok) {
const problemDetails = (await response.json()) as ProblemDetails;
console.error(
`[HTTP ${problemDetails.instance ?? ""} ${response.status}]: ${
problemDetails.details ?? problemDetails.title
}`,
);

throw new ApiConnectionError(
response.status,
problemDetails.details ?? problemDetails.title,
);
}

return (await response.json()) as T;
} catch (error) {
console.error(
`Error while sending request to ${url} with method ${httpMethod}: ${error}`,
);
throw Error;
}
};
51 changes: 51 additions & 0 deletions Client/reasn-client/apps/web/lib/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { jwtDecode, JwtPayload } from "jwt-decode";
import { UserRole } from "@reasn/common/src/enums/modelsEnums";
import { getToken } from "@/lib/token";

export const SESSION_DEFAULT = {
token: null,
isAuthenticated: () => false,
};

type User = {
email: string;
role: UserRole;
};

export type Session = {
token: string | null;
user?: User;
isAuthenticated: () => boolean;
};

interface ReasnJwtPayload extends JwtPayload {
email?: string;
role?: UserRole;
}

export const getSession = (): Session => {
const token = getToken();
if (!token) {
return SESSION_DEFAULT;
}

try {
const decodedToken = jwtDecode<ReasnJwtPayload>(token);
const isUserValid =
decodedToken.email !== undefined && decodedToken.role !== undefined;

return {
token: token,
user: isUserValid
? {
email: decodedToken.email as string,
role: decodedToken.role as UserRole,
}
: undefined,
isAuthenticated: () => token !== null && isUserValid,
};
} catch (error) {
console.error("[ERROR]: Failed to decode JWT token");
return SESSION_DEFAULT;
}
};
23 changes: 23 additions & 0 deletions Client/reasn-client/apps/web/lib/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { cookies } from "next/headers";
import { TokenPayload } from "@reasn/common/src/schemas/tokenPayload";

const TOKEN_KEY = "REASN_TOKEN";

export const setToken = (tokenPayload: TokenPayload): void => {
cookies().set(TOKEN_KEY, tokenPayload.accessToken, {
maxAge: tokenPayload.expiresIn,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
httpOnly: true,
});
};

export const clearToken = (): void => {
cookies().set(TOKEN_KEY, "", { maxAge: 0 });
};

export const getToken = (): string | null => {
const token = cookies().get(TOKEN_KEY)?.value;
if (!token) return null;
return token;
};
3 changes: 2 additions & 1 deletion Client/reasn-client/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"dependencies": {
"@reasn/common": "*",
"@reasn/ui": "*",
"next": "^14.1.1",
"jwt-decode": "^4.0.0",
"next": "^14.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native-web": "^0.19.10"
Expand Down
6 changes: 5 additions & 1 deletion Client/reasn-client/apps/web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{
"extends": "@reasn/typescript-config/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
"strictNullChecks": true,
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
Expand Down
1 change: 1 addition & 0 deletions Client/reasn-client/packages/ui/src/icons/Fire.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { IconProps } from "./IconProps";

export const Fire = (props: IconProps) => {
const { className, colors, gradientTransform } = props;
Expand Down
2 changes: 1 addition & 1 deletion Client/reasn-client/packages/ui/src/icons/IconProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type IconProps = {
export type IconProps = {
className?: string;
colors?: string[];
gradientTransform?: string;
Expand Down
10 changes: 9 additions & 1 deletion Client/reasn-client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11674,6 +11674,13 @@ __metadata:
languageName: node
linkType: hard

"jwt-decode@npm:^4.0.0":
version: 4.0.0
resolution: "jwt-decode@npm:4.0.0"
checksum: 10c0/de75bbf89220746c388cf6a7b71e56080437b77d2edb29bae1c2155048b02c6b8c59a3e5e8d6ccdfd54f0b8bda25226e491a4f1b55ac5f8da04cfbadec4e546c
languageName: node
linkType: hard

"keyv@npm:^4.5.3":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
Expand Down Expand Up @@ -17468,7 +17475,8 @@ __metadata:
eslint: "npm:^8.57.0"
eslint-config-next: "npm:14.0.4"
eslint-config-prettier: "npm:^9.1.0"
next: "npm:^14.1.1"
jwt-decode: "npm:^4.0.0"
next: "npm:^14.0.4"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
react-native-web: "npm:^0.19.10"
Expand Down

0 comments on commit d04da8a

Please sign in to comment.