feat(server): persist platform public key and enrich OpenAPI docs
This commit is contained in:
@@ -16,7 +16,8 @@ const handler = new OpenAPIHandler(router, {
|
|||||||
info: {
|
info: {
|
||||||
title: name,
|
title: name,
|
||||||
version,
|
version,
|
||||||
description: 'UX 授权服务 OpenAPI 文档:设备授权、任务解密、摘要加密与报告签名打包接口。',
|
description:
|
||||||
|
'UX 授权服务 OpenAPI 文档。该服务用于工具箱侧本地身份初始化与密码学能力调用,覆盖设备授权密文生成、任务二维码解密、摘要信息加密、报告签名打包等流程。\n\n推荐调用顺序:\n1) 写入 licence 与 OpenPGP 私钥;\n2) 读取本机身份状态进行前置校验;\n3) 执行加密/解密与签名接口。\n\n说明:除文件下载接口外,返回体均为 JSON;字段示例已提供,便于联调和 Mock。',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
docsPath: '/docs',
|
docsPath: '/docs',
|
||||||
|
|||||||
@@ -5,18 +5,26 @@ const configOutput = z
|
|||||||
.object({
|
.object({
|
||||||
licence: z.string().nullable().describe('当前本地 licence,未设置时为 null'),
|
licence: z.string().nullable().describe('当前本地 licence,未设置时为 null'),
|
||||||
fingerprint: z.string().describe('UX 本机计算得到的设备特征码(SHA-256)'),
|
fingerprint: z.string().describe('UX 本机计算得到的设备特征码(SHA-256)'),
|
||||||
|
platformPublicKey: z.string().nullable().describe('本地持久化的平台公钥(Base64 编码 SPKI DER),未设置时为 null'),
|
||||||
|
hasPlatformPublicKey: z.boolean().describe('是否已配置平台公钥'),
|
||||||
hasPgpPrivateKey: z.boolean().describe('是否已配置 OpenPGP 私钥'),
|
hasPgpPrivateKey: z.boolean().describe('是否已配置 OpenPGP 私钥'),
|
||||||
})
|
})
|
||||||
|
.describe('本地身份配置快照,用于判断设备授权初始化是否完成')
|
||||||
.meta({
|
.meta({
|
||||||
examples: [
|
examples: [
|
||||||
{
|
{
|
||||||
licence: 'LIC-8F2A-XXXX',
|
licence: 'LIC-8F2A-XXXX',
|
||||||
fingerprint: '9a3b7c1d2e4f5a6b8c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b',
|
fingerprint: '9a3b7c1d2e4f5a6b8c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b',
|
||||||
|
platformPublicKey:
|
||||||
|
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB',
|
||||||
|
hasPlatformPublicKey: true,
|
||||||
hasPgpPrivateKey: true,
|
hasPgpPrivateKey: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
licence: null,
|
licence: null,
|
||||||
fingerprint: '9a3b7c1d2e4f5a6b8c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b',
|
fingerprint: '9a3b7c1d2e4f5a6b8c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b',
|
||||||
|
platformPublicKey: null,
|
||||||
|
hasPlatformPublicKey: false,
|
||||||
hasPgpPrivateKey: false,
|
hasPgpPrivateKey: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -29,10 +37,10 @@ export const get = oc
|
|||||||
operationId: 'configGet',
|
operationId: 'configGet',
|
||||||
summary: '读取本机身份配置',
|
summary: '读取本机身份配置',
|
||||||
description:
|
description:
|
||||||
'返回 UX 本地持久化的 licence、本机设备特征码(fingerprint)以及 OpenPGP 私钥配置状态。工具箱端可据此判断是否已完成本地身份初始化。',
|
'查询 UX 当前本地身份配置状态。\n\n典型用途:页面初始化时检测授权状态、加密前检查平台公钥、签名前检查私钥是否就绪。\n\n返回内容:\n- licence:当前持久化授权码,未设置时为 null;\n- fingerprint:设备特征码(本机自动计算);\n- platformPublicKey:本地平台公钥(用于验签或加密前核对);\n- hasPlatformPublicKey:是否已写入平台公钥;\n- hasPgpPrivateKey:是否已写入 OpenPGP 私钥。',
|
||||||
tags: ['Config'],
|
tags: ['Config'],
|
||||||
})
|
})
|
||||||
.input(z.object({}))
|
.input(z.object({}).describe('空请求体,仅触发读取当前配置'))
|
||||||
.output(configOutput)
|
.output(configOutput)
|
||||||
|
|
||||||
export const setLicence = oc
|
export const setLicence = oc
|
||||||
@@ -42,7 +50,7 @@ export const setLicence = oc
|
|||||||
operationId: 'configSetLicence',
|
operationId: 'configSetLicence',
|
||||||
summary: '写入本地 licence',
|
summary: '写入本地 licence',
|
||||||
description:
|
description:
|
||||||
'写入或更新本机持久化的 licence。设备特征码(fingerprint)始终由 UX 本机自动计算,无需外部传入。此接口应在设备授权流程前调用。',
|
'写入或更新本机持久化 licence。\n\n调用时机:设备首次激活、授权码变更、授权修复。\n\n约束与行为:\n- 仅接收 licence 文本;\n- fingerprint 由本机自动计算,不允许外部覆盖;\n- 成功后返回最新配置快照,便于前端立即刷新授权状态。',
|
||||||
tags: ['Config'],
|
tags: ['Config'],
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
@@ -63,7 +71,7 @@ export const setPgpPrivateKey = oc
|
|||||||
operationId: 'configSetPgpPrivateKey',
|
operationId: 'configSetPgpPrivateKey',
|
||||||
summary: '写入本地 OpenPGP 私钥',
|
summary: '写入本地 OpenPGP 私钥',
|
||||||
description:
|
description:
|
||||||
'写入或更新本机持久化的 OpenPGP 私钥(ASCII armored 格式),用于报告签名。私钥与设备绑定,调用报告签名接口时 UX 自动读取,无需每次传入。',
|
'写入或更新本机持久化 OpenPGP 私钥(ASCII armored)。\n\n调用时机:首次导入签名私钥、私钥轮换。\n\n约束与行为:\n- 仅接收 ASCII armored 私钥文本;\n- 私钥保存在本地,后续报告签名接口会自动读取;\n- 成功后返回最新配置快照,可用于确认 hasPgpPrivateKey 状态。',
|
||||||
tags: ['Config'],
|
tags: ['Config'],
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
@@ -80,3 +88,29 @@ export const setPgpPrivateKey = oc
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.output(configOutput)
|
.output(configOutput)
|
||||||
|
|
||||||
|
export const setPlatformPublicKey = oc
|
||||||
|
.route({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/config/set-platform-public-key',
|
||||||
|
operationId: 'configSetPlatformPublicKey',
|
||||||
|
summary: '写入本地平台公钥',
|
||||||
|
description:
|
||||||
|
'写入或更新本机持久化平台公钥(Base64 编码 SPKI DER)。\n\n调用时机:设备授权初始化、平台公钥轮换。\n\n约束与行为:\n- 仅接收平台 RSA 公钥文本;\n- 公钥保存在本地,设备授权密文接口会自动读取,无需每次传参;\n- 成功后返回最新配置快照,可用于确认 hasPlatformPublicKey 状态。',
|
||||||
|
tags: ['Config'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
platformPublicKey: z.string().min(1).describe('平台公钥(Base64 编码 SPKI DER)'),
|
||||||
|
})
|
||||||
|
.meta({
|
||||||
|
examples: [
|
||||||
|
{
|
||||||
|
platformPublicKey:
|
||||||
|
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(configOutput)
|
||||||
|
|||||||
@@ -8,28 +8,16 @@ export const encryptDeviceInfo = oc
|
|||||||
operationId: 'encryptDeviceInfo',
|
operationId: 'encryptDeviceInfo',
|
||||||
summary: '生成设备授权二维码密文',
|
summary: '生成设备授权二维码密文',
|
||||||
description:
|
description:
|
||||||
'将本机 licence 与 fingerprint 组装为 JSON,使用平台 RSA 公钥(RSA-OAEP + SHA-256)加密后返回 Base64 密文,供工具箱生成设备授权二维码。参见《工具箱端 - 设备授权二维码生成指南》。',
|
'生成设备授权流程所需的二维码密文。\n\n处理流程:\n- 读取本机 licence、fingerprint 与本地持久化的平台公钥;\n- 组装为授权载荷 JSON;\n- 使用平台公钥执行 RSA-OAEP(SHA-256) 加密;\n- 返回 Base64 密文供前端生成二维码。\n\n适用场景:设备授权申请、重新授权。\n\n前置条件:需先调用 config.setPlatformPublicKey 写入平台公钥。',
|
||||||
tags: ['Crypto'],
|
tags: ['Crypto'],
|
||||||
})
|
})
|
||||||
.input(
|
.input(z.object({}).describe('空请求体。平台公钥由本地配置自动读取'))
|
||||||
z
|
|
||||||
.object({
|
|
||||||
platformPublicKey: z.string().min(1).describe('平台公钥(Base64,SPKI DER)'),
|
|
||||||
})
|
|
||||||
.meta({
|
|
||||||
examples: [
|
|
||||||
{
|
|
||||||
platformPublicKey:
|
|
||||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.output(
|
.output(
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
encrypted: z.string().describe('Base64 密文(用于设备授权二维码)'),
|
encrypted: z.string().describe('Base64 密文(可直接用于设备授权二维码内容)'),
|
||||||
})
|
})
|
||||||
|
.describe('设备授权密文生成结果')
|
||||||
.meta({
|
.meta({
|
||||||
examples: [
|
examples: [
|
||||||
{
|
{
|
||||||
@@ -46,7 +34,7 @@ export const decryptTask = oc
|
|||||||
operationId: 'decryptTask',
|
operationId: 'decryptTask',
|
||||||
summary: '解密任务二维码数据',
|
summary: '解密任务二维码数据',
|
||||||
description:
|
description:
|
||||||
'使用本机 licence 与 fingerprint 派生 AES-256-GCM 密钥(SHA-256),解密 App 任务二维码中的 Base64 密文,返回任务信息明文。参见《工具箱端 - 任务二维码解密指南》。',
|
'解密 App 下发的任务二维码密文。\n\n处理流程:\n- 基于本机 licence + fingerprint 派生 AES-256-GCM 密钥;\n- 对二维码中的 Base64 密文进行解密;\n- 返回任务明文 JSON 字符串。\n\n适用场景:扫码接收任务后解析任务详情。',
|
||||||
tags: ['Crypto'],
|
tags: ['Crypto'],
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
@@ -54,6 +42,7 @@ export const decryptTask = oc
|
|||||||
.object({
|
.object({
|
||||||
encryptedData: z.string().min(1).describe('Base64 编码的 AES-256-GCM 密文(来自任务二维码扫描结果)'),
|
encryptedData: z.string().min(1).describe('Base64 编码的 AES-256-GCM 密文(来自任务二维码扫描结果)'),
|
||||||
})
|
})
|
||||||
|
.describe('任务二维码解密请求')
|
||||||
.meta({
|
.meta({
|
||||||
examples: [
|
examples: [
|
||||||
{
|
{
|
||||||
@@ -65,8 +54,9 @@ export const decryptTask = oc
|
|||||||
.output(
|
.output(
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
decrypted: z.string().describe('解密后的任务信息 JSON 字符串'),
|
decrypted: z.string().describe('解密后的任务信息 JSON 字符串(可进一步反序列化)'),
|
||||||
})
|
})
|
||||||
|
.describe('任务二维码解密结果')
|
||||||
.meta({
|
.meta({
|
||||||
examples: [
|
examples: [
|
||||||
{
|
{
|
||||||
@@ -84,15 +74,16 @@ export const encryptSummary = oc
|
|||||||
operationId: 'encryptSummary',
|
operationId: 'encryptSummary',
|
||||||
summary: '加密摘要信息',
|
summary: '加密摘要信息',
|
||||||
description:
|
description:
|
||||||
'使用本机 licence 与 fingerprint 通过 HKDF-SHA256 派生密钥,以 AES-256-GCM 加密检查摘要明文并返回 Base64 密文,供工具箱生成摘要信息二维码。参见《工具箱端 - 摘要信息二维码生成指南》。',
|
'加密检查摘要信息并产出二维码密文。\n\n处理流程:\n- 使用 licence + fingerprint 结合 taskId(salt) 通过 HKDF-SHA256 派生密钥;\n- 使用 AES-256-GCM 加密摘要明文;\n- 返回 Base64 密文用于摘要二维码生成。\n\n适用场景:任务执行后提交摘要信息。',
|
||||||
tags: ['Crypto'],
|
tags: ['Crypto'],
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
salt: z.string().min(1).describe('HKDF salt(即 taskId,从任务二维码中获取)'),
|
salt: z.string().min(1).describe('HKDF salt(通常为 taskId,需与任务上下文一致)'),
|
||||||
plaintext: z.string().min(1).describe('待加密的摘要信息 JSON 明文'),
|
plaintext: z.string().min(1).describe('待加密的摘要信息 JSON 明文字符串'),
|
||||||
})
|
})
|
||||||
|
.describe('摘要信息加密请求')
|
||||||
.meta({
|
.meta({
|
||||||
examples: [
|
examples: [
|
||||||
{
|
{
|
||||||
@@ -106,8 +97,9 @@ export const encryptSummary = oc
|
|||||||
.output(
|
.output(
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
encrypted: z.string().describe('Base64 密文(用于摘要信息二维码)'),
|
encrypted: z.string().describe('Base64 密文(用于摘要信息二维码内容)'),
|
||||||
})
|
})
|
||||||
|
.describe('摘要信息加密结果')
|
||||||
.meta({
|
.meta({
|
||||||
examples: [
|
examples: [
|
||||||
{
|
{
|
||||||
@@ -124,11 +116,12 @@ export const signAndPackReport = oc
|
|||||||
operationId: 'signAndPackReport',
|
operationId: 'signAndPackReport',
|
||||||
summary: '签名并打包检查报告',
|
summary: '签名并打包检查报告',
|
||||||
description:
|
description:
|
||||||
'上传包含 summary.json 的原始报告 ZIP,UX 自动从 ZIP 中提取 summary.json,使用本地存储的 licence/fingerprint 计算设备签名(HKDF + HMAC-SHA256),并使用本地 OpenPGP 私钥生成分离式签名。返回包含 summary.json(含 deviceSignature)、META-INF/manifest.json、META-INF/signature.asc 的签名报告 ZIP。参见《工具箱端 - 报告加密与签名生成指南》。',
|
'对原始报告执行设备签名与 OpenPGP 签名并重新打包。\n\n处理流程:\n- 解析上传 ZIP 并提取 summary.json;\n- 用 licence/fingerprint 计算 deviceSignature(HKDF + HMAC-SHA256) 并回写 summary.json;\n- 生成 META-INF/manifest.json;\n- 使用本地 OpenPGP 私钥生成 detached signature(`META-INF/signature.asc`);\n- 返回签名后 ZIP。\n\n适用场景:检查结果归档、可追溯签名分发。',
|
||||||
tags: ['Crypto', 'Report'],
|
tags: ['Crypto', 'Report'],
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z
|
||||||
|
.object({
|
||||||
rawZip: z
|
rawZip: z
|
||||||
.file()
|
.file()
|
||||||
.mime(['application/zip', 'application/x-zip-compressed'])
|
.mime(['application/zip', 'application/x-zip-compressed'])
|
||||||
@@ -141,7 +134,8 @@ export const signAndPackReport = oc
|
|||||||
.optional()
|
.optional()
|
||||||
.describe('返回 ZIP 文件名(可选,默认 signed-report.zip)')
|
.describe('返回 ZIP 文件名(可选,默认 signed-report.zip)')
|
||||||
.meta({ examples: ['signed-report.zip'] }),
|
.meta({ examples: ['signed-report.zip'] }),
|
||||||
}),
|
})
|
||||||
|
.describe('报告签名与打包请求'),
|
||||||
)
|
)
|
||||||
.output(
|
.output(
|
||||||
z
|
z
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { validatePgpPrivateKey } from '@furtherverse/crypto'
|
import { validatePgpPrivateKey } from '@furtherverse/crypto'
|
||||||
import { ORPCError } from '@orpc/server'
|
import { ORPCError } from '@orpc/server'
|
||||||
import { ensureUxConfig, setUxLicence, setUxPgpPrivateKey } from '@/server/ux-config'
|
import { ensureUxConfig, setUxLicence, setUxPgpPrivateKey, setUxPlatformPublicKey } from '@/server/ux-config'
|
||||||
import { db } from '../middlewares'
|
import { db } from '../middlewares'
|
||||||
import { os } from '../server'
|
import { os } from '../server'
|
||||||
|
|
||||||
const toConfigOutput = (config: { licence: string | null; fingerprint: string; pgpPrivateKey: string | null }) => ({
|
const toConfigOutput = (config: {
|
||||||
|
licence: string | null
|
||||||
|
fingerprint: string
|
||||||
|
platformPublicKey: string | null
|
||||||
|
pgpPrivateKey: string | null
|
||||||
|
}) => ({
|
||||||
licence: config.licence,
|
licence: config.licence,
|
||||||
fingerprint: config.fingerprint,
|
fingerprint: config.fingerprint,
|
||||||
|
platformPublicKey: config.platformPublicKey,
|
||||||
|
hasPlatformPublicKey: config.platformPublicKey != null,
|
||||||
hasPgpPrivateKey: config.pgpPrivateKey != null,
|
hasPgpPrivateKey: config.pgpPrivateKey != null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -30,3 +37,8 @@ export const setPgpPrivateKey = os.config.setPgpPrivateKey.use(db).handler(async
|
|||||||
const config = await setUxPgpPrivateKey(context.db, input.pgpPrivateKey)
|
const config = await setUxPgpPrivateKey(context.db, input.pgpPrivateKey)
|
||||||
return toConfigOutput(config)
|
return toConfigOutput(config)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const setPlatformPublicKey = os.config.setPlatformPublicKey.use(db).handler(async ({ context, input }) => {
|
||||||
|
const config = await setUxPlatformPublicKey(context.db, input.platformPublicKey)
|
||||||
|
return toConfigOutput(config)
|
||||||
|
})
|
||||||
|
|||||||
@@ -53,15 +53,21 @@ const requireIdentity = async (dbInstance: Parameters<typeof getUxConfig>[0]) =>
|
|||||||
return config as typeof config & { licence: string }
|
return config as typeof config & { licence: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encryptDeviceInfo = os.crypto.encryptDeviceInfo.use(db).handler(async ({ context, input }) => {
|
export const encryptDeviceInfo = os.crypto.encryptDeviceInfo.use(db).handler(async ({ context }) => {
|
||||||
const config = await requireIdentity(context.db)
|
const config = await requireIdentity(context.db)
|
||||||
|
|
||||||
|
if (!config.platformPublicKey) {
|
||||||
|
throw new ORPCError('PRECONDITION_FAILED', {
|
||||||
|
message: 'Platform public key is not configured. Call config.setPlatformPublicKey first.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const deviceInfoJson = JSON.stringify({
|
const deviceInfoJson = JSON.stringify({
|
||||||
licence: config.licence,
|
licence: config.licence,
|
||||||
fingerprint: config.fingerprint,
|
fingerprint: config.fingerprint,
|
||||||
})
|
})
|
||||||
|
|
||||||
const encrypted = rsaOaepEncrypt(deviceInfoJson, input.platformPublicKey)
|
const encrypted = rsaOaepEncrypt(deviceInfoJson, config.platformPublicKey)
|
||||||
return { encrypted }
|
return { encrypted }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export const uxConfigTable = sqliteTable('ux_config', {
|
|||||||
singletonKey: text('singleton_key').notNull().unique().default('default'),
|
singletonKey: text('singleton_key').notNull().unique().default('default'),
|
||||||
licence: text('licence'),
|
licence: text('licence'),
|
||||||
fingerprint: text('fingerprint').notNull(),
|
fingerprint: text('fingerprint').notNull(),
|
||||||
|
platformPublicKey: text('platform_public_key'),
|
||||||
pgpPrivateKey: text('pgp_private_key'),
|
pgpPrivateKey: text('pgp_private_key'),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -54,3 +54,15 @@ export const setUxPgpPrivateKey = async (db: DB, pgpPrivateKey: string) => {
|
|||||||
|
|
||||||
return rows[0] as (typeof rows)[number]
|
return rows[0] as (typeof rows)[number]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setUxPlatformPublicKey = async (db: DB, platformPublicKey: string) => {
|
||||||
|
const config = await ensureUxConfig(db)
|
||||||
|
|
||||||
|
const rows = await db
|
||||||
|
.update(uxConfigTable)
|
||||||
|
.set({ platformPublicKey })
|
||||||
|
.where(eq(uxConfigTable.id, config.id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
return rows[0] as (typeof rows)[number]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user