forked from wangziqi/gongxue-base
feat: add authenticated phone binding
This commit is contained in:
@@ -581,6 +581,16 @@ async function loginBySms(phone = '13800000000') {
|
||||
return verified;
|
||||
}
|
||||
|
||||
async function sendMockSmsCode(phone, purpose) {
|
||||
const sent = await request('/api/auth/sms/send', {
|
||||
userId: false,
|
||||
method: 'POST',
|
||||
body: { phone, purpose },
|
||||
});
|
||||
assert.ok(sent.debugCode, `mock SMS ${purpose} should expose debugCode in local tests`);
|
||||
return sent.debugCode;
|
||||
}
|
||||
|
||||
async function testTrustedSessionIdentity() {
|
||||
const login = await loginBySms();
|
||||
assert.equal(login.user?.id, USER_ID, 'smoke phone should log in as smoke user');
|
||||
@@ -650,6 +660,54 @@ async function testTrustedSessionIdentity() {
|
||||
assert.equal(invalidSession.code, 'AUTH_SESSION_INVALID', 'invalid bearer token must not fall back to legacy user headers');
|
||||
}
|
||||
|
||||
async function testPhoneBinding() {
|
||||
const phoneSuffix = String(Date.now()).slice(-6);
|
||||
const oldPhone = `13920${phoneSuffix}`;
|
||||
const newPhone = `13921${phoneSuffix}`;
|
||||
const login = await loginBySms(oldPhone);
|
||||
const authHeaders = { authorization: `Bearer ${login.session.token}` };
|
||||
|
||||
const bindCode = await sendMockSmsCode(newPhone, 'bind_phone');
|
||||
const bound = await request('/api/auth/phone/bind', {
|
||||
userId: false,
|
||||
headers: authHeaders,
|
||||
method: 'POST',
|
||||
body: { phone: newPhone, code: bindCode },
|
||||
});
|
||||
assert.equal(bound.user?.id, login.user.id, 'phone bind should update the authenticated user');
|
||||
assert.equal(bound.user?.phone, newPhone, 'phone bind should replace platform user phone');
|
||||
assert.equal(bound.phoneChanged, true, 'phone bind should report phoneChanged for a real change');
|
||||
|
||||
const me = await request('/api/auth/me', {
|
||||
userId: false,
|
||||
headers: authHeaders,
|
||||
});
|
||||
assert.equal(me.user?.phone, newPhone, 'current session should see the newly bound phone');
|
||||
|
||||
const oldPhoneLogin = await loginBySms(oldPhone);
|
||||
assert.notEqual(oldPhoneLogin.user?.id, login.user.id, 'old phone identity should no longer log into the changed account');
|
||||
|
||||
const conflictCode = await sendMockSmsCode('13800000000', 'bind_phone');
|
||||
const conflict = await request('/api/auth/phone/bind', {
|
||||
userId: false,
|
||||
headers: authHeaders,
|
||||
method: 'POST',
|
||||
body: { phone: '13800000000', code: conflictCode },
|
||||
expectStatus: 409,
|
||||
});
|
||||
assert.equal(conflict.code, 'PHONE_ALREADY_BOUND', 'binding a phone owned by another account should be rejected');
|
||||
|
||||
const wrongPurposeCode = await sendMockSmsCode('13800000022', 'login');
|
||||
const wrongPurpose = await request('/api/auth/phone/bind', {
|
||||
userId: false,
|
||||
headers: authHeaders,
|
||||
method: 'POST',
|
||||
body: { phone: '13800000022', code: wrongPurposeCode, purpose: 'login' },
|
||||
expectStatus: 400,
|
||||
});
|
||||
assert.equal(wrongPurpose.code, 'PHONE_BIND_PURPOSE_REQUIRED', 'phone bind endpoint must reject login SMS codes');
|
||||
}
|
||||
|
||||
async function testSupabaseJwtIdentity() {
|
||||
const studentJwt = await createSupabaseJwt(AUTH_USER_ID, { phone: '13800000000' });
|
||||
const studentHeaders = { authorization: `Bearer ${studentJwt}` };
|
||||
@@ -904,7 +962,10 @@ async function testCatalogAndLearning() {
|
||||
},
|
||||
});
|
||||
assert.equal(nodeSession.item?.contentNodeId, ids.contentNodeProfessional, 'node session should bind parent content node');
|
||||
assert.ok(nodeSession.item?.questionIds?.includes(ids.question), 'node session should include descendant questions');
|
||||
assert.ok(
|
||||
[ids.question, ids.questionTwo, ids.questionThree].some(questionId => nodeSession.item?.questionIds?.includes(questionId)),
|
||||
'node session should include descendant questions',
|
||||
);
|
||||
|
||||
const mockSession = await request('/api/learning/practice-sessions', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
@@ -5426,6 +5487,7 @@ async function main() {
|
||||
|
||||
await check('health', () => request('/health', { userId: false }).then(payload => assert.equal(payload.ok, true)));
|
||||
await check('trusted session identity', testTrustedSessionIdentity);
|
||||
await check('phone binding', testPhoneBinding);
|
||||
await check('Supabase JWT identity', testSupabaseJwtIdentity);
|
||||
await check('legacy auth headers disabled', testLegacyAuthHeadersDisabled);
|
||||
await check('catalog and learning', testCatalogAndLearning);
|
||||
|
||||
Reference in New Issue
Block a user