npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

human-verify

v1.2.2

Published

人机验证库 - 行为分析验证

Downloads

430

Readme

HumanVerify 人机验证库

基于行为特征分析与全屏点击挑战的人机验证库,前后端分离架构。Vue 已打包进库,无需使用者安装 Vue。

包内容

human-verify/
├── frontend/
│   ├── human-verify.es.js     # 前端 ES 模块(混淆,Vue 已打包)
│   ├── human-verify.umd.js    # 前端 UMD 模块(混淆,Vue 已打包)
│   └── index.d.ts             # 前端类型声明
├── backend/
│   ├── index.js               # 后端验证库(混淆)
│   └── index.d.ts             # 后端类型声明
├── README.md
└── package.json

安装

npm install human-verify

前端使用

组件模式(Vue 3 项目)

<script setup>
import { HumanVerify } from 'human-verify'

async function onVerified(result) {
  const details = result.details
  const pow = details?.pow
  if (pow) {
    // 使用者自行调用 pow-verify 接口完成最终验证
    const verifyResp = await fetch('/api/verify/pow-verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId: details.sessionId, nonce: pow.nonce })
    })
    const verifyResult = await verifyResp.json()
    if (verifyResult.passed) {
      console.log('最终验证通过')
    }
  }
}
</script>

<template>
  <HumanVerify
    challenge-url="/api/verify/challenge"
    pow-challenge-url="/api/verify/pow-challenge"
    @verified="onVerified"
    @failed="onFailed"
  />
</template>

函数模式

import { humanVerify } from 'human-verify'

const result = await humanVerify({
  challengeUrl: '/api/verify/challenge',
  powChallengeUrl: '/api/verify/pow-challenge',
  onProgress: (progress, status) => {
    console.log(`${progress}%: ${status}`)
  }
})

if (result.passed && result.pow) {
  // 使用者自行调用 pow-verify 接口完成最终验证
  const verifyResp = await fetch('/api/verify/pow-verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      sessionId: result.sessionId,
      nonce: result.pow.nonce
    })
  })
  const verifyResult = await verifyResp.json()
  console.log('最终验证结果:', verifyResult)
}

自定义元素模式(纯 HTML)

<script src="/lib/human-verify.umd.js"></script>

<human-verify
  challenge-url="/api/verify/challenge"
  pow-challenge-url="/api/verify/pow-challenge"
  onverify-id="my-callback"
></human-verify>

<script>
  window.HumanVerifyCallbacks.set('my-callback', async (result) => {
    const details = result.details
    const pow = details?.pow
    if (pow) {
      // 使用者自行调用 pow-verify 接口
      const verifyResp = await fetch('/api/verify/pow-verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sessionId: details.sessionId, nonce: pow.nonce })
      })
      const verifyResult = await verifyResp.json()
      if (verifyResult.passed) {
        console.log('验证通过,得分:', details.score)
      }
    }
  })
</script>

组件属性

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | challenge-url | string | '/api/verify/challenge' | 获取挑战接口 URL | | pow-challenge-url | string | '/api/verify/pow-challenge' | 提交行为验证 + POW 挑战接口 URL | | timeout | number | 30000 | 超时时间(ms) | | onverify-id | string | — | 回调 ID,通过 window.HumanVerifyCallbacks.set(id, fn) 注册 |

组件事件

| 事件 | detail | 说明 | |------|--------|------| | verified | { passed, reason, details } | 行为验证通过 + POW 计算完成,pow 等信息在 details 中 | | failed | { reason, details } | 验证失败,详细信息在 details 中 | | progress | { progress, status } | 进度更新 |

返回结果

interface VerifyResult {
  passed: boolean       // 行为验证是否通过
  score: number         // 行为评分 (0-100)
  sessionId: string     // 会话 ID(供使用者调用 pow-verify 接口)
  pow?: {
    challenge: string
    nonce: number       // POW 计算结果(供使用者提交)
    powTarget: number   // POW 目标阈值
    powDurationMs: number // POW 计算耗时(ms)
  }
  reason: string        // 提示信息:仅返回"人机验证成功"或"人机验证失败"
  details?: {
    serverReason?: string  // 组件模式下可能包含服务端原始失败原因(用于调试);函数模式不返回
  }
}

注意: reason 字段仅用于 UI 展示,始终返回固定文本(成功="人机验证成功",失败="人机验证失败")。函数模式不暴露 details.serverReason,避免泄露具体是哪项校验失败(如"点击未命中挑战目标")被逆向利用;组件模式保留该字段仅供调试。

后端使用

import {
  createSession,
  verifySession,
  generatePOWChallenge,
  verifyPOWSolution,
  detectAutomation,
  analyzeBehavior,
  configureSessionStore,
  createRedisSessionStore
} from 'human-verify/backend'

后端库为 ESM 格式,Node.js 18+。所有会话相关函数均为 async(返回 Promise),请使用 await 调用。

会话存储(内存 / Redis 集群)

默认使用内存 Map(单实例)。部署到多实例/集群时,各实例内存互相隔离, 会话会"串"到其他节点导致验证失败。此时启用 Redis 存储,由调用方传入 Redis 操作函数(任意 Redis 客户端均可,如 ioredisredis、云厂商 SDK):

import { configureSessionStore, createRedisSessionStore } from 'human-verify/backend'

// 不调用 configureSessionStore → 内存模式(默认)
// 调用后 → Redis 模式,多实例共享同一份会话
configureSessionStore(createRedisSessionStore({
  // 例:ioredis
  get: (key) => client.get(key),
  set: (key, value, ttlMs) => client.set(key, value, 'PX', ttlMs),
  del: (key) => client.del(key)
  // 可选:keyPrefix 默认 'hv:session:'
}))

// 建议在服务启动时(创建任何会话前)调用一次
  • configureSessionStore(store) — 切换会话存储,可在启动时调用一次
  • createRedisSessionStore({ get, set, del, keyPrefix? }) — 用 Redis 操作函数构造存储;会话过期交给 Redis 原生 TTL,防重放删除通过 del 同步到所有实例
  • 也可自定义实现 SessionStoreget/set/del)接入其他存储(如 Memcached、数据库)

函数列表

| 函数 | 参数 | 返回值 | 说明 | |------|------|--------|------| | createSession(options?) | { viewport?: { width, height } }(可选,前端上报的视口尺寸,缺省 1920×1080;挑战图按该尺寸 1:1 原样生成,不缩放不变形;最小边 <320 拒绝) | Promise<{ sessionId, publicKey, image, expiresAt }> | 创建会话,生成 RSA 密钥对;按视口尺寸生成全屏透明挑战图(data URL),目标图案/颜色/坐标随机,坐标仅保存在服务端,不下发 | | verifySession(sessionId, encryptedData) | sessionId, 加密数据 | Promise<{ success, passed?, score?, reason?, error?, details? }> | 解密并校验点击位置是否命中服务端保存的目标 + 行为评分;分数阈值由会话级随机配置决定,客户端无法指定;details.serverReason 存原始原因 | | generatePOWChallenge(sessionId) | sessionId | Promise<{ success, challenge?, powTarget?, estimatedIterations?, error? }> | 根据评分生成 POW 挑战 | | verifyPOWSolution(sessionId, nonce) | sessionId, nonce | Promise<{ success, passed?, score?, error? }> | 验证 POW 结果,无论成功失败都销毁会话(防重放) | | detectAutomation(fingerprint) | DeviceFingerprint | { isBot, reason } | 检测自动化工具 | | analyzeBehavior(data, challengeResult, fingerprint?, weights?, threshold?, target?) | 行为数据, 挑战结果, ... | VerifyResponse | 分析行为数据(含点击位置与视口目标比对),reason 统一返回固定文本,details.serverReason 存原始原因 | | configureSessionStore(store) | SessionStore | void | 切换会话存储(见上节),启动时调用一次 | | createRedisSessionStore(opts) | { get, set, del, keyPrefix? } | SessionStore | 用 Redis 操作函数构造存储(见上节) |

示例

import express from 'express'
import { createSession, verifySession, generatePOWChallenge, verifyPOWSolution } from 'human-verify/backend'

const app = express()
app.use(express.json())

// 1. 创建验证会话(生成全屏挑战图)
app.post('/api/verify/challenge', async (req, res) => {
  const session = await createSession({ viewport: req.body?.viewport })
  res.json(session)
})

// 2. 行为验证 + POW 挑战
app.post('/api/verify/pow-challenge', async (req, res) => {
  const { sessionId, encryptedData } = req.body
  // 阈值由会话级随机配置决定,客户端无法指定
  const result = await verifySession(sessionId, encryptedData)
  if (!result.success) {
    return res.status(400).json({ error: result.error })
  }
  if (!result.passed) {
    // 验证失败,会话已销毁
    return res.json({ passed: false, score: result.score, reason: result.reason, details: result.details })
  }
  // 验证通过,生成 POW 挑战(会话保留供 POW 验证)
  const pow = await generatePOWChallenge(sessionId)
  res.json({ passed: true, score: result.score, pow, reason: result.reason })
})

// 3. POW 验证(使用者自行调用)
app.post('/api/verify/pow-verify', async (req, res) => {
  const { sessionId, nonce } = req.body
  const result = await verifyPOWSolution(sessionId, nonce)
  if (!result.success) {
    return res.status(400).json({ error: result.error })
  }
  // 无论成功失败,会话都已销毁(防重放)
  res.json({ passed: result.passed, score: result.score })
})

app.listen(3000)

验证流程

前端                                          后端
 │                                             │
 │  POST /api/verify/challenge                 │
 │   { viewport: { width, height } }           │
 │ ──────────────────────────────────────────► │  createSession()
 │ ◄────────────────────────────────────────── │  → { sessionId, publicKey, image }
 │                                             │   目标坐标仅存服务端,不下发
 │                                             │
 │  将 image 铺满屏幕(除目标图案外透明)        │
 │  用户点击随机图案(形状/颜色每次不同)         │
 │  采集指纹 + 行为数据 + 点击位置               │
 │  Web Worker 加密(RSA + AES)                │
 │                                             │
 │  POST /api/verify/pow-challenge             │
 │   { sessionId, encryptedData }              │
 │ ──────────────────────────────────────────► │  verifySession() → 校验点击位置
 │                                             │    命中视口目标 + 行为评分
 │                                             │  (失败则销毁会话)
 │                                             │  generatePOWChallenge()
 │ ◄────────────────────────────────────────── │  → { passed, score, pow }
 │                                             │
 │  Web Worker 计算 POW(实时进度)              │
 │  → 得到 nonce                               │
 │                                             │
 │  使用者自行调用 pow-verify                   │
 │  POST /api/verify/pow-verify                │
 │   { sessionId, nonce }                      │
 │ ──────────────────────────────────────────► │  verifyPOWSolution()
 │                                             │  (销毁会话,防重放)
 │ ◄────────────────────────────────────────── │  → { passed, score }

安全机制

  • 全屏挑战图 — 后端按前端上报的视口尺寸生成透明 PNG 铺满屏幕,目标图案(圆形/圆环/方形/菱形/五角星/十字/三角形)与颜色每次随机;目标坐标仅保存在服务端,不在接口中下发
  • 防图像解码提取 — PNG 每行随机使用 None/Sub 滤波(对抗按滤波 0 解析的轻量解码器)、全屏低透明噪点、大范围极淡渐变云、低透明度假目标点、相对目标偏移的淡光晕,抬高"解码图像反推坐标"的自动化成本
  • 点击位置强校验 — 前端点击后立即清除图片并把点击位置传回,服务端比对视口目标(容差 ≤60px),且要求点击位置与真实采集的点击一致(防伪造字段)
  • 视口防盲猜 — 挑战图按上报视口 1:1 原样生成(手机等任意尺寸不缩放不变形);最小边 <320 的视口直接拒绝创建会话(真实设备视口最小边 ≥320,只有伪造请求会触发)——否则过小视口(如 200×200)的目标可达范围会被压缩到容差圆之内,攻击者直接提交视口中心即可 100% 命中
  • IP 失败限流 — 同一 IP 5 分钟窗口内验证失败 ≥12 次即拒绝后续请求(429),配合视口校验让"盲猜重试"通道失效
  • 防重放 — 会话一次性使用,验证即销毁,过期时间 5 分钟
  • 双层加密 — RSA-OAEP-SHA256(2048 位)+ AES-256-GCM,Web Worker 中执行
  • 工作量证明 — SHA-256 前导零位,难度 1-10 秒,Web Worker 计算
  • 自动化检测 — Playwright / Puppeteer / Selenium / Headless 检测
  • 设备指纹 — 100+ 采集项(Canvas / WebGL / Audio / Fonts / WebRTC)
  • 代码混淆 — 控制流扁平化 / 死代码注入 / 字符串数组化

License

MIT