mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(core): password sign in with phone or email (#2266)
This commit is contained in:
parent
3e6021ad16
commit
415c24aace
7 changed files with 356 additions and 98 deletions
|
@ -1,10 +1,9 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { UserRole, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { UserRole, SignUpIdentifier } from '@logto/schemas';
|
||||
import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockSignInExperience, mockSignInMethod, mockUser } from '@/__mocks__';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { mockSignInExperience, mockUser } from '@/__mocks__';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import passwordRoutes, { registerRoute, signInRoute } from './password';
|
||||
|
@ -13,13 +12,7 @@ const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
|||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const hasActiveUsers = jest.fn(async () => true);
|
||||
const findDefaultSignInExperience = jest.fn(async () => ({
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Username,
|
||||
},
|
||||
}));
|
||||
const findDefaultSignInExperience = jest.fn(async () => mockSignInExperience);
|
||||
|
||||
jest.mock('@/queries/user', () => ({
|
||||
findUserById: async () => findUserById(),
|
||||
|
@ -36,7 +29,7 @@ jest.mock('@/queries/user', () => ({
|
|||
async findUserByUsername(username: string) {
|
||||
const roleNames = username === 'admin' ? [UserRole.Admin] : [];
|
||||
|
||||
return { id: 'user1', username, roleNames };
|
||||
return { id: 'id', username, roleNames };
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -45,17 +38,7 @@ jest.mock('@/queries/sign-in-experience', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('@/lib/user', () => ({
|
||||
async verifyUserPassword(user: User, password: string) {
|
||||
const { username } = user;
|
||||
|
||||
if (username !== 'username' && username !== 'admin') {
|
||||
throw new RequestError('session.invalid_credentials');
|
||||
}
|
||||
|
||||
if (password !== 'password') {
|
||||
throw new RequestError('session.invalid_credentials');
|
||||
}
|
||||
|
||||
async verifyUserPassword(user: User) {
|
||||
return user;
|
||||
},
|
||||
generateUserId: () => 'user1',
|
||||
|
@ -116,60 +99,52 @@ describe('session -> password routes', () => {
|
|||
],
|
||||
});
|
||||
|
||||
describe('POST /session/sign-in/password/username', () => {
|
||||
it('assign result and redirect', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
const response = await sessionRequest.post(`${signInRoute}/username`).send({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveProperty('redirectTo');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ login: { accountId: 'user1' } }),
|
||||
expect.anything()
|
||||
);
|
||||
it('POST /session/sign-in/password/username', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
const response = await sessionRequest.post(`${signInRoute}/username`).send({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveProperty('redirectTo');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ login: { accountId: 'id' } }),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if user not found', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
const response = await sessionRequest.post(`${signInRoute}/username`).send({
|
||||
username: 'notexistuser',
|
||||
password: 'password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(400);
|
||||
it('POST /session/sign-in/password/email', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
const response = await sessionRequest.post(`${signInRoute}/email`).send({
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveProperty('redirectTo');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ login: { accountId: 'id' } }),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if user found but wrong password', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
const response = await sessionRequest.post(`${signInRoute}/username`).send({
|
||||
username: 'username',
|
||||
password: '_password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
it('throw if sign in method is not enabled', async () => {
|
||||
findDefaultSignInExperience.mockResolvedValueOnce({
|
||||
...mockSignInExperience,
|
||||
signIn: {
|
||||
methods: [
|
||||
{
|
||||
...mockSignInMethod,
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const response = await sessionRequest.post(`${signInRoute}/username`).send({
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(422);
|
||||
it('POST /session/sign-in/password/sms', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
const response = await sessionRequest.post(`${signInRoute}/sms`).send({
|
||||
phone: 'phone',
|
||||
password: 'password',
|
||||
});
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveProperty('redirectTo');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ login: { accountId: 'id' } }),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
describe('POST /session/register/password/username', () => {
|
||||
|
|
|
@ -6,14 +6,20 @@ import { object, string } from 'zod';
|
|||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { assignInteractionResults } from '@/lib/session';
|
||||
import { verifyUserPassword, encryptUserPassword, generateUserId, insertUser } from '@/lib/user';
|
||||
import { encryptUserPassword, generateUserId, insertUser } from '@/lib/user';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import { findDefaultSignInExperience } from '@/queries/sign-in-experience';
|
||||
import { findUserByUsername, hasActiveUsers, hasUser, updateUserById } from '@/queries/user';
|
||||
import {
|
||||
findUserByEmail,
|
||||
findUserByPhone,
|
||||
findUserByUsername,
|
||||
hasActiveUsers,
|
||||
hasUser,
|
||||
} from '@/queries/user';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
import type { AnonymousRouter } from '../types';
|
||||
import { getRoutePrefix } from './utils';
|
||||
import { getRoutePrefix, signInWithPassword } from './utils';
|
||||
|
||||
export const registerRoute = getRoutePrefix('register', 'password');
|
||||
export const signInRoute = getRoutePrefix('sign-in', 'password');
|
||||
|
@ -28,28 +34,61 @@ export default function passwordRoutes<T extends AnonymousRouter>(router: T, pro
|
|||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
assertThat(
|
||||
signInExperience.signIn.methods.some(
|
||||
({ identifier, password }) => identifier === SignInIdentifier.Username && password
|
||||
),
|
||||
new RequestError({
|
||||
code: 'user.sign_in_method_not_enabled',
|
||||
status: 422,
|
||||
})
|
||||
);
|
||||
|
||||
await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { username, password } = ctx.guard.body;
|
||||
const type = 'SignInUsernamePassword';
|
||||
ctx.log(type, { username });
|
||||
await signInWithPassword(ctx, provider, {
|
||||
identifier: SignInIdentifier.Username,
|
||||
password,
|
||||
logType: type,
|
||||
logPayload: { username },
|
||||
findUser: async () => findUserByUsername(username),
|
||||
});
|
||||
|
||||
const user = await findUserByUsername(username);
|
||||
const { id } = await verifyUserPassword(user, password);
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
ctx.log(type, { userId: id });
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
router.post(
|
||||
`${signInRoute}/email`,
|
||||
koaGuard({
|
||||
body: object({
|
||||
email: string().min(1),
|
||||
password: string().min(1),
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { email, password } = ctx.guard.body;
|
||||
const type = 'SignInEmailPassword';
|
||||
await signInWithPassword(ctx, provider, {
|
||||
identifier: SignInIdentifier.Email,
|
||||
password,
|
||||
logType: type,
|
||||
logPayload: { email },
|
||||
findUser: async () => findUserByEmail(email),
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
`${signInRoute}/sms`,
|
||||
koaGuard({
|
||||
body: object({
|
||||
phone: string().min(1),
|
||||
password: string().min(1),
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { phone, password } = ctx.guard.body;
|
||||
const type = 'SignInSmsPassword';
|
||||
await signInWithPassword(ctx, provider, {
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password,
|
||||
logType: type,
|
||||
logPayload: { phone },
|
||||
findUser: async () => findUserByPhone(phone),
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
|
|
184
packages/core/src/routes/session/utils.test.ts
Normal file
184
packages/core/src/routes/session/utils.test.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { UserRole, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
||||
import { createMockContext } from '@shopify/jest-koa-mocks';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockSignInExperience, mockSignInMethod, mockUser } from '@/__mocks__';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
||||
import { signInWithPassword } from './utils';
|
||||
|
||||
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const hasActiveUsers = jest.fn(async () => true);
|
||||
const findDefaultSignInExperience = jest.fn(async () => ({
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifier: SignUpIdentifier.Username,
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@/queries/user', () => ({
|
||||
findUserById: async () => findUserById(),
|
||||
findUserByIdentity: async () => ({ id: 'id', identities: {} }),
|
||||
findUserByPhone: async () => ({ id: 'id' }),
|
||||
findUserByEmail: async () => ({ id: 'id' }),
|
||||
updateUserById: async (...args: unknown[]) => updateUserById(...args),
|
||||
hasUser: async (username: string) => username === 'username1',
|
||||
hasUserWithIdentity: async (connectorId: string, userId: string) =>
|
||||
connectorId === 'connectorId' && userId === 'id',
|
||||
hasUserWithPhone: async (phone: string) => phone === '13000000000',
|
||||
hasUserWithEmail: async (email: string) => email === 'a@a.com',
|
||||
hasActiveUsers: async () => hasActiveUsers(),
|
||||
async findUserByUsername(username: string) {
|
||||
const roleNames = username === 'admin' ? [UserRole.Admin] : [];
|
||||
|
||||
return { id: 'user1', username, roleNames };
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@/queries/sign-in-experience', () => ({
|
||||
findDefaultSignInExperience: async () => findDefaultSignInExperience(),
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/user', () => ({
|
||||
async verifyUserPassword(user: Nullable<User>, password: string) {
|
||||
if (!user) {
|
||||
throw new RequestError('session.invalid_credentials');
|
||||
}
|
||||
|
||||
if (password !== 'password') {
|
||||
throw new RequestError('session.invalid_credentials');
|
||||
}
|
||||
|
||||
return user;
|
||||
},
|
||||
generateUserId: () => 'user1',
|
||||
encryptUserPassword: (password: string) => ({
|
||||
passwordEncrypted: password + '_user1',
|
||||
passwordEncryptionMethod: 'Argon2i',
|
||||
}),
|
||||
updateLastSignInAt: async (...args: unknown[]) => updateUserById(...args),
|
||||
insertUser: async (...args: unknown[]) => insertUser(...args),
|
||||
}));
|
||||
|
||||
const grantSave = jest.fn(async () => 'finalGrantId');
|
||||
const grantAddOIDCScope = jest.fn();
|
||||
const grantAddResourceScope = jest.fn();
|
||||
const interactionResult = jest.fn(async () => 'redirectTo');
|
||||
const interactionDetails: jest.MockedFunction<() => Promise<unknown>> = jest.fn(async () => ({}));
|
||||
|
||||
class Grant {
|
||||
static async find(id: string) {
|
||||
return id === 'exists' ? new Grant() : undefined;
|
||||
}
|
||||
|
||||
save: typeof grantSave;
|
||||
addOIDCScope: typeof grantAddOIDCScope;
|
||||
addResourceScope: typeof grantAddResourceScope;
|
||||
|
||||
constructor() {
|
||||
this.save = grantSave;
|
||||
this.addOIDCScope = grantAddOIDCScope;
|
||||
this.addResourceScope = grantAddResourceScope;
|
||||
}
|
||||
}
|
||||
|
||||
const createContext = () => ({
|
||||
...createMockContext(),
|
||||
addLogContext: jest.fn(),
|
||||
log: jest.fn(),
|
||||
});
|
||||
|
||||
const createProvider = () => new Provider('');
|
||||
|
||||
jest.mock('oidc-provider', () => ({
|
||||
Provider: jest.fn(() => ({
|
||||
Grant,
|
||||
interactionDetails,
|
||||
interactionResult,
|
||||
})),
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
grantSave.mockClear();
|
||||
interactionResult.mockClear();
|
||||
});
|
||||
|
||||
describe('signInWithPassword()', () => {
|
||||
it('assign result', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
await signInWithPassword(createContext(), createProvider(), {
|
||||
identifier: SignInIdentifier.Username,
|
||||
password: 'password',
|
||||
findUser: jest.fn(async () => mockUser),
|
||||
logType: 'SignInUsernamePassword',
|
||||
logPayload: { username: 'username' },
|
||||
});
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ login: { accountId: mockUser.id } }),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if user not found', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
await expect(
|
||||
signInWithPassword(createContext(), createProvider(), {
|
||||
identifier: SignInIdentifier.Username,
|
||||
password: 'password',
|
||||
findUser: jest.fn(async () => null),
|
||||
logType: 'SignInUsernamePassword',
|
||||
logPayload: { username: 'username' },
|
||||
})
|
||||
).rejects.toThrowError(new RequestError('session.invalid_credentials'));
|
||||
});
|
||||
|
||||
it('throw if user found but wrong password', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
await expect(
|
||||
signInWithPassword(createContext(), createProvider(), {
|
||||
identifier: SignInIdentifier.Username,
|
||||
password: '_password',
|
||||
findUser: jest.fn(async () => mockUser),
|
||||
logType: 'SignInUsernamePassword',
|
||||
logPayload: { username: 'username' },
|
||||
})
|
||||
).rejects.toThrowError(new RequestError('session.invalid_credentials'));
|
||||
});
|
||||
|
||||
it('throw if sign in method is not enabled', async () => {
|
||||
findDefaultSignInExperience.mockResolvedValueOnce({
|
||||
...mockSignInExperience,
|
||||
signIn: {
|
||||
methods: [
|
||||
{
|
||||
...mockSignInMethod,
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
await expect(
|
||||
signInWithPassword(createContext(), createProvider(), {
|
||||
identifier: SignInIdentifier.Username,
|
||||
password: 'password',
|
||||
findUser: jest.fn(async () => mockUser),
|
||||
logType: 'SignInUsernamePassword',
|
||||
logPayload: { username: 'username' },
|
||||
})
|
||||
).rejects.toThrowError(
|
||||
new RequestError({
|
||||
code: 'user.sign_in_method_not_enabled',
|
||||
status: 422,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import type { LogType, PasscodeType } from '@logto/schemas';
|
||||
import type { LogPayload, LogType, PasscodeType, SignInIdentifier, User } from '@logto/schemas';
|
||||
import { logTypeGuard } from '@logto/schemas';
|
||||
import type { Truthy } from '@silverhand/essentials';
|
||||
import type { Nullable, Truthy } from '@silverhand/essentials';
|
||||
import dayjs from 'dayjs';
|
||||
import type { Context } from 'koa';
|
||||
import type { Provider } from 'oidc-provider';
|
||||
|
@ -8,6 +8,11 @@ import type { ZodType } from 'zod';
|
|||
import { z } from 'zod';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { assignInteractionResults } from '@/lib/session';
|
||||
import { verifyUserPassword } from '@/lib/user';
|
||||
import type { LogContext } from '@/middleware/koa-log';
|
||||
import { findDefaultSignInExperience } from '@/queries/sign-in-experience';
|
||||
import { updateUserById } from '@/queries/user';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
import { verificationTimeout } from './consts';
|
||||
|
@ -94,3 +99,38 @@ export const clearVerificationResult = async (ctx: Context, provider: Provider)
|
|||
await provider.interactionResult(ctx.req, ctx.res, rest);
|
||||
}
|
||||
};
|
||||
|
||||
type SignInWithPasswordParameter = {
|
||||
identifier: SignInIdentifier;
|
||||
password: string;
|
||||
logType: LogType;
|
||||
logPayload: LogPayload;
|
||||
findUser: () => Promise<Nullable<User>>;
|
||||
};
|
||||
|
||||
export const signInWithPassword = async (
|
||||
ctx: Context & LogContext,
|
||||
provider: Provider,
|
||||
{ identifier, findUser, password, logType, logPayload }: SignInWithPasswordParameter
|
||||
) => {
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
assertThat(
|
||||
signInExperience.signIn.methods.some(
|
||||
(method) => method.password && method.identifier === identifier
|
||||
),
|
||||
new RequestError({
|
||||
code: 'user.sign_in_method_not_enabled',
|
||||
status: 422,
|
||||
})
|
||||
);
|
||||
|
||||
await provider.interactionDetails(ctx.req, ctx.res);
|
||||
ctx.log(logType, logPayload);
|
||||
|
||||
const user = await findUser();
|
||||
const { id } = await verifyUserPassword(user, password);
|
||||
|
||||
ctx.log(logType, { userId: id });
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
};
|
||||
|
|
|
@ -72,8 +72,12 @@ export const setUpConnector = async (connectorId: string, config: Record<string,
|
|||
assert(connector.enabled, new Error('Connector Setup Failed'));
|
||||
};
|
||||
|
||||
export const setSignUpIdentifier = async (identifier: SignUpIdentifier) => {
|
||||
await updateSignInExperience({ signUp: { identifier, password: true, verify: true } });
|
||||
export const setSignUpIdentifier = async (
|
||||
identifier: SignUpIdentifier,
|
||||
password = true,
|
||||
verify = true
|
||||
) => {
|
||||
await updateSignInExperience({ signUp: { identifier, password, verify } });
|
||||
};
|
||||
|
||||
type PasscodeRecord = {
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('username and password flow', () => {
|
|||
describe('email passwordless flow', () => {
|
||||
beforeAll(async () => {
|
||||
await setUpConnector(mockEmailConnectorId, mockEmailConnectorConfig);
|
||||
await setSignUpIdentifier(SignUpIdentifier.Email);
|
||||
await setSignUpIdentifier(SignUpIdentifier.Email, false);
|
||||
});
|
||||
|
||||
// Since we can not create a email register user throw admin. Have to run the register then sign-in concurrently.
|
||||
|
@ -121,7 +121,7 @@ describe('email passwordless flow', () => {
|
|||
describe('sms passwordless flow', () => {
|
||||
beforeAll(async () => {
|
||||
await setUpConnector(mockSmsConnectorId, mockSmsConnectorConfig);
|
||||
await setSignUpIdentifier(SignUpIdentifier.Sms);
|
||||
await setSignUpIdentifier(SignUpIdentifier.Sms, false);
|
||||
});
|
||||
|
||||
// Since we can not create a sms register user throw admin. Have to run the register then sign-in concurrently.
|
||||
|
|
|
@ -79,6 +79,20 @@ const signInUsernamePasswordLogPayloadGuard = arbitraryLogPayloadGuard.and(
|
|||
})
|
||||
);
|
||||
|
||||
const signInEmailPasswordLogPayloadGuard = arbitraryLogPayloadGuard.and(
|
||||
z.object({
|
||||
userId: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
const signInSmsPasswordLogPayloadGuard = arbitraryLogPayloadGuard.and(
|
||||
z.object({
|
||||
userId: z.string().optional(),
|
||||
sms: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
const signInEmailSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and(
|
||||
z.object({
|
||||
email: z.string().optional(),
|
||||
|
@ -197,6 +211,8 @@ const logPayloadsGuard = z.object({
|
|||
RegisterSocialBind: registerSocialBindLogPayloadGuard,
|
||||
RegisterSocial: registerSocialLogPayloadGuard,
|
||||
SignInUsernamePassword: signInUsernamePasswordLogPayloadGuard,
|
||||
SignInEmailPassword: signInEmailPasswordLogPayloadGuard,
|
||||
SignInSmsPassword: signInSmsPasswordLogPayloadGuard,
|
||||
SignInEmailSendPasscode: signInEmailSendPasscodeLogPayloadGuard,
|
||||
SignInEmail: signInEmailLogPayloadGuard,
|
||||
SignInSmsSendPasscode: signInSmsSendPasscodeLogPayloadGuard,
|
||||
|
|
Loading…
Reference in a new issue