Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 221d782

Browse files
committedJun 20, 2025··
feat(core): generate totp secret
1 parent 5efe822 commit 221d782

File tree

4 files changed

+56
-0
lines changed

4 files changed

+56
-0
lines changed
 

‎packages/core/src/routes/account/index.openapi.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,23 @@
306306
}
307307
}
308308
},
309+
"/api/my-account/mfa-verifications/generate-totp-secret": {
310+
"post": {
311+
"operationId": "GenerateTotpSecret",
312+
"summary": "Generate a TOTP secret",
313+
"tags": [
314+
{
315+
"name": "Dev feature"
316+
}
317+
],
318+
"description": "Generate a TOTP secret for the user.",
319+
"responses": {
320+
"200": {
321+
"description": "The TOTP secret was generated successfully."
322+
}
323+
}
324+
}
325+
},
309326
"/api/my-account/mfa-verifications/{verificationId}/name": {
310327
"patch": {
311328
"operationId": "UpdateMfaVerificationName",

‎packages/core/src/routes/account/mfa-verifications.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import { z } from 'zod';
1010

1111
import koaGuard from '#src/middleware/koa-guard.js';
1212

13+
import { EnvSet } from '../../env-set/index.js';
1314
import RequestError from '../../errors/RequestError/index.js';
1415
import { buildVerificationRecordByIdAndType } from '../../libraries/verification.js';
1516
import assertThat from '../../utils/assert-that.js';
1617
import { transpileUserMfaVerifications } from '../../utils/user.js';
18+
import { generateTotpSecret } from '../interaction/utils/totp-validation.js';
1719
import type { UserRouter, RouterInitArgs } from '../types.js';
1820

1921
import { accountApiPrefix } from './constants.js';
@@ -122,6 +124,23 @@ export default function mfaVerificationsRoutes<T extends UserRouter>(
122124
}
123125
);
124126

127+
if (EnvSet.values.isDevFeaturesEnabled) {
128+
router.post(
129+
`${accountApiPrefix}/mfa-verifications/generate-totp-secret`,
130+
koaGuard({
131+
status: [200],
132+
}),
133+
async (ctx, next) => {
134+
const secret = generateTotpSecret();
135+
ctx.body = {
136+
secret,
137+
};
138+
139+
return next();
140+
}
141+
);
142+
}
143+
125144
// Update mfa verification name, only support webauthn
126145
router.patch(
127146
`${accountApiPrefix}/mfa-verifications/:verificationId/name`,

‎packages/integration-tests/src/api/my-account.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,6 @@ export const updateOtherProfile = async (api: KyInstance, body: Record<string, u
7474

7575
export const getUserInfo = async (api: KyInstance) =>
7676
api.get('api/my-account').json<Partial<UserProfileResponse>>();
77+
78+
export const generateTotpSecret = async (api: KyInstance) =>
79+
api.post('api/my-account/mfa-verifications/generate-totp-secret').json<{ secret: string }>();

‎packages/integration-tests/src/tests/api/account/mfa.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { UserScope } from '@logto/core-kit';
22
import { MfaFactor } from '@logto/schemas';
33

44
import { enableAllAccountCenterFields } from '#src/api/account-center.js';
5+
import { generateTotpSecret } from '#src/api/my-account.js';
56
import {
67
createWebAuthnRegistrationOptions,
78
verifyWebAuthnRegistration,
@@ -17,6 +18,7 @@ import {
1718
enableUserControlledMfaWithTotpAndWebAuthn,
1819
resetMfaSettings,
1920
} from '#src/helpers/sign-in-experience.js';
21+
import { devFeatureTest } from '#src/utils.js';
2022

2123
describe('my-account (mfa)', () => {
2224
beforeAll(async () => {
@@ -29,6 +31,21 @@ describe('my-account (mfa)', () => {
2931
await resetMfaSettings();
3032
});
3133

34+
devFeatureTest.describe('POST /my-account/mfa-verifications/generate-totp-secret', () => {
35+
devFeatureTest.it('should be able to generate totp secret', async () => {
36+
const { user, username, password } = await createDefaultTenantUserWithPassword();
37+
const api = await signInAndGetUserApi(username, password, {
38+
scopes: [UserScope.Profile, UserScope.Identities],
39+
});
40+
41+
const { secret } = await generateTotpSecret(api);
42+
43+
expect(secret).toBeTruthy();
44+
45+
await deleteDefaultTenantUser(user.id);
46+
});
47+
});
48+
3249
describe('POST /my-account/mfa-verifications/web-authn/registration', () => {
3350
it('should be able to get webauthn registration options', async () => {
3451
const { user, username, password } = await createDefaultTenantUserWithPassword();

0 commit comments

Comments
 (0)
Please sign in to comment.