Skip to content

Commit

Permalink
add helpers to frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveOrDead committed Apr 19, 2023
1 parent ccd20e0 commit 00db101
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 29 deletions.
173 changes: 165 additions & 8 deletions src/frontend/AuthProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import React, {
} from "react";
import { config } from "../config/index";

const flagDataTypeMap = {
s: "string",
i: "integer",
b: "boolean",
};

const handleError = () => {
throw new Error(
"Oops! Seems like you forgot to wrap your app in <KindeProvider>."
Expand All @@ -20,6 +26,7 @@ const handleError = () => {
* @property {string | null} given_name
* @property {string | null} family_name
* @property {string | null} updated_at
* @property {string | null} picture
*/

/**
Expand All @@ -45,7 +52,7 @@ const AuthContext = createContext({
*/
export const useKindeAuth = () => useContext(AuthContext);

const userFetcher = async (url) => {
const tokenFetcher = async (url) => {
let response;
try {
response = await fetch(url);
Expand All @@ -54,7 +61,8 @@ const userFetcher = async (url) => {
}

if (response.ok) {
return response.json();
const json = await response.json();
return json;
} else if (response.status === 401) {
return;
}
Expand All @@ -65,21 +73,141 @@ export const KindeProvider = ({ children }) => {
...config.initialState,
});

const profileUrl = "/api/auth/me";
const setupUrl = "/api/auth/setup";

// try and get the user (by fetching /api/auth/me) -> this needs to do the OAuth stuff
// try and get the user (by fetching /api/auth/setup) -> this needs to do the OAuth stuff
const checkSession = useCallback(async () => {
try {
const user = await userFetcher(profileUrl);
const tokens = await tokenFetcher(setupUrl);

const user = {
id: tokens.id_token.sub,
name: tokens.id_token.name,
given_name: tokens.id_token.given_name,
family_name: tokens.id_token.family_name,
updated_at: tokens.id_token.updated_at,
email: tokens.id_token.email,
picture: tokens.id_token.picture,
};

const getClaim = (claim, tokenKey = "access_token") => {
const token =
tokenKey === "access_token" ? tokens.access_token : tokens.id_token;
return token ? { name: claim, value: token[claim] } : null;
};

const getClaimValue = (claim, tokenKey = "access_token") => {
const obj = getClaim(claim, tokenKey);
return obj && obj.value;
};

const getFlag = (code, defaultValue, flagType) => {
const flags = getClaimValue("feature_flags");
const flag = flags && flags[code] ? flags[code] : {};

if (!flag.v && !defaultValue) {
throw Error(
`Flag ${code} was not found, and no default value has been provided`
);
}

if (flagType && flag.t && flagType !== flag.t) {
throw Error(
`Flag ${code} is of type ${
flagDataTypeMap[flag.t]
} - requested type ${flagDataTypeMap[flagType]}`
);
}
return {
code,
type: flagDataTypeMap[flag.t || flagType],
value: flag.v == null ? defaultValue : flag.v,
is_default: flag.v == null,
};
};

const getBooleanFlag = (code, defaultValue) => {
try {
const flag = getFlag(code, defaultValue, "b");
return flag.value;
} catch (err) {
console.error(err);
}
};

const getStringFlag = (code, defaultValue) => {
try {
const flag = getFlag(code, defaultValue, "s");
return flag.value;
} catch (err) {
console.error(err);
}
};

const getIntegerFlag = (code, defaultValue) => {
try {
const flag = getFlag(code, defaultValue, "i");
return flag.value;
} catch (err) {
console.error(err);
}
};

const getPermissions = () => {
const orgCode = getClaimValue("org_code");
const permissions = getClaimValue("permissions");
return {
permissions,
orgCode,
};
};

const getPermission = (key) => {
const orgCode = getClaimValue("org_code");
const permissions = getClaimValue("permissions") || [];
return {
isGranted: permissions.some((p) => p === key),
orgCode,
};
};

const getOrganization = () => {
const orgCode = getClaimValue("org_code");
return {
orgCode,
};
};

const getUserOrganizations = () => {
const orgCodes = getClaimValue("org_codes", "id_token");
return {
orgCodes,
};
};

const getToken = () => {
return tokens.access_token;
};

setState((previous) => ({
...previous,
user,
getToken,
getClaim,
getFlag,
getBooleanFlag,
getStringFlag,
getIntegerFlag,
getPermissions,
getPermission,
getOrganization,
getUserOrganizations,
error: undefined,
}));
} catch (error) {
setState((previous) => ({ ...previous, isLoading: false, error }));
}
}, [profileUrl]);
}, [setupUrl]);

// if you get the user set loading false
useEffect(() => {
Expand All @@ -96,10 +224,39 @@ export const KindeProvider = ({ children }) => {
}, [state.user]);

// provide this stuff to the rest of your app
const { user, error, isLoading } = state;
const {
user,
getToken,
getClaim,
getFlag,
getBooleanFlag,
getStringFlag,
getIntegerFlag,
getPermissions,
getPermission,
getOrganization,
getUserOrganizations,
error,
isLoading,
} = state;
return (
<AuthContext.Provider
value={{ user, error, isLoading, isAuthenticated: !!user }}
value={{
user,
error,
getToken,
getClaim,
getFlag,
getBooleanFlag,
getStringFlag,
getIntegerFlag,
getPermissions,
getPermission,
getOrganization,
getUserOrganizations,
isLoading,
isAuthenticated: !!user,
}}
>
{children}
</AuthContext.Provider>
Expand Down
40 changes: 20 additions & 20 deletions src/handlers/handleAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@ import { register } from "./register";
import { callback } from "./callback";
import { createOrg } from "./createOrg";
import { getToken } from "./getToken";
import { setup } from "./setup";

const getRoute = (endpoint) => {
const routeMap = {
create_org: createOrg,
get_token: getToken,
kinde_callback: callback,
login,
logout,
me,
register,
setup,
};
return routeMap[endpoint];
};

export default () =>
async function handler(req, res) {
let {
query: { kindeAuth: route },
query: { kindeAuth: endpoint },
} = req;

route = Array.isArray(route) ? route[0] : route;
endpoint = Array.isArray(endpoint) ? endpoint[0] : endpoint;

const route = getRoute(endpoint);

switch (route) {
case "login":
return await login(req, res);
case "register":
return await register(req, res);
case "me":
return await me(req, res);
case "logout":
return await logout(req, res);
case "kinde_callback":
return await callback(req, res);
case "create_org":
return await createOrg(req, res);
case "get_token":
return await getToken(req, res);
default:
return res.status(404).end();
}
return route ? await route(req, res) : res.status(404).end();
};
2 changes: 1 addition & 1 deletion src/handlers/me.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const me = async (req, res) => {
const data = await response.json();
res.send(data);
} catch (err) {
console.log(err);
console.error(err);
}
} else {
res.status(401).send({
Expand Down
21 changes: 21 additions & 0 deletions src/handlers/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { config } from "../config/index";
const cookie = require("cookie");
import jwt_decode from "jwt-decode";

export const setup = async (req, res) => {
const kinde_token = cookie.parse(req.headers.cookie || "")["kinde_token"];

if (kinde_token) {
const token = JSON.parse(kinde_token);
const accessTokenPayload = jwt_decode(token.access_token);
const idTokenPayload = jwt_decode(token.id_token);
res.send({
id_token: idTokenPayload,
access_token: accessTokenPayload,
});
} else {
res.status(401).send({
message: "Unauthorized",
});
}
};

0 comments on commit 00db101

Please sign in to comment.