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

feat: custom built fp and logger in debug mode #113

Merged
merged 3 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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: any[]) => {
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,20 @@ export const AuthProvider = ({
idToken,
refreshToken,
removeStateAndLocalStorage,
logger,
],
);

/**
* This effect is responsible to set the fingerprintRef value when the
* component is first loaded.
*/
useEffect(() => {
(async () => {
fingerprintRef.current = await getCustomFingerprint();
})();
}, []);
aversini marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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 +152,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 +164,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 +191,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 +212,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 +244,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 +276,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 +286,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 +301,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 +376,8 @@ export const AuthProvider = ({
removeAccessToken();
removeRefreshToken();

logger("loginWithPasskey");

const temporaryAnonymousUserId = uuidv4();
let response = await graphQLCall({
accessToken,
Expand All @@ -356,7 +401,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.