Compare commits

..

6 commits

8 changed files with 233 additions and 9 deletions

21
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: deploy
on:
workflow_dispatch:
schedule:
# 土曜日 21:00 (JST) は 土曜日 12:00 (UTC)
# https://time.is/compare/JST/UTC
# https://crontab.guru/#00_12_*_*_SAT
- cron: 00 12 * * SAT
push:
branches: main
jobs:
deploy:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Deploy
run: curl -sSf -X POST "${DEPLOY_WEBHOOK_URL}"
env:
# https://developers.cloudflare.com/pages/platform/deploy-hooks
DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }}

40
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: test
on:
pull_request:
workflow_run:
workflows: deploy
types: completed
jobs:
test:
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- if: github.ref_name != github.event.repository.default_branch
id: set_preview_env
shell: bash
run: echo url="https://$(echo "${HEAD_REF}" | sed s/[^_0-9a-z]/-/gi).jwk.pages.dev/" >> "${GITHUB_OUTPUT}"
env:
HEAD_REF: ${{ github.head_ref }}
- name: Run tests on ${{ env.BASE_URL }}
run: npm test
env:
BASE_URL: ${{ github.ref_name == github.event.repository.default_branch && 'https://jwk.pages.dev/' || steps.set_preview_env.outputs.url }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: "playwright-report-${{ matrix.os }}"
path: playwright-report

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
node_modules node_modules
dist dist
playwright-report
test-results

76
package-lock.json generated
View file

@ -10,6 +10,8 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@exampledev/new.css": "^1.1.3", "@exampledev/new.css": "^1.1.3",
"@playwright/test": "^1.42.1",
"@types/node": "^20.11.28",
"esbuild": "^0.20.0", "esbuild": "^0.20.0",
"jose": "^5.0.0" "jose": "^5.0.0"
} }
@ -388,6 +390,30 @@
"integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w==", "integrity": "sha512-qhbGfqBRwUlM6MCSaJdUfjq86opNCMvM+6kVvs6S0kYhy0V8dKbe4rDMIklEJGuMc5QH5OuPjdCReu9I0tim2w==",
"dev": true "dev": true
}, },
"node_modules/@playwright/test": {
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
"integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
"dev": true,
"dependencies": {
"playwright": "1.42.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@types/node": {
"version": "20.11.28",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz",
"integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.20.1", "version": "0.20.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz",
@ -426,6 +452,20 @@
"@esbuild/win32-x64": "0.20.1" "@esbuild/win32-x64": "0.20.1"
} }
}, },
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/jose": { "node_modules/jose": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz",
@ -434,6 +474,42 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
},
"node_modules/playwright": {
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
"integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
"dev": true,
"dependencies": {
"playwright-core": "1.42.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.42.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
} }
} }
} }

View file

@ -5,11 +5,14 @@
"author": "Kohei Watanabe <nebel@fogtype.com>", "author": "Kohei Watanabe <nebel@fogtype.com>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "playwright test",
"build": "esbuild --bundle --loader:.html=copy --outdir=dist src/*", "build": "esbuild --bundle --loader:.html=copy --outdir=dist src/*",
"start": "esbuild --bundle --loader:.html=copy --outdir=dist --servedir=dist src/*" "start": "esbuild --bundle --loader:.html=copy --outdir=dist --servedir=dist src/*"
}, },
"devDependencies": { "devDependencies": {
"@exampledev/new.css": "^1.1.3", "@exampledev/new.css": "^1.1.3",
"@playwright/test": "^1.42.1",
"@types/node": "^20.11.28",
"esbuild": "^0.20.0", "esbuild": "^0.20.0",
"jose": "^5.0.0" "jose": "^5.0.0"
} }

39
playwright.config.ts Normal file
View file

@ -0,0 +1,39 @@
import { defineConfig, devices } from "@playwright/test";
const baseURL = process.env.BASE_URL ?? "http://127.0.0.1:8000";
export default defineConfig({
testDir: "tests",
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
timeout: 10_000,
retries: 2,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
use: {
baseURL,
trace: "on-first-retry",
video: "on-first-retry",
},
projects: [
{
name: "chromium",
use: devices["Desktop Chrome"],
},
{
name: "firefox",
use: devices["Desktop Firefox"],
},
{
name: "webkit",
use: devices["Desktop Safari"],
},
],
webServer: {
url: baseURL,
command: "npm start",
stdout: "ignore",
stderr: "ignore",
reuseExistingServer: true,
},
});

View file

@ -23,15 +23,15 @@
Algorithm Algorithm
<select name="alg"> <select name="alg">
<!-- https://www.iana.org/assignments/jose/web-signature-encryption-algorithms.csv --> <!-- https://www.iana.org/assignments/jose/web-signature-encryption-algorithms.csv -->
<option label="ECDSA (ES256)">ES256</option> <option value="ES256">ECDSA (ES256)</option>
<option label="ECDSA (ES384)">ES384</option> <option value="ES384">ECDSA (ES384)</option>
<option label="ECDSA (ES512)">ES512</option> <option value="ES512">ECDSA (ES512)</option>
<option label="RSASSA-PKCS1-v1_5 (RS256)">RS256</option> <option value="RS256">RSASSA-PKCS1-v1_5 (RS256)</option>
<option label="RSASSA-PKCS1-v1_5 (RS384)">RS384</option> <option value="RS384">RSASSA-PKCS1-v1_5 (RS384)</option>
<option label="RSASSA-PKCS1-v1_5 (RS512)">RS512</option> <option value="RS512">RSASSA-PKCS1-v1_5 (RS512)</option>
<option label="RSASSA-PSS (PS256)">PS256</option> <option value="PS256">RSASSA-PSS (PS256)</option>
<option label="RSASSA-PSS (PS384)">PS384</option> <option value="PS384">RSASSA-PSS (PS384)</option>
<option label="RSASSA-PSS (PS512)">PS512</option> <option value="PS512">RSASSA-PSS (PS512)</option>
<option>ECDH-ES</option> <option>ECDH-ES</option>
<option>ECDH-ES+A128KW</option> <option>ECDH-ES+A128KW</option>
<option>ECDH-ES+A192KW</option> <option>ECDH-ES+A192KW</option>

43
tests/jwk.test.ts Normal file
View file

@ -0,0 +1,43 @@
import { test, expect } from "@playwright/test";
// prettier-ignore
const properties = {
"ECDSA (ES256)": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"ECDSA (ES384)": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"ECDSA (ES512)": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"RSASSA-PKCS1-v1_5 (RS256)": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSASSA-PKCS1-v1_5 (RS384)": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSASSA-PKCS1-v1_5 (RS512)": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSASSA-PSS (PS256)": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSASSA-PSS (PS384)": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSASSA-PSS (PS512)": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"ECDH-ES": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"ECDH-ES+A128KW": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"ECDH-ES+A192KW": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"ECDH-ES+A256KW": ["alg", "use", "kid", "kty", "crv", "x", "y", "d"],
"RSA-OAEP": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSA-OAEP-256": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSA-OAEP-384": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
"RSA-OAEP-512": ["alg", "use", "kid", "kty", "e", "n", "d", "p", "q", "dp", "dq", "qi"],
};
// prettier-ignore-end
for (const [algorithm, expected] of Object.entries(properties)) {
test(`${algorithm} has ${expected}`, async ({ page }) => {
await page.goto("/");
await page.getByLabel("Algorithm").selectOption({ label: algorithm });
await page.getByLabel("Generate").click();
const jwk = await page
.getByLabel("Private Key")
.getByText(/./)
.inputValue()
.then(JSON.parse);
for (const property of expected) {
expect(jwk).toHaveProperty(property, expect.stringMatching(/./));
}
expect(Object.keys(jwk)).toHaveLength(expected.length);
});
}