Files
Agent-System/web/src/api/auth.local.ts
T
weichen d612c1193e fix(web): remove identity simulation and stop sending X-User-ID
Product UI no longer switches sessions locally; API clients attach Bearer
tokens only and LocalAuthClient stays test-only outside the prod bundle.
2026-09-13 02:50:22 +00:00

185 lines
6.0 KiB
TypeScript

/**
* 测试专用本地鉴权客户端 — 仅由单元测试 import,不进入生产 bundle 主路径。
*/
import {
UserAccount,
SystemAuthConfig,
LoginPayload,
LoginResult,
RegisterPayload,
RegisterResult,
CreateAdminUserPayload,
AccountStatus,
} from '../types/auth';
import type { AuthClientOptions } from './auth';
const AUTH_STORAGE_KEY_SESSION = 'smdf_auth_session';
export class LocalAuthClient {
private users: Map<number, UserAccount & { password?: string }> = new Map();
private nextId = 10;
private config: SystemAuthConfig = { allowRegistration: true, requireApproval: false };
private env = 'dev';
private adminUsername = 'admin';
private adminPassword = 'admin-secret-password-123';
constructor(options?: AuthClientOptions) {
if (options?.env) this.env = options.env;
if (options?.adminUsername) this.adminUsername = options.adminUsername;
if (options?.adminPassword) this.adminPassword = options.adminPassword;
if (options?.allowRegistration !== undefined) this.config.allowRegistration = options.allowRegistration;
if (options?.requireApproval !== undefined) this.config.requireApproval = options.requireApproval;
this.initDefaultUsers();
}
private initDefaultUsers() {
const now = new Date().toISOString();
this.users.set(1, {
id: 1,
username: this.adminUsername,
password: this.adminPassword,
name: '系统管理员',
roleId: 'role-admin',
position: '系统管理员',
department: '系统运维部',
employeeId: 'SYS-000',
status: 'active',
mustChangePassword: false,
createdAt: now,
updatedAt: now,
});
}
async login(payload: LoginPayload): Promise<LoginResult> {
const trimmedUser = payload.username.trim();
let matched: (UserAccount & { password?: string }) | undefined;
for (const u of this.users.values()) {
if (u.username.toLowerCase() === trimmedUser.toLowerCase()) {
matched = u;
break;
}
}
if (!matched || matched.password !== payload.password) {
throw new Error('用户名或密码错误');
}
if (matched.status === 'pending_approval') {
throw new Error('账号正在等待管理员审核中,暂无法登录');
}
const { password: _, ...safeUser } = matched;
const result: LoginResult = {
token: `token-${Date.now()}-${safeUser.id}`,
user: safeUser,
mustChangePassword: !!safeUser.mustChangePassword,
env: this.env,
};
this.saveSession(result);
return result;
}
async register(payload: RegisterPayload): Promise<RegisterResult> {
if (!this.config.allowRegistration) {
throw new Error('系统当前已暂停自主注册,请联系系统管理员分配账号');
}
const trimmedUser = payload.username.trim();
if (!trimmedUser || !payload.password || !payload.name?.trim()) {
throw new Error('用户名、密码与姓名为必填项');
}
for (const u of this.users.values()) {
if (u.username.toLowerCase() === trimmedUser.toLowerCase()) {
throw new Error(`用户名已存在: ${trimmedUser}`);
}
}
const now = new Date().toISOString();
const status: AccountStatus = this.config.requireApproval ? 'pending_approval' : 'active';
const newId = this.nextId++;
this.users.set(newId, {
id: newId,
username: trimmedUser,
password: payload.password,
name: payload.name.trim(),
roleId: 'role-guest',
position: '访客/待指派',
department: payload.department || '待分配部门',
employeeId: payload.employeeId || `EXT-${newId}`,
status,
mustChangePassword: false,
createdAt: now,
updatedAt: now,
});
return {
status,
message: status === 'pending_approval' ? '注册成功,等待审核' : '注册成功',
userId: newId,
};
}
async changePassword(userId: number, oldPass: string, newPass: string): Promise<boolean> {
const user = this.users.get(userId);
if (!user || user.password !== oldPass) throw new Error('原密码不正确');
user.password = newPass;
user.mustChangePassword = false;
return true;
}
async updateSystemAuthConfig(newConfig: SystemAuthConfig): Promise<SystemAuthConfig> {
this.config = { ...newConfig };
return { ...this.config };
}
async updateUserStatus(userId: number, status: AccountStatus, roleId?: string, position?: string): Promise<UserAccount> {
const user = this.users.get(userId);
if (!user) throw new Error('用户不存在');
user.status = status;
if (roleId) user.roleId = roleId;
if (position) user.position = position;
const { password: _, ...safe } = user;
return safe;
}
async createAdminUser(payload: CreateAdminUserPayload): Promise<UserAccount> {
const now = new Date().toISOString();
const newId = this.nextId++;
const user: UserAccount & { password?: string } = {
id: newId,
username: payload.username.trim(),
password: payload.initialPassword,
name: payload.name.trim(),
roleId: payload.roleId,
position: payload.position,
department: payload.department || '研发中心',
employeeId: payload.employeeId || `EMP-${newId}`,
status: 'active',
mustChangePassword: true,
createdAt: now,
updatedAt: now,
};
this.users.set(newId, user);
const { password: _, ...safe } = user;
return safe;
}
logout() {
if (typeof window !== 'undefined' && window.localStorage) {
window.localStorage.removeItem(AUTH_STORAGE_KEY_SESSION);
}
}
saveSession(session: LoginResult) {
if (typeof window !== 'undefined' && window.localStorage) {
window.localStorage.setItem(AUTH_STORAGE_KEY_SESSION, JSON.stringify(session));
}
}
loadSavedSession(): LoginResult | null {
return null;
}
getSessionToken(): string | null {
return null;
}
}
export function createInMemoryAuthClient(options?: AuthClientOptions): LocalAuthClient {
return new LocalAuthClient({ ...options, localFallback: true });
}