Skip to content

Commit

Permalink
apli client works for pages router
Browse files Browse the repository at this point in the history
  • Loading branch information
peterphanouvong committed Sep 21, 2023
1 parent 5e6d800 commit 547ef32
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 1 deletion.
27 changes: 27 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"react-dom": "^18.1.0"
},
"dependencies": {
"@kinde-oss/kinde-typescript-sdk": "^2.2.0",
"cookie": "^0.5.0",
"crypto": "^1.0.1",
"crypto-js": "^4.1.1",
Expand Down
40 changes: 40 additions & 0 deletions server/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import {ReactElement, LinkHTMLAttributes} from 'react';
import {NextRequest} from 'next/server';
import {NextApiResponse} from 'next';
import {
APIsApi,
ApplicationsApi,
BusinessApi,
CallbacksApi,
ConnectedAppsApi,
EnvironmentsApi,
FeatureFlagsApi,
IndustriesApi,
OAuthApi,
OrganizationsApi,
PermissionsApi,
RolesApi,
SubscribersApi,
TimezonesApi,
UsersApi
} from '@kinde-oss/kinde-typescript-sdk';

export declare function RegisterLink(props): ReactElement<LinkHTMLAttributes>;

Expand Down Expand Up @@ -79,3 +97,25 @@ export declare function handleAuth(
export declare function getKindeServerSession(): ServerSession;

export declare function authMiddleware();

export function createKindeManagementAPIClient(
req?: Request | NextApiResponse,
res?: Response | NextApiResponse
): Promise<{
getToken: (req?: Request, res?: Response) => string;
usersApi: UsersApi;
oauthApi: OAuthApi;
subscribersApi: SubscribersApi;
organizationsApi: OrganizationsApi;
connectedAppsApi: ConnectedAppsApi;
featureFlagsApi: FeatureFlagsApi;
environmentsApi: EnvironmentsApi;
permissionsApi: PermissionsApi;
rolesApi: RolesApi;
businessApi: BusinessApi;
industriesApi: IndustriesApi;
timezonesApi: TimezonesApi;
applicationsApi: ApplicationsApi;
callbacksApi: CallbacksApi;
apisApi: APIsApi;
}>;
90 changes: 90 additions & 0 deletions src/api-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
APIsApi,
ApplicationsApi,
BusinessApi,
CallbacksApi,
Configuration,
ConnectedAppsApi,
EnvironmentsApi,
FeatureFlagsApi,
IndustriesApi,
OAuthApi,
OrganizationsApi,
PermissionsApi,
RolesApi,
SubscribersApi,
TimezonesApi,
UsersApi
} from '@kinde-oss/kinde-typescript-sdk';
import {config} from './config/index';
import {sessionManager} from './session/sessionManager';
import {isTokenValid} from './utils/pageRouter/isTokenValid';

/**
* Create the Kinde Management API client
* @param {Request | NextApiRequest} [req] - optional request (required when used with pages router)
* @param {Response} [res] - optional response (required when used with pages router)
*/
export const createKindeManagementAPIClient = async (req, res) => {
let apiToken = null;

const store = sessionManager(req, res);
const tokenFromCookie = store.getSessionItem('kinde_api_access_token');
if (isTokenValid(tokenFromCookie)) {
apiToken = tokenFromCookie;
} else {
const response = await fetch(`${config.issuerURL}/oauth2/token`, {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: config.clientID,
client_secret: config.clientSecret,
audience: config.audience
})
});
let apiToken = (await response.json()).access_token;
store.setSessionItem('kinde_api_access_token', apiToken);
}

const cfg = new Configuration({
basePath: config.issuerURL,
accessToken: apiToken,
headers: {Accept: 'application/json'}
});
const usersApi = new UsersApi(cfg);
const oauthApi = new OAuthApi(cfg);
const subscribersApi = new SubscribersApi(cfg);
const organizationsApi = new OrganizationsApi(cfg);
const connectedAppsApi = new ConnectedAppsApi(cfg);
const featureFlagsApi = new FeatureFlagsApi(cfg);
const environmentsApi = new EnvironmentsApi(cfg);
const permissionsApi = new PermissionsApi(cfg);
const rolesApi = new RolesApi(cfg);
const businessApi = new BusinessApi(cfg);
const industriesApi = new IndustriesApi(cfg);
const timezonesApi = new TimezonesApi(cfg);
const applicationsApi = new ApplicationsApi(cfg);
const callbacksApi = new CallbacksApi(cfg);
const apisApi = new APIsApi(cfg);

return {
usersApi,
oauthApi,
subscribersApi,
organizationsApi,
connectedAppsApi,
featureFlagsApi,
environmentsApi,
permissionsApi,
rolesApi,
businessApi,
industriesApi,
timezonesApi,
applicationsApi,
callbacksApi,
apisApi
};
};
1 change: 1 addition & 0 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export {RegisterLink} from '../components/RegisterLink';
export {LoginLink} from '../components/LoginLink';
export {LogoutLink} from '../components/LogoutLink';
export {CreateOrgLink} from '../components/CreateOrgLink';
export {createKindeManagementAPIClient} from '../api-client';
98 changes: 98 additions & 0 deletions src/session/sessionManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {isAppRouter} from '../utils/isAppRouter';
import {cookies} from 'next/headers';

var cookie = require('cookie');

export const sessionManager = (req, res) => {
if (!req) return appRouterSessionManager(cookies());
return isAppRouter(req)
? appRouterSessionManager(cookies())
: pageRouterSessionManager(req, res);
};

export const appRouterSessionManager = (cookieStore) => ({
getSessionItem: (itemKey) => {
const item = cookieStore.get(itemKey);
if (item) {
try {
const jsonValue = JSON.parse(item.value);
if (typeof jsonValue === 'object') {
return jsonValue;
}
return item.value;
} catch (error) {
return item.value;
}
}
return undefined;
},
setSessionItem: (itemKey, itemValue) =>
cookieStore.set(
itemKey,
typeof itemValue === 'object' ? JSON.stringify(itemValue) : itemValue
),
removeSessionItem: (itemKey) => cookieStore.delete(itemKey),
destroySession: () => {
[
'id_token_payload',
'id_token',
'access_token_payload',
'access_token',
'user',
'refresh_token'
].forEach((name) => cookieStore.delete(name));
}
});

export const pageRouterSessionManager = (req, res) => ({
getSessionItem: (itemKey) => {
const itemValue = req.cookies[itemKey];
if (itemValue) {
try {
const jsonValue = JSON.parse(itemValue);
if (typeof jsonValue === 'object') {
return jsonValue;
}
return itemValue;
} catch (error) {
return itemValue;
}
}
return undefined;
},
setSessionItem: (itemKey, itemValue) => {
let cookies = res.getHeader('Set-Cookie') || [];

if (!Array.isArray(cookies)) {
cookies = [cookies];
}

res.setHeader('Set-Cookie', [
...cookies.filter((cookie) => !cookie.startsWith(`${itemKey}=`)),
cookie.serialize(
itemKey,
typeof itemValue === 'object' ? JSON.stringify(itemValue) : itemValue,
{path: '/'}
)
]);
},
removeSessionItem: (itemKey) => {
res.setHeader(
'Set-Cookie',
cookie.serialize(itemKey, '', {path: '/', maxAge: -1})
);
},
destroySession: () => {
res.setHeader(
'Set-Cookie',
[
'id_token_payload',
'id_token',
'access_token_payload',
'access_token',
'user',
'refresh_token'
].map((name) => cookie.serialize(name, '', {path: '/', maxAge: -1}))
);
}
});
3 changes: 3 additions & 0 deletions src/utils/isAppRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const isAppRouter = (req) => {
return req instanceof Request;
};
4 changes: 3 additions & 1 deletion src/utils/pageRouter/isTokenValid.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const isTokenValid = (token) => {
const accessTokenPayload = jwt_decode(accessToken);
let isAudienceValid = true;
if (config.audience)
isAudienceValid = payload.aud && payload.aud.includes(config.audience);
isAudienceValid =
accessTokenPayload.aud &&
accessTokenPayload.aud.includes(config.audience);

if (
accessTokenPayload.iss == config.issuerURL &&
Expand Down
21 changes: 21 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
// Change this to match your project
"include": ["src/**/*"],
"compilerOptions": {
// Tells TypeScript to read JS files, as
// normally they are ignored as source files
"allowJs": true,
// Generate d.ts files
"declaration": true,
// This compiler run should
// only output d.ts files
"emitDeclarationOnly": true,
// Types should go into this directory.
// Removing this would place the .d.ts files
// next to the .js files
"outDir": "dist",
// go to js file when using IDE functions like
// "Go to Definition" in VSCode
"declarationMap": true
}
}

0 comments on commit 547ef32

Please sign in to comment.