Skip to content

Commit

Permalink
feat: custom built fp and logger in debug mode (#113)
Browse files Browse the repository at this point in the history
* feat: custom built fp and logger in debug mode

* Update bundlesize.config.js

* cleanup useEffect to prevent potential memory leaks
  • Loading branch information
aversini authored Jul 18, 2024
1 parent 12463c7 commit b1b131c
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 48 deletions.
1 change: 1 addition & 0 deletions examples/code-flow/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
clientId={"b44c68f0-e5b3-4a1d-a3e3-df8632b0223b"}
sessionExpiration={timeout}
domain={"gizmette.local.com"}
debug
>
<App timeout={timeout} />
</AuthProvider>
Expand Down
22 changes: 2 additions & 20 deletions examples/code-flow/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,27 @@ export const App = ({ timeout }: { timeout: string }) => {
logout,
isAuthenticated,
getAccessToken,
isLoading,
registeringForPasskey,
loginWithPasskey,
} = useAuth();
const [apiResponse, setApiResponse] = useState({ data: "" });

console.info({ isAuthenticated, isLoading });

const logger = console;
logger.log("isAuthenticated", isAuthenticated);
logger.log("isLoading", isLoading);
const handleValidLogin = async (e: { preventDefault: () => void }) => {
e.preventDefault();
const response = await login(
await login(
process.env.PUBLIC_TEST_USER as string,
process.env.PUBLIC_TEST_USER_PASSWORD as string,
AUTH_TYPES.CODE,
);
if (!response) {
console.error(`==> [${Date.now()}] : `, "Login failed");
} else {
console.info(`==> [${Date.now()}] : `, "Login successful");
console.info(`==> [${Date.now()}] : `, response);
}
};

const handleInvalidLogin = async (e: { preventDefault: () => void }) => {
e.preventDefault();
const response = await login(
await login(
process.env.PUBLIC_TEST_USER as string,
"invalid-password",
AUTH_TYPES.CODE,
);
if (!response) {
console.error(`==> [${Date.now()}] : `, "Login failed");
} else {
console.info(`==> [${Date.now()}] : `, "Login successful");
console.info(`==> [${Date.now()}] : `, response);
}
};

const handleValidAPICall = async (e: { preventDefault: () => void }) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-provider/bundlesize.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default {
*/
{
path: "dist/index.js",
limit: "21 kb",
limit: "18 kb",
},
],
};
6 changes: 2 additions & 4 deletions packages/auth-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -45,8 +43,8 @@
},
"dependencies": {
"@simplewebauthn/browser": "10.0.0",
"@thumbmarkjs/thumbmarkjs": "0.14.8",
"@versini/auth-common": "workspace:../auth-common",
"@versini/ui-fingerprint": "1.0.1",
"@versini/ui-hooks": "4.0.1",
"jose": "5.6.3",
"uuid": "10.0.0"
Expand Down
2 changes: 2 additions & 0 deletions packages/auth-provider/src/common/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type AuthProviderProps = {
clientId: string;
accessType?: string;
domain?: string;
debug?: boolean;
};

export type AuthState = {
Expand All @@ -36,6 +37,7 @@ export type AuthState = {
userId?: string;
username?: string;
};
debug?: boolean;
};

export type LoginType = (
Expand Down
10 changes: 2 additions & 8 deletions packages/auth-provider/src/common/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getFingerprint } from "@thumbmarkjs/thumbmarkjs";
import {
API_TYPE,
AUTH_TYPES,
HEADERS,
JWT,
verifyAndExtractToken,
} from "@versini/auth-common";
import { getFingerprintHash } from "@versini/ui-fingerprint";

import { API_ENDPOINT } from "./constants";
import type { ServiceCallProps } from "./types";
Expand Down Expand Up @@ -379,13 +379,7 @@ export const graphQLCall = async ({

export const getCustomFingerprint = async () => {
try {
const res = await getFingerprint();
if (typeof res === "string") {
return res;
} else if (res.hash && typeof res.hash === "string") {
return res.hash;
}
return "";
return await getFingerprintHash();
} catch (_error) {
return "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@ export const AuthProvider = ({
sessionExpiration,
clientId,
domain = "",
debug = false,
}: AuthProviderProps) => {
const [state, dispatch] = useReducer(reducer, {
isLoading: true,
isAuthenticated: false,
authenticationType: null,
user: undefined,
logoutReason: "",
debug,
});

const effectDidRunRef = useRef(false);
const fingerprintRef = useRef<string>("");

const [idToken, setIdToken, , removeIdToken] = useLocalStorage({
key: `${LOCAL_STORAGE_PREFIX}::${clientId}::@@user@@`,
Expand All @@ -67,11 +70,23 @@ export const AuthProvider = ({
const [nonce, setNonce, , removeNonce] = useLocalStorage({
key: `${LOCAL_STORAGE_PREFIX}::${clientId}::@@nonce@@`,
});

const logger = useCallback(
(...args: unknown[]) => {
if (debug) {
console.info(`==> [Auth ${Date.now()}]: `, ...args);
}
},
[debug],
);
const tokenManager = new TokenManager(accessToken, refreshToken);

const removeStateAndLocalStorage = useCallback(
(logoutReason?: string) => {
console.warn(logoutReason);
logger(
"removeStateAndLocalStorage: removing state and local storage with reason: ",
logoutReason,
);
dispatch({
type: ACTION_TYPE_LOGOUT,
payload: {
Expand All @@ -84,11 +99,12 @@ export const AuthProvider = ({
removeNonce();
dispatch({ type: ACTION_TYPE_LOADING, payload: { isLoading: false } });
},
[removeAccessToken, removeIdToken, removeNonce, removeRefreshToken],
[removeAccessToken, removeIdToken, removeNonce, removeRefreshToken, logger],
);

const invalidateAndLogout = useCallback(
async (message: string) => {
logger("invalidateAndLogout: invalidating and logging out");
const { user } = state;
await logoutUser({
userId: user?.userId || "",
Expand All @@ -108,9 +124,27 @@ export const AuthProvider = ({
idToken,
refreshToken,
removeStateAndLocalStorage,
logger,
],
);

/**
* This effect is responsible to set the fingerprintRef value when the
* component is first loaded.
*/

// biome-ignore lint/correctness/useExhaustiveDependencies: logger is stable
useEffect(() => {
(async () => {
logger("useEffect: setting the fingerprint");
fingerprintRef.current = await getCustomFingerprint();
})();
return () => {
logger("useEffect: cleaning up the fingerprint");
fingerprintRef.current = "";
};
}, []);

/**
* This effect is responsible to set the authentication state based on the
* idToken stored in the local storage. It is used when the page is being
Expand All @@ -125,6 +159,7 @@ export const AuthProvider = ({
try {
const jwt = await verifyAndExtractToken(idToken);
if (jwt && jwt.payload[JWT.USER_ID_KEY] !== "") {
logger("useEffect: setting the authentication state");
dispatch({
type: ACTION_TYPE_LOGIN,
payload: {
Expand All @@ -136,19 +171,24 @@ export const AuthProvider = ({
},
});
} else {
logger("useEffect: invalid JWT, invalidating and logging out");
await invalidateAndLogout(EXPIRED_SESSION);
}
} catch (_error) {
logger(
"useEffect: exception validating JWT, invalidating and logging out",
);
await invalidateAndLogout(EXPIRED_SESSION);
}
})();
} else {
logger("useEffect: setting the loading state to false");
dispatch({ type: ACTION_TYPE_LOADING, payload: { isLoading: false } });
}
return () => {
effectDidRunRef.current = true;
};
}, [state.isLoading, idToken, invalidateAndLogout]);
}, [state.isLoading, idToken, invalidateAndLogout, logger]);

const login: LoginType = async (username, password, type) => {
const _nonce = uuidv4();
Expand All @@ -158,9 +198,10 @@ export const AuthProvider = ({
removeAccessToken();
removeRefreshToken();

logger("login: Logging in with type: ", type);

if (type === AUTH_TYPES.CODE) {
const { code_verifier, code_challenge } = await pkceChallengePair();

const preResponse = await getPreAuthCode({
nonce: _nonce,
clientId,
Expand All @@ -178,7 +219,7 @@ export const AuthProvider = ({
code: preResponse.code,
code_verifier,
domain,
fingerprint: await getCustomFingerprint(),
fingerprint: fingerprintRef.current,
});
if (response.status) {
setIdToken(response.idToken);
Expand Down Expand Up @@ -210,7 +251,7 @@ export const AuthProvider = ({
nonce: _nonce,
type,
domain,
fingerprint: await getCustomFingerprint(),
fingerprint: fingerprintRef.current,
});
if (response.status) {
setIdToken(response.idToken);
Expand Down Expand Up @@ -242,6 +283,7 @@ export const AuthProvider = ({
try {
if (isAuthenticated && user && user.userId) {
if (accessToken) {
logger("getAccessToken");
const jwtAccess = await verifyAndExtractToken(accessToken);
if (jwtAccess && jwtAccess.payload[JWT.USER_ID_KEY] !== "") {
return accessToken;
Expand All @@ -251,6 +293,7 @@ export const AuthProvider = ({
* accessToken is not valid, so we need to try to refresh it using the
* refreshToken - this is a silent refresh.
*/
logger("getAccessToken: invalid access token, refreshing it");
const res = await tokenManager.refreshtoken({
clientId,
userId: user.userId as string,
Expand All @@ -265,12 +308,19 @@ export const AuthProvider = ({
/**
* refreshToken is not valid, so we need to re-authenticate the user.
*/
logger("getAccessToken: invalid refresh token, re-authenticating user");
await invalidateAndLogout(ACCESS_TOKEN_ERROR);
return "";
}
logger(
"getAccessToken: user is not authenticated, cannot get access token",
);
await invalidateAndLogout(ACCESS_TOKEN_ERROR);
return "";
} catch (_error) {
logger(
"getAccessToken: exception occurred, invalidating and logging out",
);
await invalidateAndLogout(ACCESS_TOKEN_ERROR);
return "";
}
Expand Down Expand Up @@ -333,6 +383,8 @@ export const AuthProvider = ({
removeAccessToken();
removeRefreshToken();

logger("loginWithPasskey");

const temporaryAnonymousUserId = uuidv4();
let response = await graphQLCall({
accessToken,
Expand All @@ -356,7 +408,7 @@ export const AuthProvider = ({
authentication,
nonce: _nonce,
domain,
fingerprint: await getCustomFingerprint(),
fingerprint: fingerprintRef.current,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const InternalContext = React.createContext<{
authenticationType: null,
user: undefined,
logoutReason: "",
debug: false,
},
dispatch: () => {},
});
16 changes: 8 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b1b131c

Please sign in to comment.