Files
qipai/backend/tests/auth.test.mjs
T

156 lines
4.1 KiB
JavaScript

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.');