mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix: use timing-safe comparison for CLI API token validation (#9)
* fix: use timing-safe comparison for CLI API token validation Replace direct string comparison (===) with constant-time comparison using crypto.timingSafeEqual to prevent timing attacks that could leak information about the token character by character. Affected locations: - server/src/web/routes/auth.ts (accessToken validation) - server/src/web/routes/cli.ts (bearer token middleware) - server/src/socket/server.ts (socket.io /cli namespace auth) * Update server/src/utils/crypto.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
Claude
parent
e2731fc7f0
commit
aababe6a57
@@ -4,6 +4,7 @@ import { jwtVerify } from 'jose'
|
||||
import { z } from 'zod'
|
||||
import type { Store } from '../store'
|
||||
import { configuration } from '../configuration'
|
||||
import { safeCompareStrings } from '../utils/crypto'
|
||||
import { registerCliHandlers } from './handlers/cli'
|
||||
import { registerTerminalHandlers } from './handlers/terminal'
|
||||
import { RpcRegistry } from './rpcRegistry'
|
||||
@@ -93,7 +94,7 @@ export function createSocketServer(deps: SocketServerDeps): {
|
||||
cliNs.use((socket, next) => {
|
||||
const auth = socket.handshake.auth as Record<string, unknown> | undefined
|
||||
const token = typeof auth?.token === 'string' ? auth.token : null
|
||||
if (token !== configuration.cliApiToken) {
|
||||
if (!safeCompareStrings(token, configuration.cliApiToken)) {
|
||||
return next(new Error('Invalid token'))
|
||||
}
|
||||
next()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { timingSafeEqual } from 'node:crypto'
|
||||
|
||||
export function safeCompareStrings(a: string | null | undefined, b: string | null | undefined): boolean {
|
||||
if (a == null || b == null) {
|
||||
return false
|
||||
}
|
||||
const bufA = Buffer.from(a, 'utf8')
|
||||
const bufB = Buffer.from(b, 'utf8')
|
||||
try {
|
||||
return timingSafeEqual(bufA, bufB)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Hono } from 'hono'
|
||||
import { SignJWT } from 'jose'
|
||||
import { z } from 'zod'
|
||||
import { configuration } from '../../configuration'
|
||||
import { safeCompareStrings } from '../../utils/crypto'
|
||||
import { validateTelegramInitData } from '../telegramInitData'
|
||||
import { getOrCreateOwnerId } from '../ownerId'
|
||||
import type { WebAppEnv } from '../middleware/auth'
|
||||
@@ -33,7 +34,7 @@ export function createAuthRoutes(jwtSecret: Uint8Array): Hono<WebAppEnv> {
|
||||
|
||||
// Access Token authentication (CLI_API_TOKEN)
|
||||
if ('accessToken' in parsed.data) {
|
||||
if (parsed.data.accessToken !== configuration.cliApiToken) {
|
||||
if (!safeCompareStrings(parsed.data.accessToken, configuration.cliApiToken)) {
|
||||
return c.json({ error: 'Invalid access token' }, 401)
|
||||
}
|
||||
userId = await getOrCreateOwnerId()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Hono } from 'hono'
|
||||
import { z } from 'zod'
|
||||
import { configuration } from '../../configuration'
|
||||
import { safeCompareStrings } from '../../utils/crypto'
|
||||
import type { SyncEngine } from '../../sync/syncEngine'
|
||||
|
||||
const bearerSchema = z.string().regex(/^Bearer\s+(.+)$/i)
|
||||
@@ -32,7 +33,7 @@ export function createCliRoutes(getSyncEngine: () => SyncEngine | null): Hono {
|
||||
}
|
||||
|
||||
const token = parsed.data.replace(/^Bearer\s+/i, '')
|
||||
if (token !== configuration.cliApiToken) {
|
||||
if (!safeCompareStrings(token, configuration.cliApiToken)) {
|
||||
return c.json({ error: 'Invalid token' }, 401)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user