-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bc5f067
Showing
11 changed files
with
1,989 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
generated | ||
pnpm-lock.yaml | ||
CHANGELOG.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// setToken.test.ts | ||
import { describe, it, expect, beforeAll } from "vitest"; | ||
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", | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
Oops, something went wrong.