新增 MFA 验证
要求
适用环境
- 私有部署
ONES 版本
v6.3.0+
依赖包版本
{
"dependencies": {
"@ones-op/fetch": "0.46.12+",
"@ones-open/node-host": "0.4.2+",
"@ones/cli-plugin": "1.29.0+",
"@ones/cli-plugin-template": "1.10.8+",
"@ones-op/sdk": "0.46.7+"
}
}
特别地,还需要在插件 backend 也安装 @ones-open/node-host
cd backend/
npm install @ones-open/node-host@0.4.2+
能力概述
本文档介绍如何通过功能扩展添加一种 MFA 验证方式(例如手机短信验证)。
功能扩展配置
在 config/plugin.yaml
中添加以下配置:
oauth:
type:
- admin
scope:
- read:account:user
extension:
- twoFactorAuthenticator:
provider: smsProvider
funcs:
- name: getTwoFactorAuthenticatorName
url: getTwoFactorAuthenticatorName
- name: hasBound
url: hasBound
- name: isCodeValid
url: isCodeValid
- name: bind
url: bind
slots:
- name: ones:global:authenticator:verify:new
entryUrl: modules/ones-global-authenticator-verify-new-pnME/index.html
- name: ones:global:authenticator:bind:new
entryUrl: modules/ones-global-authenticator-bind-new-NNPG/index.html
- name: ones:global:authenticator:verify:h5:new
entryUrl: modules/ones-global-authenticator-verify-h5-new-N7lg/index.html
注意:@ones/cli 工具暂时还不支持生成功能扩展的配置文件,需要手动填写。
后端实现
功能概览
后端需要实现以下函数:
- getTwoFactorAuthenticatorName:获取验证方式名称
- hasBound:检查用户是否已绑定验证设备(如手机号)
- isCodeValid:检查验证码是否正确
- bind:为用户绑定设备(如手机号)
实现步骤
- 创建文件
backend/src/phone-verification.ts
:
import { FetchAsAdmin } from '@ones-op/fetch'
import { Logger } from '@ones-op/node-logger'
import type { PluginRequest, PluginResponse } from '@ones-op/node-types'
// 存储用户和设备的绑定关系
let userDeviceBindings: { [key: string]: string } = {}
export async function getTwoFactorAuthenticatorName(body: any): Promise<PluginResponse> {
const languages = body?.languages
Logger.info('languages', languages)
return {
statusCode: 200,
body: {
code: 200,
body: {
name: '手机短信验证器',
},
},
}
}
export async function hasBound(body: any): Promise<PluginResponse> {
const authUserUUID = body?.auth_user_uuid
Logger.info('hasBound authUserUUID', authUserUUID)
if (userDeviceBindings[authUserUUID]) {
return {
statusCode: 200,
body: {
code: 200,
body: {
has_bound_device: true,
},
},
}
} else {
return {
statusCode: 200,
body: {
code: 200,
body: {
has_bound_device: false,
},
},
}
}
}
export async function isCodeValid(body: any): Promise<PluginResponse> {
const sessionID = body?.session_id
const authUserUUID = body?.auth_user_uuid
const code = body?.code
Logger.info(
'isCodeValid sessionID: ',
sessionID,
' authUserUUID: ',
authUserUUID,
' code: ',
code,
)
if (code === '123456') {
return {
statusCode: 200,
body: {
code: 200,
body: {
is_valid: true,
},
},
}
} else {
return {
statusCode: 200,
body: {
code: 200,
body: {
is_valid: false,
},
},
}
}
}
export async function bind(body: any): Promise<PluginResponse> {
const sessionID = body?.session_id
const authUserUUID = body?.auth_user_uuid
const identifier = body?.identifier
const code = body?.code
Logger.info(
'bind sessionID: ',
sessionID,
' authUserUUID: ',
authUserUUID,
' identifier: ',
identifier,
' code: ',
code,
)
if (code !== '123456') {
return {
statusCode: 200,
body: {
code: 400,
errcode: 'Plugin.CodeInvalid',
model: 'Plugin.Code',
reason: '验证码错误',
type: 'error',
},
}
}
userDeviceBindings[authUserUUID] = identifier
return {
statusCode: 200,
body: {
code: 200,
body: {},
},
}
}
// 用于导入用户和手机号码绑定关系时,通过组织和用户 uuid 换取 auth_user_uuid
export async function fetchOrganizationAuthUserUUIDs(
organizationID: string,
userUUIDs: string[],
): Promise<Map<string, string>> {
const url = `openapi/v2/account/organization/${organizationID}/auth_user_uuid`
const res = await FetchAsAdmin<{
result: string
data: {
auth_user_uuid: string
user_uuid: string
}[]
}>(url, {
method: 'POST',
params: {
organizationID: organizationID,
},
data: {
users: userUUIDs,
},
headers: {
'Content-Type': 'application/json',
},
})
const authUserUUIDMap = new Map<string, string>()
for (const user of res.data.data) {
authUserUUIDMap.set(user.user_uuid, user.auth_user_uuid)
}
return authUserUUIDMap
}
- 在
backend/src/index.ts
中导出上述函数:
export * from './phone-verification'
函数使用场景和作用
getTwoFactorAuthenticatorName
用于返回新的验证方式的名称。
入参说明:
字段 | 说明 | 数据示例 | 获取方式 |
---|---|---|---|
languages | 语言数组 | ["zh"] | body?.languages |
建议根据不同的语言返回对应的验证方式名称。
hasBound
入参说明:
字段 | 说明 | 数据示例 |
---|---|---|
auth_user_uuid | 用户标识 | DAAprqQf |
这个函数使用场景是:用户登录时,需要知道用户是否已经绑定过手机号,如果绑定过,会跳转到二次验证。开发者要做的事情是根据 auth_user_uuid 去查数据库,看这个用户是否绑定过手机号。
注意,auth_user_uuid 不是用户 uuid,下文会解释 auth_user_uuid 和用户 uuid 的区别以及怎么转换。