DevOS v1 + Phase 2: unified resource model, infra ops, website deployment, AI chat ops

- Monorepo: NestJS API (Fastify) + React web (antd/ProComponents) + shared packages
- PostgreSQL/Prisma (unified resources, relations graph, audit, events, jobs)
- Auth/RBAC/workspace isolation, credential vault (AES-256-GCM)
- Servers/SSH, Docker discovery & lifecycle, Job Center
- Websites & deployments (git provider credential clone, env, health verification)
- Domains/DNS (Cloudflare adapter), certificates (TLS probe), monitoring
- Automation (cron/event/webhook HMAC + notifications), AI Gateway, MCP (29 tools)
- AI chat window (LLM function-calling + rules fallback, SSE streaming, approvals)
- Notes: Vditor WYSIWYG markdown editor, non-blocking autosave
- Tests: 35 unit/security tests; UI audit clean (desktop+mobile, deep links)
This commit is contained in:
phptits
2026-09-10 06:35:55 +00:00
commit 0d6cd37579
209 changed files with 22783 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@devos/auth",
"version": "1.0.0",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"@devos/types": "workspace:*"
}
}
+153
View File
@@ -0,0 +1,153 @@
/**
* # @devos/auth
* 认证相关的纯逻辑实现:HS256 JWT 签名/校验、密码哈希/校验。
* 设计原则:零第三方依赖、算法固定(防止 alg 混淆)、常量时间比较。
*/
import { createHmac, randomBytes, randomUUID, timingSafeEqual, scrypt as scryptCb } from "node:crypto";
import { promisify } from "node:util";
/** # scrypt Promise 封装 **/
const scrypt = promisify(scryptCb) as (
password: string,
salt: Buffer,
keylen: number,
) => Promise<Buffer>;
/** # base64url 编码
* @param input 待编码字符串
* @returns base64url 字符串(无填充)
*/
function b64url(input: Buffer | string): string {
return Buffer.from(input).toString("base64url");
}
/** # base64url 解码 */
function b64urlDecode(input: string): Buffer {
return Buffer.from(input, "base64url");
}
/** # JWT 载荷结构 **/
export interface JwtPayload {
/** # 用户 ID **/
sub: string;
/** # 登录邮箱 **/
email: string;
/** # 登录时激活的工作区 ID **/
wid: string | null;
/** # 令牌唯一 ID **/
jti: string;
/** # 签发时间(秒) **/
iat: number;
/** # 过期时间(秒) **/
exp: number;
}
/** # 签发选项 **/
export interface SignOptions {
/** # 有效期(秒),默认 12 小时 **/
expiresInSec?: number;
/** # 当前时间(秒),用于测试注入 **/
nowSec?: number;
}
/** # 签发 HS256 JWT
* @param payload 业务载荷(不含 iat/exp/jti)
* @param secret 应用密钥
* @param options 签发选项
* @returns JWT 字符串
*/
export function signJwt(
payload: Omit<JwtPayload, "iat" | "exp" | "jti">,
secret: string,
options: SignOptions = {},
): string {
const now = options.nowSec ?? Math.floor(Date.now() / 1000);
const expiresInSec = options.expiresInSec ?? 12 * 3600;
const header = { alg: "HS256", typ: "JWT" };
const body: JwtPayload = { ...payload, jti: randomUUID(), iat: now, exp: now + expiresInSec };
const head = b64url(JSON.stringify(header));
const data = b64url(JSON.stringify(body));
const sig = createHmac("sha256", secret).update(`${head}.${data}`).digest("base64url");
return `${head}.${data}.${sig}`;
}
/** # 校验结果 **/
export type VerifyResult = { ok: true; payload: JwtPayload } | { ok: false; reason: string };
/** # 校验 HS256 JWT
* 安全约束:仅接受 alg=HS256 的令牌;签名使用常量时间比较;校验 exp 过期。
* @param token JWT 字符串
* @param secret 应用密钥
* @returns 校验结果
*/
export function verifyJwt(token: string, secret: string, nowSec?: number): VerifyResult {
const parts = token.split(".");
if (parts.length !== 3) return { ok: false, reason: "MALFORMED" };
const [head, data, sig] = parts;
let header: { alg?: string };
try {
header = JSON.parse(b64urlDecode(head).toString("utf8"));
} catch {
return { ok: false, reason: "MALFORMED" };
}
if (header.alg !== "HS256") return { ok: false, reason: "BAD_ALG" };
const expected = createHmac("sha256", secret).update(`${head}.${data}`).digest();
let given: Buffer;
try {
given = b64urlDecode(sig);
} catch {
return { ok: false, reason: "MALFORMED" };
}
if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
return { ok: false, reason: "BAD_SIGNATURE" };
}
let payload: JwtPayload;
try {
payload = JSON.parse(b64urlDecode(data).toString("utf8"));
} catch {
return { ok: false, reason: "MALFORMED" };
}
const now = nowSec ?? Math.floor(Date.now() / 1000);
if (typeof payload.exp !== "number" || payload.exp <= now) return { ok: false, reason: "EXPIRED" };
return { ok: true, payload };
}
/** # 密码哈希
* Bun 运行时下使用 argon2id;否则回退 scrypt。
* @param password 明文密码
* @returns 哈希字符串
*/
export async function hashPassword(password: string): Promise<string> {
const bun = (globalThis as Record<string, unknown>).Bun as typeof Bun | undefined;
if (bun?.password) {
return bun.password.hash(password, { algorithm: "argon2id" });
}
const salt = randomBytes(16);
const key = await scrypt(password, salt, 32);
return `scrypt$${salt.toString("hex")}$${key.toString("hex")}`;
}
/** # 校验密码
* @param password 明文密码
* @param hash 存储的哈希
* @returns 是否匹配
*/
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
if (hash.startsWith("scrypt$")) {
const [, saltHex, keyHex] = hash.split("$");
const key = await scrypt(password, Buffer.from(saltHex, "hex"), 32);
const expected = Buffer.from(keyHex, "hex");
return key.length === expected.length && timingSafeEqual(key, expected);
}
const bun = (globalThis as Record<string, unknown>).Bun as typeof Bun | undefined;
if (bun?.password) {
return bun.password.verify(password, hash);
}
return false;
}
/** # 生成随机令牌(如 API 临时凭证) */
export function randomToken(bytes = 32): string {
return randomBytes(bytes).toString("hex");
}
+15
View File
@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"types": [
"node",
"bun-types"
]
},
"include": [
"src/**/*.ts"
]
}