feat: migrate runtime backend to node server

This commit is contained in:
victo
2026-04-08 16:41:29 +08:00
parent 9d2fc9e4b8
commit a83841ff2d
70 changed files with 8239 additions and 1561 deletions

View File

@@ -0,0 +1,70 @@
import type { AppContext } from '../context.js';
import { badRequest, unauthorized } from '../errors.js';
import { hashPassword, verifyPassword } from './password.js';
import { signAccessToken } from './token.js';
const USERNAME_PATTERN = /^[A-Za-z0-9_]{3,24}$/u;
function normalizeUsername(username: string) {
return username.trim();
}
function validateCredentials(username: string, password: string) {
if (!USERNAME_PATTERN.test(username)) {
throw badRequest('用户名只允许 3 到 24 位字母、数字、下划线');
}
if (password.length < 6 || password.length > 128) {
throw badRequest('密码长度需要在 6 到 128 位之间');
}
}
export async function entryWithPassword(
context: AppContext,
usernameInput: string,
password: string,
) {
const username = normalizeUsername(usernameInput);
validateCredentials(username, password);
let user = context.userRepository.findByUsername(username);
if (!user) {
const passwordHash = await hashPassword(password);
user = context.userRepository.create(username, passwordHash);
} else {
const isValid = await verifyPassword(user.passwordHash, password);
if (!isValid) {
throw unauthorized('用户名或密码错误');
}
}
if (!user) {
throw new Error('failed to resolve user after auth entry');
}
const token = await signAccessToken(
{
userId: user.id,
tokenVersion: user.tokenVersion,
},
context.config,
);
return {
token,
user: {
id: user.id,
username: user.username,
},
};
}
export async function logoutUser(context: AppContext, userId: string) {
const user = context.userRepository.incrementTokenVersion(userId);
if (!user) {
throw unauthorized('用户不存在');
}
return {
ok: true as const,
};
}