Dani
02/18/2023, 10:28 PMUserRoles.init({
skipAddingRolesToAccessToken: true,
skipAddingPermissionsToAccessToken: true,
})
does that mean that the permission claim validator will fetch the current values from supertokens core every time? I want that behavior because I would like to set permissions for offline/online users all the time, and these changes in permissions need to be available in real time in the front-end route guards/ back-end api guards. So for example, if I as an admin, remove a permission from an online user and after a second they try to access a protected api route, I would like to deny that.rp
02/19/2023, 5:00 AMDani
02/19/2023, 5:00 AMrp
02/19/2023, 5:01 AMskipAddingRolesToAccessToken
and skipAddingPermissionsToAccessToken
basically make it so that the session claims don't have any info about roles / permission during session creation - this is not what you want.maxAgeInSeconds
to 0
UserRoles.UserRoleClaim.validators.includes("admin")
, you want to use UserRoles.UserRoleClaim.validators.includes("admin", 0)
maxAgeInSeconds
is a time value which will cause supertokens to refresh the value of the claims (in this case the roles claims) if maxAgeInSeconds
has been passed since the last refetch time.Dani
02/19/2023, 5:03 AMrp
02/19/2023, 5:04 AMsession.fetchAndSetClaim
Dani
02/19/2023, 5:07 AMts
import { useEffect, useMemo } from "react";
import { refreshPermissionsClaim } from "shared/session.telefunc";
import Session, {
SessionAuth,
useSessionContext,
} from "supertokens-auth-react/recipe/session";
import { PermissionClaim } from "supertokens-auth-react/recipe/userroles";
export type ProtectedProps = {
permission?: string;
fallback?: React.ReactNode;
disableRedirect?: boolean;
};
//Avoids fetching multiple times when multiple Protected components mount
let refreshingPromise: Promise<void> | null = null;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
PermissionClaim.refresh = async () => {
if (refreshingPromise) {
return refreshingPromise;
}
console.log("refreshing permissions claim");
refreshingPromise = refreshPermissionsClaim();
const result = await refreshingPromise;
refreshingPromise = null;
return result;
};
export const Protected = ({
children,
permission,
fallback = null,
disableRedirect = false,
}: {
children: React.ReactNode;
} & ProtectedProps): JSX.Element => {
const extraValidators: Session.SessionClaimValidator[] = useMemo(() => {
const v = [];
if (permission) {
v.push(PermissionClaim.validators.includes(permission, 0.2));
}
return v;
}, [permission]);
const sessionContext = useSessionContext();
useEffect(() => {
const listener = () => {
for (const validator of extraValidators) {
if (
!sessionContext.loading &&
validator.shouldRefresh(sessionContext.accessTokenPayload, {})
) {
validator.refresh({});
}
}
};
window.addEventListener("focus", listener);
return () => {
window.removeEventListener("focus", listener);
};
}, [extraValidators, sessionContext]);
return (
<SessionAuth
doRedirection={!disableRedirect}
overrideGlobalClaimValidators={(globalValidators) => [
...globalValidators,
...extraValidators,
]}
>
<InvalidClaimHandler fallback={fallback}>{children}</InvalidClaimHandler>
</SessionAuth>
);
};
const InvalidClaimHandler = (
props: React.PropsWithChildren<{
fallback?: React.ReactNode;
}>
) => {
const sessionContext = useSessionContext();
if (sessionContext.loading) {
return <></>;
}
if (sessionContext.invalidClaims.length) {
return <>{props.fallback}</>;
}
return <div>{props.children}</div>;
};
Server:
ts
import { getSession } from "server/auth/session";
import UserRoles from "supertokens-node/recipe/userroles";
export const refreshPermissionsClaim = async () => {
const session = getSession();
await session.fetchAndSetClaim(UserRoles.PermissionClaim);
};
rp
02/19/2023, 7:07 AMvalidator.shouldRefresh
or validator.refresh
.
Just use the PermissionClaim
validators and set the maxAgeInSeconds to 0
in there too (this is on the frontend).
Also, when calling getSession
on the backend, you should await it, and it takes a request and response object as well.Dani
02/19/2023, 7:14 AMrp
02/19/2023, 7:15 AMgetSession();
function working if you aren't awaiting it? Im confused.Dani
02/19/2023, 7:26 AMts
import { getContext, Abort } from "telefunc";
import { TelefuncCtx } from "shared/types";
import { getSession } from "server/auth/session";
import UserRoles from "supertokens-node/recipe/userroles";
const getSession = () => {
const { req } = getContext<TelefuncCtx>();
const session = req.session;
if (!session) {
throw Abort("Session not found");
}
return session;
};
export const refreshPermissionsClaim = async () => {
const session = getSession();
await session.fetchAndSetClaim(UserRoles.PermissionClaim);
};
And in my main.ts server entry point:
ts
app.use(
"/_telefunc",
verifySession({ sessionRequired: false }), // sets req.session
express.text({ limit: "10mb" }),
async (req, res) => {
provideTelefuncContext({ req, res }); // async_hooks
const httpResponse = await telefunc({ //this line calls the actual function in the file above
url: req.originalUrl,
method: req.method,
body: req.body,
});
const { body, statusCode, contentType } = httpResponse;
res.status(statusCode).type(contentType).send(body);
}
);
rp
02/19/2023, 7:33 AM