import { randomBytes } from 'node:crypto'; import { getCurrentMailMessageCount, getTokenFromLatestMailMessage, } from '@affine-test/kit/utils/cloud'; import type { INestApplication } from '@nestjs/common'; import type { TestFn } from 'ava'; import ava from 'ava'; import { AuthService } from '../src/core/auth/service'; import { MailService } from '../src/fundamentals/mailer'; import { changeEmail, changePassword, createTestingApp, currentUser, sendChangeEmail, sendSetPasswordEmail, sendVerifyChangeEmail, signUp, } from './utils'; const test = ava as TestFn<{ app: INestApplication; auth: AuthService; mail: MailService; }>; test.beforeEach(async t => { const { app } = await createTestingApp(); const auth = app.get(AuthService); const mail = app.get(MailService); t.context.app = app; t.context.auth = auth; t.context.mail = mail; }); test.afterEach.always(async t => { await t.context.app.close(); }); test('change email', async t => { const { mail, app } = t.context; if (mail.hasConfigured()) { const u1Email = 'u1@affine.pro'; const u2Email = 'u2@affine.pro'; const u1 = await signUp(app, 'u1', u1Email, '1'); const primitiveMailCount = await getCurrentMailMessageCount(); await sendChangeEmail(app, u1.token.token, u1Email, 'affine.pro'); const afterSendChangeMailCount = await getCurrentMailMessageCount(); t.is( primitiveMailCount + 1, afterSendChangeMailCount, 'failed to send change email' ); const changeEmailToken = await getTokenFromLatestMailMessage(); t.not( changeEmailToken, null, 'fail to get change email token from email content' ); await sendVerifyChangeEmail( app, u1.token.token, changeEmailToken as string, u2Email, 'affine.pro' ); const afterSendVerifyMailCount = await getCurrentMailMessageCount(); t.is( afterSendChangeMailCount + 1, afterSendVerifyMailCount, 'failed to send verify email' ); const verifyEmailToken = await getTokenFromLatestMailMessage(); t.not( verifyEmailToken, null, 'fail to get verify change email token from email content' ); await changeEmail(app, u1.token.token, verifyEmailToken as string, u2Email); const afterNotificationMailCount = await getCurrentMailMessageCount(); t.is( afterSendVerifyMailCount + 1, afterNotificationMailCount, 'failed to send notification email' ); } t.pass(); }); test('set and change password', async t => { const { mail, app, auth } = t.context; if (mail.hasConfigured()) { const u1Email = 'u1@affine.pro'; const u1 = await signUp(app, 'u1', u1Email, '1'); const primitiveMailCount = await getCurrentMailMessageCount(); await sendSetPasswordEmail(app, u1.token.token, u1Email, 'affine.pro'); const afterSendSetMailCount = await getCurrentMailMessageCount(); t.is( primitiveMailCount + 1, afterSendSetMailCount, 'failed to send set email' ); const setPasswordToken = await getTokenFromLatestMailMessage(); t.not( setPasswordToken, null, 'fail to get set password token from email content' ); const newPassword = randomBytes(16).toString('hex'); const userId = await changePassword( app, u1.token.token, setPasswordToken as string, newPassword ); t.is(u1.id, userId, 'failed to set password'); const ret = auth.signIn(u1Email, newPassword); t.notThrowsAsync(ret, 'failed to check password'); t.is((await ret).id, u1.id, 'failed to check password'); } t.pass(); }); test('should revoke token after change user identify', async t => { const { mail, app, auth } = t.context; if (mail.hasConfigured()) { // change email { const u1Email = 'u1@affine.pro'; const u2Email = 'u2@affine.pro'; const u1 = await signUp(app, 'u1', u1Email, '1'); { const user = await currentUser(app, u1.token.token); t.is(user?.email, u1Email, 'failed to get current user'); } await sendChangeEmail(app, u1.token.token, u1Email, 'affine.pro'); const changeEmailToken = await getTokenFromLatestMailMessage(); await sendVerifyChangeEmail( app, u1.token.token, changeEmailToken as string, u2Email, 'affine.pro' ); const verifyEmailToken = await getTokenFromLatestMailMessage(); await changeEmail( app, u1.token.token, verifyEmailToken as string, u2Email ); const user = await currentUser(app, u1.token.token); t.is(user, null, 'token should be revoked'); const newUserSession = await auth.signIn(u2Email, '1'); t.is(newUserSession?.email, u2Email, 'failed to sign in with new email'); } // change password { const u3Email = 'u3@affine.pro'; const u3 = await signUp(app, 'u1', u3Email, '1'); { const user = await currentUser(app, u3.token.token); t.is(user?.email, u3Email, 'failed to get current user'); } await sendSetPasswordEmail(app, u3.token.token, u3Email, 'affine.pro'); const token = await getTokenFromLatestMailMessage(); const newPassword = randomBytes(16).toString('hex'); await changePassword(app, u3.token.token, token as string, newPassword); const user = await currentUser(app, u3.token.token); t.is(user, null, 'token should be revoked'); const newUserSession = await auth.signIn(u3Email, newPassword); t.is( newUserSession?.email, u3Email, 'failed to sign in with new password' ); } } t.pass(); });