feat(M02-B): 实现微信登录与可撤销会话
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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.');
|
||||
Reference in New Issue
Block a user