import assert from 'node:assert/strict'; import { buildApp } from '../dist/app.js'; import { signAccessToken, verifyAccessToken } from '../dist/auth/jwt.js'; import { WechatApiError, parseWechatAppSecrets } from '../dist/auth/wechat-client.js'; const secret = 'test-only-jwt-secret-with-at-least-32-characters'; const token = signAccessToken({ sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 3 }, secret, 900, 1000); assert.deepEqual(verifyAccessToken(token, secret, 1001), { iss: 'qipai-api', aud: 'qipai-miniapp', sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 3, iat: 1000, exp: 1900 }); assert.throws(() => verifyAccessToken(token, `${secret}-wrong`, 1001), /signature/); assert.throws(() => verifyAccessToken(token, secret, 1900), /expired/); assert.deepEqual(parseWechatAppSecrets('{"wx-app":"secret-value"}'), { 'wx-app': 'secret-value' }); let sessionValid = true; let revokedSessionId = null; const auth = { repository: { async resolveLoginContext(appId, tenantId) { assert.equal(appId, 'wx-test-app'); assert.equal(tenantId, '7'); return { appId, tenantId, platformAppId: '9' }; }, async loginWithWechat(input) { assert.equal(input.openid, 'openid-test'); return { id: input.sessionId, tenantId: '7', platformAppId: '9', expiresAt: input.expiresAt, user: { id: '21', tenantId: '7', userType: 'CUSTOMER', status: 'ACTIVE', roleVersion: 1, nickname: '', avatarUrl: '', phone: '' } }; }, async validateSession(sessionId, tenantId, userId) { if (!sessionValid) return null; return { id: sessionId, tenantId, platformAppId: '9', expiresAt: new Date(Date.now() + 60_000), user: { id: userId, tenantId, userType: 'CUSTOMER', status: 'ACTIVE', roleVersion: 1, nickname: '', avatarUrl: '', phone: '' } }; }, async revokeSession(sessionId) { revokedSessionId = sessionId; sessionValid = false; return true; } }, wechat: { async exchange(appId, code) { assert.equal(appId, 'wx-test-app'); assert.equal(code, 'valid-code'); return { openid: 'openid-test', unionid: 'unionid-test' }; } }, jwtSecret: secret, accessTokenTtlSeconds: 900, sessionTtlSeconds: 604800 }; const app = await buildApp({ auth }); const login = await app.inject({ method: 'POST', url: '/app-api/auth/wechat-login', headers: { 'x-wechat-appid': 'wx-test-app', 'tenant-id': '7' }, payload: { code: 'valid-code' } }); assert.equal(login.statusCode, 200); const accessToken = login.json().data.accessToken; assert.equal(login.json().data.user.tenantId, '7'); const me = await app.inject({ method: 'GET', url: '/app-api/auth/me', headers: { authorization: `Bearer ${accessToken}` } }); assert.equal(me.statusCode, 200); assert.equal(me.json().data.user.id, '21'); const logout = await app.inject({ method: 'POST', url: '/app-api/auth/logout', headers: { authorization: `Bearer ${accessToken}` } }); assert.equal(logout.statusCode, 200); assert.ok(revokedSessionId); const afterLogout = await app.inject({ method: 'GET', url: '/app-api/auth/me', headers: { authorization: `Bearer ${accessToken}` } }); assert.equal(afterLogout.statusCode, 401); assert.equal(afterLogout.json().code, 'AUTH_SESSION_INVALID'); await app.close(); const failedApp = await buildApp({ auth: { ...auth, wechat: { async exchange() { throw new WechatApiError('invalid code'); } } } }); const failedLogin = await failedApp.inject({ method: 'POST', url: '/app-api/auth/wechat-login', headers: { 'x-wechat-appid': 'wx-test-app', 'tenant-id': '7' }, payload: { code: 'bad-code' } }); assert.equal(failedLogin.statusCode, 401); assert.equal(failedLogin.json().code, 'WECHAT_LOGIN_FAILED'); await failedApp.close(); console.log('PASS: M02-B JWT, WeChat login and revocable session flow is present.');