import assert from 'node:assert/strict'; import { buildApp } from '../dist/app.js'; import { AdminAuthError } from '../dist/auth/admin-auth-repository.js'; import { hashPassword, verifyPassword } from '../dist/auth/password.js'; const secret = 'test-only-admin-password-auth-secret-32'; const user = { id: '21', tenantId: '7', userType: 'STAFF', status: 'ACTIVE', roleVersion: 1, nickname: '租户管理员', avatarUrl: '', phone: '13800138000' }; let loginInput; let refreshInput; let credentialInput; let revokedSessionId; const repository = { async loginWithPassword(input) { loginInput = input; if (input.password === 'wrong-password') throw new AdminAuthError('ADMIN_LOGIN_INVALID'); if (input.password === 'locked-password') throw new AdminAuthError('ADMIN_LOGIN_LOCKED'); return { id: input.sessionId, tenantId: '7', platformAppId: '9', user, expiresAt: input.expiresAt }; }, async rotateRefreshToken(input) { refreshInput = input; return { id: input.sessionId, tenantId: '7', platformAppId: '9', user, expiresAt: new Date(Date.now() + 60000) }; }, async setCredential(actor, tenantId, input) { credentialInput = { actor, tenantId, input }; return { userId: input.userId, configured: true }; } }; const authRepository = { async validateSession(sessionId) { return { id: sessionId, tenantId: '7', platformAppId: '9', user, expiresAt: new Date(Date.now() + 60000) }; }, async revokeSession(sessionId) { revokedSessionId = sessionId; return true; } }; const access = { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] }; const app = await buildApp({ adminAuth: { repository, authRepository, accessControl: { async getAccessProfile() { return access; } }, jwtSecret: secret, accessTokenTtlSeconds: 900, sessionTtlSeconds: 604800 } }); const login = await app.inject({ method: 'POST', url: '/admin-api/auth/login', payload: { tenantCode: 'demo', loginName: 'Admin.User', ['password']: 'ValidPassword!123' } }); assert.equal(login.statusCode, 200); assert.equal(loginInput.tenantCode, 'demo'); assert.equal(login.json().data.user.phone, undefined); assert.deepEqual(login.json().data.access.roles, ['TENANT_ADMIN']); assert.ok(login.json().data.access.menus.includes('system')); assert.ok(login.json().data.access.menus.includes('products')); assert.match(login.json().data.accessToken, /^[^.]+\.[^.]+\.[^.]+$/); assert.match(login.json().data.refreshToken, /^[0-9a-f-]{36}\.[A-Za-z0-9_-]{43}$/); const successfulSessionId = loginInput.sessionId; const bad = await app.inject({ method: 'POST', url: '/admin-api/auth/login', payload: { tenantCode: 'demo', loginName: 'admin', ['password']: 'wrong-password' } }); assert.equal(bad.statusCode, 401); const locked = await app.inject({ method: 'POST', url: '/admin-api/auth/login', payload: { tenantCode: 'demo', loginName: 'admin', ['password']: 'locked-password' } }); assert.equal(locked.statusCode, 423); const refreshed = await app.inject({ method: 'POST', url: '/admin-api/auth/refresh', payload: { refreshToken: login.json().data.refreshToken } }); assert.equal(refreshed.statusCode, 200); assert.equal(refreshInput.sessionId, successfulSessionId); assert.notEqual(refreshed.json().data.refreshToken, login.json().data.refreshToken); const auth = { authorization: `Bearer ${login.json().data.accessToken}` }; const me = await app.inject({ method: 'GET', url: '/admin-api/auth/me', headers: auth }); assert.equal(me.statusCode, 200); assert.equal(me.json().data.user.nickname, '租户管理员'); const weakCredential = await app.inject({ method: 'PUT', url: '/admin-api/auth/credentials/22', headers: auth, payload: { loginName: 'operator', ['password']: 'too-weak' } }); assert.equal(weakCredential.statusCode, 400); const credential = await app.inject({ method: 'PUT', url: '/admin-api/auth/credentials/22', headers: auth, payload: { loginName: 'Operator.22', ['password']: 'StrongPassword!2026' } }); assert.equal(credential.statusCode, 200); assert.equal(credentialInput.tenantId, '7'); assert.equal(credentialInput.input.loginName, 'Operator.22'); assert.match(credentialInput.input.passwordHash, /^scrypt\$16384\$8\$1\$/); const logout = await app.inject({ method: 'POST', url: '/admin-api/auth/logout', headers: auth }); assert.equal(logout.statusCode, 200); assert.equal(revokedSessionId, successfulSessionId); await app.close(); const passwordHash = await hashPassword('StrongPassword!2026'); assert.equal(await verifyPassword('StrongPassword!2026', passwordHash), true); assert.equal(await verifyPassword('WrongPassword!2026', passwordHash), false); assert.equal(await verifyPassword('anything', 'invalid-hash'), false); console.log('PASS: M08-D admin password login, rotating refresh, session cleanup and credential setup are present.');