diff --git a/packages/auth-common/package.json b/packages/auth-common/package.json index 06cd124..c0f3cbb 100644 --- a/packages/auth-common/package.json +++ b/packages/auth-common/package.json @@ -14,9 +14,7 @@ "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build:check": "tsc", "build:js": "vite build", @@ -27,8 +25,6 @@ "dev:types": "tsup --watch src", "dev": "npm-run-all clean --parallel dev:js dev:types", "lint": "biome lint src", - "start": "static-server dist --port 5173", - "test:watch": "vitest", - "test": "vitest run" + "start": "static-server dist --port 5173" } } diff --git a/packages/auth-common/src/components/index.ts b/packages/auth-common/src/components/index.ts index d863173..d3b2c40 100644 --- a/packages/auth-common/src/components/index.ts +++ b/packages/auth-common/src/components/index.ts @@ -1,3 +1,7 @@ export const AUTH_TYPES = { ID_TOKEN: "id_token", }; + +export const HEADERS = { + tenantId: "X-Auth-TenantId", +}; diff --git a/packages/auth-provider/package.json b/packages/auth-provider/package.json index 4e39761..8d42405 100644 --- a/packages/auth-provider/package.json +++ b/packages/auth-provider/package.json @@ -14,9 +14,7 @@ "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build:check": "tsc", "build:js": "vite build", diff --git a/packages/auth-provider/src/common/constants.ts b/packages/auth-provider/src/common/constants.ts new file mode 100644 index 0000000..40206cf --- /dev/null +++ b/packages/auth-provider/src/common/constants.ts @@ -0,0 +1,9 @@ +export const EXPIRED_SESSION = + "Oops! It looks like your session has expired. For your security, please log in again to continue."; +export const AUTH_CONTEXT_ERROR = + "You forgot to wrap your component in ."; + +export const API_ENDPOINT = { + dev: "https://auth.gizmette.local.com:3003", + prod: "https://auth.gizmette.com", +}; diff --git a/packages/auth-provider/src/common/types.d.ts b/packages/auth-provider/src/common/types.d.ts new file mode 100644 index 0000000..39ce4f0 --- /dev/null +++ b/packages/auth-provider/src/common/types.d.ts @@ -0,0 +1,29 @@ +export type ServiceCallProps = { + params: any; +}; + +export type AuthState = { + isAuthenticated: boolean; + idToken: string; + logoutReason: string; + userId: string; + accessToken?: string; + refreshToken?: string; +}; + +export type AuthProviderProps = { + children: React.ReactNode; + sessionExpiration?: string; + tenantId: string; + accessType?: string; +}; + +export type AuthContextProps = { + login: (username: string, password: string) => Promise; + logout: () => void; + isAuthenticated: boolean; + accessToken?: string; + refreshToken?: string; + idToken?: string; + logoutReason?: string; +}; diff --git a/packages/auth-provider/src/common/utilities.ts b/packages/auth-provider/src/common/utilities.ts new file mode 100644 index 0000000..84d9e58 --- /dev/null +++ b/packages/auth-provider/src/common/utilities.ts @@ -0,0 +1,45 @@ +import { HEADERS } from "@versini/auth-common"; +import { v4 as uuidv4 } from "uuid"; + +import { API_ENDPOINT } from "./constants"; +import type { ServiceCallProps } from "./types"; + +export const isProd = process.env.NODE_ENV === "production"; +export const isDev = !isProd; + +export const serviceCall = async ({ params = {} }: ServiceCallProps) => { + try { + const nonce = uuidv4(); + const response = await fetch( + isDev + ? `${API_ENDPOINT.dev}/authenticate` + : `${API_ENDPOINT.prod}/authenticate`, + { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json", + [HEADERS.tenantId]: `${params.tenantId}`, + }, + body: JSON.stringify({ ...params, nonce }), + }, + ); + + if (response.status !== 200) { + return { status: response.status, data: [] }; + } + const { data, errors } = await response.json(); + if (data.nonce !== nonce) { + return { status: 500, data: [] }; + } + + return { + status: response.status, + data, + errors, + }; + } catch (_error) { + console.error(_error); + return { status: 500, data: [] }; + } +}; diff --git a/packages/auth-provider/src/components/AuthProvider/AuthContext.ts b/packages/auth-provider/src/components/AuthProvider/AuthContext.ts index 4ad041c..ad81d47 100644 --- a/packages/auth-provider/src/components/AuthProvider/AuthContext.ts +++ b/packages/auth-provider/src/components/AuthProvider/AuthContext.ts @@ -1,20 +1,12 @@ import { createContext } from "react"; - -export type AuthContextType = { - login: (username: string, password: string) => Promise; - logout: () => void; - isAuthenticated: boolean; - accessToken?: string; - refreshToken?: string; - idToken?: string; - logoutReason?: string; -}; +import { AUTH_CONTEXT_ERROR } from "../../common/constants"; +import type { AuthContextProps } from "../../common/types"; const stub = (): never => { - throw new Error("You forgot to wrap your component in ."); + throw new Error(AUTH_CONTEXT_ERROR); }; -export const AuthContext = createContext({ +export const AuthContext = createContext({ isAuthenticated: false, login: stub, logout: stub, diff --git a/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx b/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx index f997817..74ee55a 100644 --- a/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx +++ b/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx @@ -1,76 +1,19 @@ import { AUTH_TYPES } from "@versini/auth-common"; import { useLocalStorage } from "@versini/ui-hooks"; -import { useEffect, useRef, useState } from "react"; -import { v4 as uuidv4 } from "uuid"; +import { useEffect, useState } from "react"; +import { EXPIRED_SESSION } from "../../common/constants"; +import type { AuthProviderProps, AuthState } from "../../common/types"; +import { serviceCall } from "../../common/utilities"; +import { usePrevious } from "../hooks/usePrevious"; import { AuthContext } from "./AuthContext"; -type AuthState = { - isAuthenticated: boolean; - idToken: string; - logoutReason: string; - userId: string; - accessToken?: string; - refreshToken?: string; -}; - -const EXPIRED_SESSION = - "Oops! It looks like your session has expired. For your security, please log in again to continue."; - -const serviceCall = async ({ params = {} }: { params?: any }) => { - try { - const nonce = uuidv4(); - const response = await fetch( - `${process.env.PUBLIC_AUTH_SERVER_URL}/authenticate`, - { - credentials: "include", - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Auth-TenantId": `${params.tenantId}`, - }, - body: JSON.stringify({ ...params, nonce }), - }, - ); - - if (response.status !== 200) { - return { status: response.status, data: [] }; - } - const { data, errors } = await response.json(); - if (data.nonce !== nonce) { - return { status: 500, data: [] }; - } - - return { - status: response.status, - data, - errors, - }; - } catch (_error) { - console.error(_error); - return { status: 500, data: [] }; - } -}; - -function usePrevious(state: T): T | undefined { - const ref = useRef(); - useEffect(() => { - ref.current = state; - }); - return ref.current; -} - export const AuthProvider = ({ children, sessionExpiration, tenantId, accessType, -}: { - children: React.ReactNode; - sessionExpiration?: string; - tenantId: string; - accessType?: string; -}) => { +}: AuthProviderProps) => { const [accessToken, setAccessToken, removeAccessToken] = useLocalStorage( `@@auth@@::${tenantId}::@@access@@`, "", diff --git a/packages/auth-provider/src/components/AuthProvider/__tests__/AuthProvider.test.tsx b/packages/auth-provider/src/components/AuthProvider/__tests__/AuthProvider.test.tsx new file mode 100644 index 0000000..bb5e17a --- /dev/null +++ b/packages/auth-provider/src/components/AuthProvider/__tests__/AuthProvider.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from "@testing-library/react"; +import { AuthProvider } from "../.."; + +describe("AuthProvider", () => { + it("renders children components", () => { + render( + +
Child Component
+
, + ); + const childComponentElement = screen.getByTestId("child-component"); + expect(childComponentElement).toBeInTheDocument(); + }); +}); diff --git a/packages/auth-provider/src/components/AuthProvider/index.ts b/packages/auth-provider/src/components/AuthProvider/index.ts deleted file mode 100644 index 1cfb93d..0000000 --- a/packages/auth-provider/src/components/AuthProvider/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { AuthProvider } from "./AuthProvider"; -export { useAuth } from "./useAuth"; diff --git a/packages/auth-provider/src/components/AuthProvider/useAuth.ts b/packages/auth-provider/src/components/AuthProvider/useAuth.ts deleted file mode 100644 index 3c8d49b..0000000 --- a/packages/auth-provider/src/components/AuthProvider/useAuth.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useContext } from "react"; -import { AuthContext, type AuthContextType } from "./AuthContext"; - -export const useAuth = (context = AuthContext): AuthContextType => - useContext(context) as AuthContextType; diff --git a/packages/auth-provider/src/components/hooks/__tests__/useAuth.test.ts b/packages/auth-provider/src/components/hooks/__tests__/useAuth.test.ts new file mode 100644 index 0000000..fe8a057 --- /dev/null +++ b/packages/auth-provider/src/components/hooks/__tests__/useAuth.test.ts @@ -0,0 +1,10 @@ +import { renderHook } from "@testing-library/react"; + +import { useAuth } from "../.."; + +describe("useAuth tests", () => { + it("should return isAuthenticated false", () => { + const res1 = renderHook(() => useAuth()); + expect(res1.result.current.isAuthenticated).toBe(false); + }); +}); diff --git a/packages/auth-provider/src/components/hooks/useAuth.ts b/packages/auth-provider/src/components/hooks/useAuth.ts new file mode 100644 index 0000000..e82e67b --- /dev/null +++ b/packages/auth-provider/src/components/hooks/useAuth.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import type { AuthContextProps } from "../../common/types"; +import { AuthContext } from "../AuthProvider/AuthContext"; + +export const useAuth = (context = AuthContext): AuthContextProps => + useContext(context) as AuthContextProps; diff --git a/packages/auth-provider/src/components/hooks/usePrevious.ts b/packages/auth-provider/src/components/hooks/usePrevious.ts new file mode 100644 index 0000000..1de307a --- /dev/null +++ b/packages/auth-provider/src/components/hooks/usePrevious.ts @@ -0,0 +1,9 @@ +import { useEffect, useRef } from "react"; + +export function usePrevious(state: T): T | undefined { + const ref = useRef(); + useEffect(() => { + ref.current = state; + }); + return ref.current; +} diff --git a/packages/auth-provider/src/components/index.ts b/packages/auth-provider/src/components/index.ts index 40e4719..b50b2cd 100644 --- a/packages/auth-provider/src/components/index.ts +++ b/packages/auth-provider/src/components/index.ts @@ -1,2 +1,2 @@ export { AuthProvider } from "./AuthProvider/AuthProvider"; -export { useAuth } from "./AuthProvider/useAuth"; +export { useAuth } from "./hooks/useAuth"; diff --git a/packages/auth-provider/vite.config.ts b/packages/auth-provider/vite.config.ts index aa418ba..0c79585 100644 --- a/packages/auth-provider/vite.config.ts +++ b/packages/auth-provider/vite.config.ts @@ -1,9 +1,6 @@ -import path from "node:path"; import { resolve } from "node:path"; -import { fileURLToPath } from "node:url"; import fs from "fs-extra"; -import { glob } from "glob"; import { defineConfig } from "vite"; import { externalDependencies } from "../../configuration/vite.common"; diff --git a/packages/auth-provider/vitest.config.ts b/packages/auth-provider/vitest.config.ts index 10f3896..c90a77e 100644 --- a/packages/auth-provider/vitest.config.ts +++ b/packages/auth-provider/vitest.config.ts @@ -13,7 +13,7 @@ export default defineConfig((configEnv) => setupFiles: ["./vitest.setup.ts"], environment: "happy-dom", coverage: { - include: ["src/**/*.ts", "src/**/*.tsx", "!src/style.ts"], + include: ["src/**/*.ts", "src/**/*.tsx"], provider: "v8", thresholds: { statements: 100,