mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
refactor(core,schemas): add user logto_data column to store mfa (#4792)
* feat(core,phrases): disable auto skip mfa * refactor(experience): skip mfa manually (#4788) * refactor(core,schemas): add user logto_data column to store mfa skipped info --------- Co-authored-by: Xiao Yijun <xiaoyijun@silverhand.io>
This commit is contained in:
parent
9ea79a18d6
commit
b118fc54a6
8 changed files with 54 additions and 10 deletions
|
@ -15,6 +15,7 @@ export const mockUser: User = {
|
|||
identities: {
|
||||
connector1: { userId: 'connector1', details: {} },
|
||||
},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
customData: {},
|
||||
applicationId: 'bar',
|
||||
|
@ -69,6 +70,7 @@ export const mockUserWithPassword: User = {
|
|||
connector1: { userId: 'connector1', details: {} },
|
||||
},
|
||||
customData: {},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_789,
|
||||
|
@ -89,6 +91,7 @@ export const mockUserList: User[] = [
|
|||
avatar: null,
|
||||
identities: {},
|
||||
customData: {},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
|
@ -107,6 +110,7 @@ export const mockUserList: User[] = [
|
|||
avatar: null,
|
||||
identities: {},
|
||||
customData: {},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
|
@ -125,6 +129,7 @@ export const mockUserList: User[] = [
|
|||
avatar: null,
|
||||
identities: {},
|
||||
customData: {},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
|
@ -143,6 +148,7 @@ export const mockUserList: User[] = [
|
|||
avatar: null,
|
||||
identities: {},
|
||||
customData: {},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
|
@ -161,6 +167,7 @@ export const mockUserList: User[] = [
|
|||
avatar: null,
|
||||
identities: {},
|
||||
customData: {},
|
||||
logtoConfig: {},
|
||||
mfaVerifications: [],
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
|
|
|
@ -39,6 +39,7 @@ describe('user query', () => {
|
|||
...mockUser,
|
||||
identities: JSON.stringify(mockUser.identities),
|
||||
customData: JSON.stringify(mockUser.customData),
|
||||
logtoConfig: JSON.stringify(mockUser.logtoConfig),
|
||||
mfaVerifications: JSON.stringify(mockUser.mfaVerifications),
|
||||
};
|
||||
|
||||
|
@ -272,6 +273,7 @@ describe('user query', () => {
|
|||
...mockUser,
|
||||
identities: JSON.stringify(restIdentities),
|
||||
customData: JSON.stringify(mockUser.customData),
|
||||
logtoConfig: JSON.stringify(mockUser.logtoConfig),
|
||||
mfaVerifications: JSON.stringify(mockUser.mfaVerifications),
|
||||
};
|
||||
|
||||
|
|
|
@ -183,7 +183,7 @@ describe('submit action', () => {
|
|||
{
|
||||
id: 'uid',
|
||||
...upsertProfile,
|
||||
customData: {
|
||||
logtoConfig: {
|
||||
[userMfaDataKey]: {
|
||||
skipped: true,
|
||||
},
|
||||
|
@ -348,7 +348,7 @@ describe('submit action', () => {
|
|||
google: { userId: 'googleId', details: {} },
|
||||
},
|
||||
lastSignInAt: now,
|
||||
customData: {
|
||||
logtoConfig: {
|
||||
[userMfaDataKey]: {
|
||||
skipped: true,
|
||||
},
|
||||
|
|
|
@ -118,7 +118,7 @@ async function handleSubmitRegister(
|
|||
),
|
||||
...conditional(
|
||||
mfaSkipped && {
|
||||
customData: {
|
||||
logtoConfig: {
|
||||
[userMfaDataKey]: {
|
||||
skipped: true,
|
||||
},
|
||||
|
@ -174,8 +174,8 @@ async function handleSubmitSignIn(
|
|||
),
|
||||
...conditional(
|
||||
mfaSkipped && {
|
||||
customData: {
|
||||
...user.customData,
|
||||
logtoConfig: {
|
||||
...user.logtoConfig,
|
||||
[userMfaDataKey]: {
|
||||
skipped: true,
|
||||
},
|
||||
|
|
|
@ -200,7 +200,7 @@ describe('validateMandatoryBindMfa', () => {
|
|||
it('user mfaVerifications and bindMfa missing, mark skipped, and not required should pass', async () => {
|
||||
findUserById.mockResolvedValueOnce({
|
||||
...mockUser,
|
||||
customData: {
|
||||
logtoConfig: {
|
||||
mfa: { skipped: true },
|
||||
},
|
||||
});
|
||||
|
|
|
@ -126,15 +126,29 @@ export const userMfaDataKey = 'mfa';
|
|||
/**
|
||||
* Check if the user has skipped MFA binding
|
||||
*/
|
||||
const isMfaSkipped = (customData: JsonObject): boolean => {
|
||||
const isMfaSkipped = (logtoConfig: JsonObject): boolean => {
|
||||
const userMfaDataGuard = z.object({
|
||||
skipped: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const parsed = z.object({ [userMfaDataKey]: userMfaDataGuard }).safeParse(customData);
|
||||
const parsed = z.object({ [userMfaDataKey]: userMfaDataGuard }).safeParse(logtoConfig);
|
||||
|
||||
return parsed.success ? parsed.data[userMfaDataKey].skipped === true : false;
|
||||
};
|
||||
/**
|
||||
* Mark MFA as skipped in user custom data
|
||||
*/
|
||||
export const markMfaSkipped = async (tenant: TenantContext, accountId: string) => {
|
||||
const { customData } = await tenant.queries.users.findUserById(accountId);
|
||||
await tenant.queries.users.updateUserById(accountId, {
|
||||
customData: {
|
||||
...customData,
|
||||
[userMfaDataKey]: {
|
||||
skipped: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const validateMandatoryBindMfa = async (
|
||||
tenant: TenantContext,
|
||||
|
@ -168,9 +182,9 @@ export const validateMandatoryBindMfa = async (
|
|||
|
||||
if (event === InteractionEvent.SignIn) {
|
||||
const { accountId } = interaction;
|
||||
const { mfaVerifications, customData } = await tenant.queries.users.findUserById(accountId);
|
||||
const { mfaVerifications, logtoConfig } = await tenant.queries.users.findUserById(accountId);
|
||||
|
||||
if (isMfaSkipped(customData)) {
|
||||
if (isMfaSkipped(logtoConfig)) {
|
||||
return interaction;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table users
|
||||
add column if not exists logto_config jsonb not null default '{}'::jsonb;
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table users
|
||||
drop column logto_config;
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -16,6 +16,7 @@ create table users (
|
|||
application_id varchar(21),
|
||||
identities jsonb /* @use Identities */ not null default '{}'::jsonb,
|
||||
custom_data jsonb /* @use JsonObject */ not null default '{}'::jsonb,
|
||||
logto_config jsonb /* @use JsonObject */ not null default '{}'::jsonb,
|
||||
mfa_verifications jsonb /* @use MfaVerifications */ not null default '[]'::jsonb,
|
||||
is_suspended boolean not null default false,
|
||||
last_sign_in_at timestamptz,
|
||||
|
|
Loading…
Add table
Reference in a new issue