Skip to content

Commit

Permalink
feat: add token validation
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielRivers committed Apr 29, 2024
0 parents commit bc5f067
Show file tree
Hide file tree
Showing 11 changed files with 1,989 additions and 0 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/build-test-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Build and test

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
main:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8
- name: Setting up Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Enabling pre-post scripts
run: pnpm config set enable-pre-post-scripts true
- run: pnpm install
- run: pnpm lint
- name: Cache pnpm modules
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- run: pnpm build
- run: pnpm test:coverage
- name: Upload coverage reports to Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
coverage

api_
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generated
pnpm-lock.yaml
CHANGELOG.md
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 changelogen@latest --output CHANGELOG.md && npm run build"
},
"github": {
"release": true
},
"npm": {
"publish": true
}
}
74 changes: 74 additions & 0 deletions lib/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// setToken.test.ts
import { describe, it, expect, beforeAll } from "vitest";

Check failure on line 2 in lib/main.test.ts

View workflow job for this annotation

GitHub Actions / main (20.x)

'beforeAll' is declared but its value is never read.
import { createHmac } from "crypto";
import { validateToken } from "./main";

function base64UrlEncode(str: string) {
return Buffer.from(str)
.toString("base64")
.replace("+", "-")
.replace("/", "_")
.replace(/=+$/, "");
}

export function jwtSign({
header,
payload,
secret,
}: {
header: any;
payload: any;
secret: string;
}) {
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));

const signature = createHmac("sha256", secret)
.update(encodedHeader + "." + encodedPayload)
.digest("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");

return encodedHeader + "." + encodedPayload + "." + signature;
}

describe("Validate token", () => {
it("no token supplied", async () => {
const isTokenValid = await validateToken({});
expect(isTokenValid).toEqual({
valid: false,
message: "Token is required",
});
});

it("no domain supplied", async () => {
const token = jwtSign({
header: { alg: "HS256", typ: "JWT" },
payload: { sub: "1234567890", name: "John Doe", iat: 1516239022 },
secret: "your-256-bit-secret",
});
const isTokenValid = await validateToken({ token });
expect(isTokenValid).toEqual({
valid: false,
message: "Domain is required",
});
});

it("token is present but not valid", async () => {
const token = jwtSign({
header: { alg: "HS256", typ: "JWT" },
payload: { sub: "1234567890", name: "John Doe", iat: 1516239022 },
secret: "your-256-bit-secret",
});

const isTokenValid = await validateToken({
token,
domain: "https://danielkinde.kinde.com",
});
expect(isTokenValid).toEqual({
valid: false,
message: "Missing Issuer. Expected: https://danielkinde.kinde.com",
});
});
});
46 changes: 46 additions & 0 deletions lib/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { JwtRsaVerifier } from "aws-jwt-verify";

export type jwtValidationResponse = {
valid: boolean;
message: string;
};

async function verifyJwt(
token: string,
domain: string,
audience?: string,
): Promise<jwtValidationResponse> {
const verifier = JwtRsaVerifier.create({
issuer: domain,
audience: audience || null,
jwksUri: `${domain}/.well-known/jwks.json`,
});

try {
await verifier.verify(token);
return { valid: true, message: "Token is valid" };
} catch (ex) {
console.warn(`Token Validation Failed: ${(ex as Error).message}`);
return { valid: false, message: (ex as Error).message };
}
}

export const validateToken = async (validateOptions: {
token?: string;
domain?: string;
audience?: string;
}): Promise<jwtValidationResponse> => {
if (!validateOptions.token) {
return { valid: false, message: "Token is required" };
}

if (!validateOptions.domain) {
return { valid: false, message: "Domain is required" };
}

return await verifyJwt(
validateOptions.token,
validateOptions.domain,
validateOptions.audience,
);
};
36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@kinde/jwt-validator",
"private": false,
"type": "module",
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"version": "0.1.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:coverage": "vitest --coverage",
"lint": "prettier --check .",
"lint:fix": "prettier --write ."
},
"module": "dist/jwt-validator.js",
"main": "dist/jwt-validator.cjs",
"types": "dist/main.d.ts",
"devDependencies": {
"@types/node": "^20.12.7",
"@vitest/coverage-v8": "^1.5.2",
"prettier": "^3.2.5",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vite-plugin-dts": "^3.9.0",
"vitest": "^1.5.2"
},
"dependencies": {
"aws-jwt-verify": "^4.0.1"
}
}
Loading

0 comments on commit bc5f067

Please sign in to comment.