Skip to content

Commit

Permalink
feat: add helper methods and support nuxt
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielRivers committed Apr 11, 2024
1 parent d37eaf8 commit 6a2df5d
Show file tree
Hide file tree
Showing 11 changed files with 802 additions and 50 deletions.
16 changes: 16 additions & 0 deletions .release-it.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"git": {
"requireBranch": "main",
"commitMessage": "chore: release v${version}"
},
"hooks": {
"before:init": ["git pull", "pnpm run lint"],
"after:bump": "npx auto-changelog -p && npm run build"
},
"github": {
"release": true
},
"npm": {
"publish": true
}
}
23 changes: 21 additions & 2 deletions lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { OpenAPI } from "./api/index";
import { getToken } from "./utilities/getToken";

export interface TokenStore {
getToken(): Promise<string>;
setToken(tokens: string): Promise<void>;
clearToken(): Promise<void>;
}

export const kindeConfig: {
clientId: string;
clientSecret: string;
clientId?: string;
clientSecret?: string;
kinde_domain: string;
audience: string;
token: string;
Expand All @@ -18,3 +21,19 @@ export const kindeConfig: {
audience: "audience",
token: "",
};

export const init = () => {
if (!process.env.KINDE_DOMAIN) {
throw new Error("KINDE_DOMAIN is not set");
}

kindeConfig.clientId = process.env.KINDE_CLIENT_ID;
kindeConfig.clientSecret = process.env.KINDE_CLIENT_SECRET;
kindeConfig.audience = process.env.KINDE_DOMAIN + "/api";

OpenAPI.BASE = process.env.KINDE_DOMAIN;

OpenAPI.TOKEN = async () => {
return await getToken();
};
};
2 changes: 1 addition & 1 deletion lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const TOKEN_ENDPOINT = `${process.env.KINDE_DOMAIN}/oauth2/token`;

export const DEFAULT_TOKEN_SCOPES: string = "openid profile email offline";
export const DEFAULT_TOKEN_SCOPES: string = "";
37 changes: 4 additions & 33 deletions lib/main.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,9 @@
import * as dotenv from "dotenv";
import { init } from "./config";
dotenv.config();

init();

export * from "./api/index";
export * from "./utilities/index";

import { OpenAPI } from "./api/index";
import { kindeConfig } from "./config";
import { getToken } from "./utilities/getToken";

if (!process.env.KINDE_DOMAIN) {
throw new Error("KINDE_DOMAIN is not set");
}

if (!process.env.KINDE_CLIENT_SECRET) {
throw new Error("KINDE_CLIENT_SECRET is not set");
}

if (!process.env.KINDE_CLIENT_ID) {
throw new Error("KINDE_CLIENT_ID is not set");
}

if (!process.env.KINDE_DOMAIN) {
throw new Error("KINDE_DOMAIN is not set");
}

kindeConfig.clientId = process.env.KINDE_CLIENT_ID;
kindeConfig.clientSecret = process.env.KINDE_CLIENT_SECRET;
kindeConfig.audience = process.env.KINDE_CLIENT_SECRET;
OpenAPI.BASE = process.env.KINDE_DOMAIN;

async function main() {
OpenAPI.TOKEN = async () => {
return await getToken();
};
}

main();
export { init } from "./config";
41 changes: 31 additions & 10 deletions lib/utilities/getToken.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as constants from "../constants";
import { setToken } from "./setToken";
import { jwtDecode } from "./jwt-decode";
import { kindeConfig } from "../config";
import { OpenAPI } from "../api";
import { LIB_VERSION } from "../version";
import { checkAudience } from "./token/checkAudience";
import { hasTokenExpired } from "./token/hasExpired";

/**
* Set the token to be used for requests
Expand All @@ -13,13 +14,34 @@ export const getToken = async (): Promise<string> => {
let token: string = "";
if (kindeConfig.tokenStore?.getToken) {
token = await kindeConfig.tokenStore.getToken();
} else {
token = kindeConfig.token;
}

if (kindeConfig.token || token) {
const decoded = jwtDecode(kindeConfig.token);
if (decoded.exp && decoded.exp * 1000 > Date.now()) {
return kindeConfig.token;
if (token) {
const hasAudience = checkAudience(token, kindeConfig.audience);
const tokenExpired = hasTokenExpired(token);

if (!hasAudience || tokenExpired) {
// Supplied token does not have the required audience to make API calls
token = await generateM2MToken();
}
} else {
token = await generateM2MToken();
}

/// TODO: store the M2M token elsewhere so not to override the user defined token
setToken(token);
return token;
};

async function generateM2MToken() {
if (!kindeConfig.clientId) {
throw new Error("Required KINDE_CLIENT_ID is not set");
}

if (!kindeConfig.clientSecret) {
throw new Error("Required KINDE_CLIENT_SECRET is not set");
}

const body = new URLSearchParams({
Expand All @@ -35,15 +57,14 @@ export const getToken = async (): Promise<string> => {
"Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8",
);
headers.append('frameworkVersion', LIB_VERSION);
headers.append('framework', 'management-api-js');
headers.append("frameworkVersion", LIB_VERSION);
headers.append("framework", "management-api-js");

const config: RequestInit = { method: "POST", headers, body };

const response = await fetch(constants.TOKEN_ENDPOINT, config);
const payload: {
access_token: string;
} = (await response.json()) as { access_token: string };
setToken(payload.access_token);

return payload.access_token;
};
}
33 changes: 31 additions & 2 deletions lib/utilities/jwt-decode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
// write a method to decode a JWT token
export const jwtDecode = (token: string) => {
export type JWTDecoded = {
aud: string[];
azp: string;
billing: {
has_payment_details: boolean;
};
exp: number;
iat: number;
iss: string;
jti: string;
org_code: string;
permissions: string[];
scp: string[];
sub: string;
};

/**
* Decode JWT token
* @param token - JWT token to be decoded
* @returns - Decoded JWT token
*
* @example
* jwtDecode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")
* Returns:
* {
* sub: "1234567890",
* name: "John Doe",
* iat: 1516239022
* }
*/
export const jwtDecode = (token?: string): JWTDecoded | null => {
if (!token) {
return null;
}
Expand Down
19 changes: 19 additions & 0 deletions lib/utilities/token/checkAudience.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type JWTDecoded, jwtDecode } from "../jwt-decode";
/**
* Checks if the given audience is present in the token
* @param token - JWT to be checked, can be a string or a decoded JWT token
* @returns - True if the audience is present in the token, false otherwise
*/
export const checkAudience = (
token?: string | JWTDecoded,
audience?: string,
): boolean => {
if (!audience) {
return false;
}
const decoded = typeof token === "string" ? jwtDecode(token) : token;
if (!decoded) {
return false;
}
return decoded.aud.includes(audience);
};
13 changes: 13 additions & 0 deletions lib/utilities/token/hasExpired.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type JWTDecoded, jwtDecode } from "../jwt-decode";
/**
* Checks if the given audience is present in the token
* @param token - JWT to be checked, can be a string or a decoded JWT token
* @returns - True if the audience is present in the token, false otherwise
*/
export const hasTokenExpired = (token?: string | JWTDecoded): boolean => {
const decoded = typeof token === "string" ? jwtDecode(token) : token;
if (!decoded) {
return false;
}
return !!decoded.exp && decoded.exp * 1000 > Date.now();
};
6 changes: 6 additions & 0 deletions lib/utilities/webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { jwtDecode } from "./jwt-decode";

export const decodeWebhook = (token: string): any => {
const decoded = jwtDecode(token);
return decoded;
};
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
{
"name": "kinde-management-sdk-js",
"name": "@kinde/management-api-js",
"private": false,
"version": "0.0.1",
"scripts": {
"dev": "vite",
"prebuild": "node -p \"'export const LIB_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > lib/version.ts",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:coverage": "vitest --coverage",
"lint": "prettier --check .",
"lint:fix": "prettier --write ."
},
"module": "lib/main.js",
"main": "lib/main.ts",
"devDependencies": {
"@types/node": "^20.11.28",
"@vitest/coverage-v8": "^1.4.0",
"prettier": "^3.2.5",
"typescript": "^5.2.2",
"vite": "^5.1.6",
"vite-plugin-dts": "^3.7.3"
"vite-plugin-dts": "^3.7.3",
"vitest": "^1.4.0"
},
"dependencies": {
"dotenv": "^16.4.5"
Expand Down
Loading

0 comments on commit 6a2df5d

Please sign in to comment.