Skip to content

实名认证与两步验证

本章覆盖 HyperFRP Panel 后端的实名认证与两步验证(2FA)接口,共 7 个,全部位于 /api/users/ 路由组下且全部要求 JWT Bearer 认证:实名认证 4 个(人工审核提交、二要素核验、自动二要素核验、实名状态查询),两步验证 3 个(开启、确认、关闭)。

公共行为:所有接口通过 Authorization: Bearer <token> 请求头携带 JWT,Bearer 令牌的获取方式见 认证指南;全部路由经过全站 WAF 防护,并显式禁止缓存(响应头 Cache-Control: no-store, no-cache, must-revalidate, private)。所有响应均为统一包络 {"code": 0, "message": "...", "data": ...};200 成功响应的外层 message 固定为「成功」,业务文案位于 data.message。与具体接口无关的通用错误(未认证 401、账户封禁 403 等)见 通用约定

隐私口径:身份证号明文只进不出——明文仅随提交请求进入服务端,落库前经 RSA-4096-OAEP-SHA256 加密;任何接口的响应中,姓名与身份证号都只出现掩码形态(maskedName / maskedCardNo,如 张*110101********1234),不存在回显明文的字段;服务端审计日志仅记录姓名与结果状态,不记录身份证号。

实名认证

实名认证支持两条路径:二要素核验POST /api/users/profile/verifyPOST /api/users/profile/verify/face)调用外部云市场「身份证二要素核验」服务实时比对姓名与身份证号,匹配即立刻生效;人工审核POST /api/users/profile/verify/manual)上传身份证照片进入 pending 状态,由管理员审核。核验通过后账号自动迁移至「正式用户」用户组(若该组存在且用户尚不在其中)。

提交受尝试次数限制:服务端按配置的尝试上限(未配置时兜底 5 次)计数,已实名、有审核中申请或次数用尽时拒绝提交。状态取值:not_submitted(未提交)/ pending(人工审核中)/ approved(已通过)/ rejected(已驳回)。

POST /api/users/profile/verify/manual

以人工审核方式提交实名认证:上传身份证照片并填写姓名、身份证号,进入 pending 状态等待管理员审核。

鉴权:JWT Bearer · 缓存:NoCache

请求体

multipart/form-data

字段类型必填说明
realNamestring(表单字段)真实姓名,2~20 个字符
idCardNumberstring(表单字段)18 位身份证号;前 17 位为数字,末位为数字或 X/x,且校验位须通过 MOD 11-2 校验
prooffile身份证照片,仅允许 jpg / jpeg / png / gif / webp,单文件 ≤ 10MiB;multipart 部件的 Content-Type 须与扩展名匹配(如 image/png),且文件头魔数须为真实图片格式

整个 multipart 请求体在请求体边界中间件中登记的硬上限为 11MiB(单文件 10MiB),超限由中间件直接返回 413。

请求示例

bash
curl -X POST https://api.hyperfrp.com/api/users/profile/verify/manual \
  -H "Authorization: Bearer <token>" \
  -F "realName=张三" \
  -F "idCardNumber=110101199001011237" \
  -F "proof=@/path/to/idcard.png;type=image/png"
javascript
const form = new FormData();
form.append('realName', '张三');
form.append('idCardNumber', '110101199001011237');
form.append('proof', fileInput.files[0]); // 待上传的文件:/path/to/idcard.png

const res = await fetch('https://api.hyperfrp.com/api/users/profile/verify/manual', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer <token>' }, // 不手动设 Content-Type,由浏览器自动写入 boundary
  body: form
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.post(
    'https://api.hyperfrp.com/api/users/profile/verify/manual',
    headers={'Authorization': 'Bearer <token>'},
    files={'proof': open('/path/to/idcard.png', 'rb')},
)
data = res.json()
print(data)
go
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
)

func main() {
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)
	file, _ := os.Open('/path/to/idcard.png')
	defer file.Close()
	part, _ := writer.CreateFormFile('proof', filepath.Base('/path/to/idcard.png'))
	io.Copy(part, file)
	writer.Close()

	req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/users/profile/verify/manual", &buf)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
messagestring固定为 人工认证申请已提交,请等待审核
statusstring固定为 pending

响应示例

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "message": "人工认证申请已提交,请等待审核",
    "status": "pending"
  }
}

错误场景

场景HTTP说明
multipart 表单解析失败400解析实名认证表单失败
请求体读取超时408读取上传请求超时
请求体或单文件超过登记上限413由请求体边界中间件统一返回(该路由单文件 10MiB / 请求体 11MiB);文件本身不超 11MiB 但大于 10MiB 时为 文件大小不能超过10MiB
realName 缺失或长度不在 2~20400请填写真实的姓名
idCardNumber 缺失或长度非 18400请输入有效的18位身份证号码
idCardNumber 校验位不符合 MOD 11-2400请输入有效的身份证号码
缺少 proof 文件400请上传身份证照片
文件扩展名 / Content-Type / 魔数不符合图片要求400只允许上传有效的图片文件
已完成实名认证400您已完成实名认证
有正在审核中的申请400您有正在审核中的认证申请
尝试次数已用尽400您的认证尝试次数已用尽,请联系管理员
服务端创建目录 / 保存文件 / 落库失败500创建上传目录失败 / 保存文件失败 / 提交人工认证失败: ...

注意事项

  • 人工提交不消耗尝试次数;尝试次数只在管理员驳回时 +1。
  • 照片保存为 uploads/verifications/{userId}-{毫秒时间戳}{原扩展名},登记的 proofUrl 仅供管理端审核列表使用;提交后若服务端落库失败,已上传的照片文件会被自动删除。
  • 提交后身份证号即以 RSA-4096-OAEP-SHA256 加密落库、姓名明文入库供审核;is_verified 在管理员审核通过前保持 false,此期间重复提交会被 您有正在审核中的认证申请 拦截。

POST /api/users/profile/verify/face

调用外部「身份证二要素核验」服务实时比对姓名与身份证号:匹配即实名成功(approved),不匹配或服务异常即拒绝(rejected)。路由名中的 face 为历史命名,实际核验的是姓名 + 身份证号二要素,不涉及人脸照片。

鉴权:JWT Bearer · 缓存:NoCache

请求体

字段类型必填说明
realNamestring真实姓名,2~20 个字符
idCardNumberstring18 位身份证号,校验位须通过 MOD 11-2 校验

请求示例

bash
curl -X POST https://api.hyperfrp.com/api/users/profile/verify/face \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"realName":"张三","idCardNumber":"110101199001011237"}'
javascript
const res = await fetch('https://api.hyperfrp.com/api/users/profile/verify/face', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <token>',
  },
  body: JSON.stringify({"realName":"张三","idCardNumber":"110101199001011237"})
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.post(
    'https://api.hyperfrp.com/api/users/profile/verify/face',
    headers={'Authorization': 'Bearer <token>'},
    json={
        'realName': '张三',
        'idCardNumber': '110101199001011237'
    },
)
data = res.json()
print(data)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	var payload bytes.Buffer
	json.NewEncoder(&payload).Encode(map[string]any{
		"realName": "张三",
		"idCardNumber": "110101199001011237",
	})

	req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/users/profile/verify/face", &payload)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
messagestring核验结果文案:实名认证通过姓名与身份证号不匹配,请确认信息后重试实名认证服务暂未开启,请联系管理员
statusstringapproved(匹配,实名成功)或 rejected(不匹配 / 服务不可用等)
maskedNamestringstatus=approved 时返回;脱敏姓名如 张*,优先取外部服务脱敏返回值,缺失时由服务端按「保留首字、其余以 * 补齐」生成
maskedCardNostringstatus=approved 时返回;脱敏身份证号如 110101********1234(优先取外部服务返回值,缺失时由服务端按「前 6 位 + 8 个 * + 末 4 位」生成)

响应示例

核验通过(approved):

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "message": "实名认证通过",
    "status": "approved",
    "maskedName": "张*",
    "maskedCardNo": "110101********1234"
  }
}

核验不通过(rejected,HTTP 仍为 200):

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "message": "姓名与身份证号不匹配,请确认信息后重试",
    "status": "rejected"
  }
}

错误场景

场景HTTP说明
JSON 绑定失败(缺 realName / idCardNumber 或长度不符)400请填写姓名和身份证号码
idCardNumber 校验位不符合 MOD 11-2400请输入有效的18位身份证号码
已完成实名认证400您已完成实名认证
有正在审核中的申请400您有正在审核中的认证申请
尝试次数已用尽400您的认证尝试次数已用尽,请联系管理员

注意事项

  • 每次调用无论核验成功与否都会消耗 1 次尝试次数。
  • rejected 不是 HTTP 错误:不匹配、服务未开启、服务暂时不可用、认证中心库中无此身份证记录(可能为现役军人 / 户口迁移未同步 / 姓名变更未同步 / 未更换二代身份证等原因)、网关响应异常等一律返回 HTTP 200 + status=rejected,具体原因看 message
  • approved 时服务端完成落库:身份证号 RSA 加密存储、is_verified=true、状态置为 approved、脱敏结果存入认证信息字段,并自动迁移「正式用户」用户组。
  • 外部核验服务由服务端统一配置与鉴权,对客户端完全透明;接口只透出核验结果,不暴露第三方服务的任何信息。

POST /api/users/profile/verify

自动二要素核验。与 POST /api/users/profile/verify/face 调用同一个外部核验服务与同一套落库逻辑,差异仅在响应字段名与尝试次数的扣减时机:

差异点verify/faceverify(本接口)
响应状态字段名statusrealNameVerificationStatus
尝试次数扣减成功与失败都 +1仅核验不通过时 +1(含服务未开启等失败),通过不扣减
尝试次数预检位置service 内部handler 预检(错误消息相同)

鉴权:JWT Bearer · 缓存:NoCache

请求体

字段类型必填说明
realNamestring真实姓名,2~20 个字符
idCardNumberstring18 位身份证号,校验位须通过 MOD 11-2 校验

请求示例

bash
curl -X POST https://api.hyperfrp.com/api/users/profile/verify \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"realName":"张三","idCardNumber":"110101199001011237"}'
javascript
const res = await fetch('https://api.hyperfrp.com/api/users/profile/verify', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <token>',
  },
  body: JSON.stringify({"realName":"张三","idCardNumber":"110101199001011237"})
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.post(
    'https://api.hyperfrp.com/api/users/profile/verify',
    headers={'Authorization': 'Bearer <token>'},
    json={
        'realName': '张三',
        'idCardNumber': '110101199001011237'
    },
)
data = res.json()
print(data)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	var payload bytes.Buffer
	json.NewEncoder(&payload).Encode(map[string]any{
		"realName": "张三",
		"idCardNumber": "110101199001011237",
	})

	req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/users/profile/verify", &payload)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
messagestring核验结果文案,取值同 verify/face
realNameVerificationStatusstringapproved(匹配,实名成功)或 rejected(不匹配 / 服务不可用等)
maskedNamestringapproved 时返回,规则同 verify/face
maskedCardNostringapproved 时返回,规则同 verify/face

响应示例

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "message": "实名认证通过",
    "realNameVerificationStatus": "approved",
    "maskedName": "张*",
    "maskedCardNo": "110101********1234"
  }
}

错误场景

场景HTTP说明
JSON 绑定失败(缺 realName / idCardNumber 或长度不符)400请填写完整的实名认证信息
idCardNumber 校验位不符合 MOD 11-2400请输入有效的18位身份证号码
已完成实名认证 / 有审核中申请 / 次数用尽400消息同 verify/face
用户不存在或落库失败500用户不存在 / 保存认证结果失败: ...

注意事项

  • 服务未开启或外部服务异常导致的核验失败同样消耗 1 次尝试次数(失败路径固定 +1)。
  • rejected 同样返回 HTTP 200,原因看 messageapproved 的落库与用户组迁移行为同 verify/face

GET /api/users/profile/verification-status

查询当前用户的实名认证状态与脱敏结果。

鉴权:JWT Bearer · 缓存:NoCache

请求示例

bash
curl -X GET https://api.hyperfrp.com/api/users/profile/verification-status \
  -H "Authorization: Bearer <token>"
javascript
const res = await fetch('https://api.hyperfrp.com/api/users/profile/verification-status', {
  headers: {
    'Authorization': 'Bearer <token>',
  },
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.get(
    'https://api.hyperfrp.com/api/users/profile/verification-status',
    headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)
go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/users/profile/verification-status", nil)
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
realNameVerificationStatusstring实名状态:not_submitted / pending / approved / rejected
isRealNameVerifiedboolean是否已实名
realNamestring脱敏姓名;仅 approved 时返回,其余状态为空串
realNameVerificationMessagestring认证结果信息:自动核验通过时为脱敏结果的 JSON 字符串(如 {"name":"张*","cardNo":"110101********1234"}),人工审核时为管理员审核留言
realNameVerificationTimestring / null最近一次实名操作时间(RFC 3339),无则为 null
realNameVerificationAttemptsnumber已消耗的提交 / 核验尝试次数
realNameVerificationRemainingnumber剩余尝试次数(直接按服务端配置值相减,未配置时可能为 0 或负值)
maxAttemptsnumber服务端配置的尝试上限(未配置时为 0;实际提交校验兜底为 5 次)
canRetrybooleanrejected 状态下次数未用尽时为 true,其余状态固定 false
remainingDaysnumber固定为 0(预留字段)
maskedNamestring脱敏姓名;approved 时优先从存储的脱敏结果读取,缺失时按姓名生成,其余状态为空串
maskedCardNostring脱敏身份证号;规则同 maskedName

响应示例

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "realNameVerificationStatus": "approved",
    "isRealNameVerified": true,
    "realName": "张*",
    "realNameVerificationMessage": "{\"name\":\"张*\",\"cardNo\":\"110101********1234\"}",
    "realNameVerificationTime": "2026-09-19T10:30:00+08:00",
    "realNameVerificationAttempts": 1,
    "realNameVerificationRemaining": 4,
    "maxAttempts": 5,
    "canRetry": false,
    "remainingDays": 0,
    "maskedName": "张*",
    "maskedCardNo": "110101********1234"
  }
}

错误场景

场景HTTP说明
用户不存在500用户不存在

注意事项

  • 该接口带有服务端自愈副作用:approved 用户若尚未迁移「正式用户」组会自动补迁;存在加密身份证数据但状态异常的历史数据会被自动修正为 approved。查询本身不会改动正常用户的实名信息。
  • 身份证号任何形态下都不返回明文,只返回掩码。

两步验证(2FA)

基于 TOTP(算法 SHA1、6 位动态码、30 秒周期)的两步验证,兼容 Google Authenticator、Microsoft Authenticator、1Password 等标准验证器。开启流程:先 enable 获取密钥与 otpauth URI,验证器扫码或手动录入后 confirm 提交一枚动态码完成开启并一次性领取恢复码;disable 关闭需再次提交动态码。密钥与恢复码以 AES 加密存储于服务端 data/2fa/{userId}.enc

confirmdisable 只接受 TOTP 动态码;恢复码不能用于这两个接口,只能在登录的两步验证环节使用。确认与关闭成功都会使 auth_version 递增,该账号所有已签发 JWT 立即失效,需要重新登录。

POST /api/users/2fa/enable

生成新的 TOTP 密钥与 5 枚恢复码并暂存,返回密钥与 otpauth:// URI 供验证器绑定。

鉴权:JWT Bearer · 缓存:NoCache

无请求体。

请求示例

bash
curl -X POST https://api.hyperfrp.com/api/users/2fa/enable \
  -H "Authorization: Bearer <token>"
javascript
const res = await fetch('https://api.hyperfrp.com/api/users/2fa/enable', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <token>',
  },
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.post(
    'https://api.hyperfrp.com/api/users/2fa/enable',
    headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)
go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/users/2fa/enable", nil)
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
secretstringBase32(RFC 4648,无填充)编码的 TOTP 密钥:20 字节随机数编码为 32 字符,用于验证器手动录入
uristringotpauth:// 标准 URI,即二维码内容,格式为 otpauth://totp/HyperZoneFRP:{username}?algorithm=SHA1&digits=6&issuer=HyperZoneFRP&period=30&secret={secret},label 为 HyperZoneFRP:{username}

响应示例

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "secret": "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP",
    "uri": "otpauth://totp/HyperZoneFRP:hyperzone?algorithm=SHA1&digits=6&issuer=HyperZoneFRP&period=30&secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP"
  }
}

错误场景

场景HTTP说明
密钥生成失败或密钥数据写盘失败500启用两步验证失败

注意事项

  • 服务端不校验当前是否已开启 2FA:已开启时再次调用不报错,而是生成一套全新的 secret 与 5 枚新恢复码并覆盖原数据——旧密钥与旧恢复码立即作废,而 two_factor_enabled 仍为 true,必须用新 secret 重新 confirm,否则旧验证器将无法通过登录校验。
  • 本调用仅暂存密钥,two_factor_enabled 不会置为 true,登录行为不变;confirm 成功后才真正生效。
  • 5 枚恢复码在本步生成并加密存盘,但不在本响应中返回,confirm 时一次性返回。
  • 二维码内容即 uri 字段,由客户端自行渲染;也可将 secret 手动录入验证器(SHA1、6 位、30 秒)。

POST /api/users/2fa/confirm

提交一枚 TOTP 动态码确认开启两步验证,成功后一次性返回 5 枚恢复码。

鉴权:JWT Bearer · 缓存:NoCache

请求体

字段类型必填说明
codestring验证器当前显示的 6 位 TOTP 动态码(允许前后各 1 个 30 秒窗口的时钟偏移);恢复码不可用

请求示例

bash
curl -X POST https://api.hyperfrp.com/api/users/2fa/confirm \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"code":"492031"}'
javascript
const res = await fetch('https://api.hyperfrp.com/api/users/2fa/confirm', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <token>',
  },
  body: JSON.stringify({"code":"492031"})
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.post(
    'https://api.hyperfrp.com/api/users/2fa/confirm',
    headers={'Authorization': 'Bearer <token>'},
    json={
        'code': '492031'
    },
)
data = res.json()
print(data)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	var payload bytes.Buffer
	json.NewEncoder(&payload).Encode(map[string]any{
		"code": "492031",
	})

	req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/users/2fa/confirm", &payload)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
messagestring固定为 两步验证已启用
recoveryCodesstring[]5 枚一次性恢复码,每枚 8 字符(字符集 23456789ABCDEFGHJKMNPQRSTUVWXYZ,不含易混淆的 0/1/I/L/O);仅在确认成功的本响应中返回这一次

响应示例

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "message": "两步验证已启用",
    "recoveryCodes": ["K7M2P4QX", "R9T3W6YB", "C4D7F2HN", "J5M8P3TR", "W2X6Y9AB"]
  }
}

错误场景

场景HTTP说明
缺少 code400请输入验证码
未先调用 enable(服务端无该用户的密钥数据)或数据损坏400服务端原始错误文本
验证码错误(非当前时间窗内的有效 TOTP 码)400验证码错误
数据库更新失败400该接口将服务层错误统一按 400 返回,文本为原始错误信息

注意事项

  • 确认成功后 two_factor_enabled=trueauth_version 递增:该账号所有已签发 JWT 立即失效(认证中间件校验 auth_version 一致性),所有已登录会话需重新登录,此后登录进入两步验证环节。
  • recoveryCodes 仅此一次返回;服务端以加密形态存储副本但列表不再展示,请立即妥善保存。丢失后只能先关闭 2FA 再重新开启换取新码。
  • 关闭并重新开启会生成全新的一组恢复码,旧恢复码一律作废。

POST /api/users/2fa/disable

关闭两步验证:提交一枚 TOTP 动态码校验通过后关闭开关、删除服务端密钥数据(未用完的恢复码一并销毁),此后登录不再要求 2FA。

鉴权:JWT Bearer · 缓存:NoCache

请求体

字段类型必填说明
codestring验证器当前显示的 6 位 TOTP 动态码;恢复码不可用

请求示例

bash
curl -X POST https://api.hyperfrp.com/api/users/2fa/disable \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"code":"718294"}'
javascript
const res = await fetch('https://api.hyperfrp.com/api/users/2fa/disable', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <token>',
  },
  body: JSON.stringify({"code":"718294"})
});
const data = await res.json();
console.log(data);
python
import requests

res = requests.post(
    'https://api.hyperfrp.com/api/users/2fa/disable',
    headers={'Authorization': 'Bearer <token>'},
    json={
        'code': '718294'
    },
)
data = res.json()
print(data)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	var payload bytes.Buffer
	json.NewEncoder(&payload).Encode(map[string]any{
		"code": "718294",
	})

	req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/users/2fa/disable", &payload)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer <token>")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result map[string]any
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result)
}

响应字段

字段类型说明
messagestring固定为 两步验证已禁用

响应示例

json
{
  "code": 0,
  "message": "成功",
  "data": {
    "message": "两步验证已禁用"
  }
}

错误场景

场景HTTP说明
缺少 code400请输入验证码
未先调用 enable(服务端无该用户的密钥数据)或数据损坏400服务端原始错误文本
验证码错误400验证码错误
数据库更新或密钥数据删除失败400该接口将服务层错误统一按 400 返回,文本为原始错误信息

注意事项

  • 关闭成功后 two_factor_enabled=falseauth_version 递增:已签发 JWT 全部失效,需重新登录;重新登录后不再要求 2FA。
  • 服务端删除 data/2fa/{userId}.enc,其中未用完的恢复码一并销毁;若日后重新开启,恢复码为全新一组。

本站基于 VitePress 构建,文档内容以 backend-Go handler 源码为权威依据