move JWT validation to package

This commit is contained in:
Devin Zuczek
2026-07-09 20:14:23 -04:00
parent cb48cefa7d
commit bdebc50075
35 changed files with 102 additions and 530 deletions
+1
View File
@@ -17,6 +17,7 @@
"dependencies": {
"@repo/domain": "workspace:*",
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
+1 -2
View File
@@ -11,8 +11,7 @@ import {
updateAccount,
} from '@repo/domain'
import { logger, withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from './jwt'
import { validateAndGetAccountId } from '@repo/jwt'
import type { Context } from 'hono'
import type { Account } from '@repo/domain'
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1
View File
@@ -17,6 +17,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
+1 -1
View File
@@ -1,4 +1,4 @@
import { validateAndGetAccountId } from './jwt'
import { validateAndGetAccountId } from '@repo/jwt'
import type { Context } from 'hono'
import type { App } from './context'
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1
View File
@@ -18,6 +18,7 @@
"dependencies": {
"@repo/domain": "workspace:*",
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
+1 -1
View File
@@ -9,8 +9,8 @@ import {
setPasswordHash,
} from '@repo/domain'
import { logger, withNotFound, withOnError } from '@repo/hono-helpers'
import { generateToken, TOKEN_TTL_SECONDS, validateAndGetAccountId } from '@repo/jwt'
import { generateToken, TOKEN_TTL_SECONDS, validateAndGetAccountId } from './jwt'
import { hashPassword, verifyPassword } from './password'
import { consumeRefreshToken, issueRefreshToken } from './refresh-db'
+1
View File
@@ -16,6 +16,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
+1 -1
View File
@@ -2,9 +2,9 @@ import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from '@repo/jwt'
import loadingScreenTipData from '../static/loading-screen-tip-data.json'
import { validateAndGetAccountId } from './jwt'
import type { Context } from 'hono'
import type { App, Env } from './context'
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1
View File
@@ -16,6 +16,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
+1 -2
View File
@@ -2,8 +2,7 @@ import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from './jwt'
import { validateAndGetAccountId } from '@repo/jwt'
import type { Context } from 'hono'
import type { App } from './context'
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1
View File
@@ -16,6 +16,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
+1 -1
View File
@@ -2,13 +2,13 @@ import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from '@repo/jwt'
import defaultAvatarItems from '../static/default-avatar-items.json'
import defaultAvatar from '../static/default-avatar.json'
import myProgress from '../static/my-progress.json'
import weeklyChallenge from '../static/weekly-challenge.json'
import { getAvatar, setAvatar } from './avatar-db'
import { validateAndGetAccountId } from './jwt'
import type { Context } from 'hono'
import type { Avatar } from './avatar-db'
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1
View File
@@ -17,6 +17,7 @@
"dependencies": {
"@repo/domain": "workspace:*",
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1 -1
View File
@@ -3,8 +3,8 @@ import { useWorkersLogger } from 'workers-tagged-logger'
import { RoomInstanceType } from '@repo/domain'
import { withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from '@repo/jwt'
import { validateAndGetAccountId } from './jwt'
import {
createRoomInstance,
getJoinableInstance,
+1
View File
@@ -16,6 +16,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
@@ -2,9 +2,9 @@ import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from '@repo/jwt'
import { DEFAULT_SETTINGS } from './default-settings'
import { validateAndGetAccountId } from './jwt'
import type { Context } from 'hono'
import type { App } from './context'
+1
View File
@@ -17,6 +17,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1 -1
View File
@@ -2,8 +2,8 @@ import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { logger, withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from '@repo/jwt'
import { validateAndGetAccountId } from './jwt'
import {
cloneRoom,
findSubRoom,
+1
View File
@@ -16,6 +16,7 @@
},
"dependencies": {
"@repo/hono-helpers": "workspace:*",
"@repo/jwt": "workspace:*",
"hono": "4.12.27",
"workers-tagged-logger": "1.0.1"
},
-57
View File
@@ -1,57 +0,0 @@
/**
* Minimal HS256 JWT validation.
*
* The signing key is supplied by the caller from the shared `JWT_SECRET` Secrets
* Store binding (see context.ts) - the same key the `auth` worker signs with.
*/
function base64urlToBytes(input: string): Uint8Array {
const padded = input.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4))
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/**
* Validate an HS256 token and return its `sub` (account id) claim, or `null`
* when the token is malformed, has a bad signature, or is expired.
*/
export async function validateAndGetAccountId(
token: string,
secret: string
): Promise<string | null> {
const parts = token.split('.')
if (parts.length !== 3) return null
const [header, payload, signature] = parts
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const valid = await crypto.subtle.verify(
'HMAC',
key,
base64urlToBytes(signature),
new TextEncoder().encode(`${header}.${payload}`)
)
if (!valid) return null
let claims: { sub?: string; exp?: number }
try {
claims = JSON.parse(new TextDecoder().decode(base64urlToBytes(payload)))
} catch {
return null
}
if (typeof claims.exp === 'number' && claims.exp < Math.floor(Date.now() / 1000)) {
return null
}
return claims.sub ?? null
}
+1 -2
View File
@@ -2,8 +2,7 @@ import { Hono } from 'hono'
import { useWorkersLogger } from 'workers-tagged-logger'
import { withNotFound, withOnError } from '@repo/hono-helpers'
import { validateAndGetAccountId } from './jwt'
import { validateAndGetAccountId } from '@repo/jwt'
import type { Context } from 'hono'
import type { App } from './context'
+9
View File
@@ -0,0 +1,9 @@
# jwt
Shared HS256 JWT helpers for the recflare workers. Single source of truth for
token validation and generation, so the same signing/verification code isn't
re-copied per worker.
`auth` signs tokens (`generateToken`); every worker validates the incoming
Bearer token (`validateAndGetAccountId`). Both take the signing key from the
shared `JWT_SECRET` binding (see each worker's `context.ts`).
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@repo/jwt",
"version": "0.1.0",
"private": true,
"sideEffects": false,
"type": "module",
"main": "src/index.ts",
"scripts": {
"check:lint": "run-oxlint",
"check:types": "run-tsc"
},
"devDependencies": {
"@cloudflare/workers-types": "4.20260630.1",
"@repo/tools": "workspace:*",
"@repo/typescript-config": "workspace:*"
}
}
+1
View File
@@ -0,0 +1 @@
export { validateAndGetAccountId, generateToken, TOKEN_TTL_SECONDS } from './jwt'
@@ -1,8 +1,9 @@
/**
* Minimal HS256 JWT generation.
* Minimal HS256 JWT generation and validation.
*
* The signing key is supplied by the caller from the `JWT_SECRET` binding
* (a Cloudflare secret in deployed envs, `.dev.vars` locally) see context.ts.
* The signing key is supplied by the caller from the shared `JWT_SECRET` binding
* (a Cloudflare secret in deployed envs, `.dev.vars` locally) see each worker's
* context.ts. `auth` signs tokens; every worker validates them with the same key.
*/
/** Token lifetime in seconds (mirrored in the `expires_in` response field). */
@@ -81,7 +82,7 @@ const TOKEN_SCOPES = [
]
/** Roles granted — the client needs `gameClient` to operate. */
const TOKEN_ROLES = ['gameClient', /* 'developer', 'moderator', 'junior'*/];
const TOKEN_ROLES = ['gameClient' /* 'developer', 'moderator', 'junior'*/]
export async function generateToken(
accountId: string,
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@repo/typescript-config/workers-lib.json",
// jwt has no vitest of its own, so pull in only the Workers ambient types
// (drop @cloudflare/vitest-pool-workers/types that workers-lib.json adds).
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
}
}
+42
View File
@@ -59,6 +59,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -90,6 +93,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -124,6 +130,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -155,6 +164,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -217,6 +229,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -279,6 +294,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -347,6 +365,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -440,6 +461,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -471,6 +495,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -502,6 +529,9 @@ importers:
'@repo/hono-helpers':
specifier: workspace:*
version: link:../../packages/hono-helpers
'@repo/jwt':
specifier: workspace:*
version: link:../../packages/jwt
hono:
specifier: 4.12.27
version: 4.12.27
@@ -626,6 +656,18 @@ importers:
specifier: 4.1.9
version: 4.1.9(@types/node@26.0.1)(vite@6.4.3(@types/node@26.0.1)(jiti@2.6.1)(lightningcss@1.29.2)(tsx@4.22.4)(yaml@2.9.0))
packages/jwt:
devDependencies:
'@cloudflare/workers-types':
specifier: 4.20260630.1
version: 4.20260630.1
'@repo/tools':
specifier: workspace:*
version: link:../tools
'@repo/typescript-config':
specifier: workspace:*
version: link:../typescript-config
packages/oxlint-config:
dependencies:
oxlint: