mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix(core): checkRequiredProfile
should work correctly when sign up identifier is emailOrSms
(#2572)
This commit is contained in:
parent
d1427e56a0
commit
aafae8e02b
2 changed files with 186 additions and 14 deletions
|
@ -7,7 +7,7 @@ import { Provider } from 'oidc-provider';
|
|||
import { mockSignInExperience, mockSignInMethod, mockUser } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
||||
import { signInWithPassword } from './utils.js';
|
||||
import { checkRequiredProfile, signInWithPassword } from './utils.js';
|
||||
|
||||
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
|
@ -108,6 +108,178 @@ afterEach(() => {
|
|||
interactionResult.mockClear();
|
||||
});
|
||||
|
||||
describe('checkRequiredProfile', () => {
|
||||
// eslint-disable-next-line @silverhand/fp/no-let
|
||||
let mockDate: jest.SpyInstance;
|
||||
const mockedExpiredAt = '2022-02-02';
|
||||
beforeEach(() => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
mockDate = jest.spyOn(Date.prototype, 'toISOString').mockReturnValue(mockedExpiredAt);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockDate.mockRestore();
|
||||
});
|
||||
|
||||
it("throw if password is required but the user's password is not set", async () => {
|
||||
const user = {
|
||||
...mockUser,
|
||||
passwordEncrypted: null,
|
||||
passwordEncryptionMethod: null,
|
||||
identities: {},
|
||||
};
|
||||
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
password: true,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
checkRequiredProfile(createContext(), createProvider(), user, signInExperience)
|
||||
).rejects.toThrowError(new RequestError({ code: 'user.require_password', status: 422 }));
|
||||
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ continueSignIn: { userId: user.id, expiresAt: mockedExpiredAt } })
|
||||
);
|
||||
});
|
||||
|
||||
it("throw if the sign up identifier is ['username'] but the user's username is missing", async () => {
|
||||
const user = {
|
||||
...mockUser,
|
||||
username: null,
|
||||
};
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: false,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
checkRequiredProfile(createContext(), createProvider(), user, signInExperience)
|
||||
).rejects.toThrowError(new RequestError({ code: 'user.require_username', status: 422 }));
|
||||
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ continueSignIn: { userId: user.id, expiresAt: mockedExpiredAt } })
|
||||
);
|
||||
});
|
||||
|
||||
it("throw if the sign up identifier is ['email'] but the user's email is missing", async () => {
|
||||
const user = {
|
||||
...mockUser,
|
||||
primaryEmail: null,
|
||||
};
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: true,
|
||||
verify: true,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
checkRequiredProfile(createContext(), createProvider(), user, signInExperience)
|
||||
).rejects.toThrowError(new RequestError({ code: 'user.require_email', status: 422 }));
|
||||
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ continueSignIn: { userId: user.id, expiresAt: mockedExpiredAt } })
|
||||
);
|
||||
});
|
||||
|
||||
it("throw if the sign up identifier is ['sms'] but the user's phone is missing", async () => {
|
||||
const user = {
|
||||
...mockUser,
|
||||
primaryPhone: null,
|
||||
};
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
password: true,
|
||||
verify: true,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
checkRequiredProfile(createContext(), createProvider(), user, signInExperience)
|
||||
).rejects.toThrowError(new RequestError({ code: 'user.require_sms', status: 422 }));
|
||||
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ continueSignIn: { userId: user.id, expiresAt: mockedExpiredAt } })
|
||||
);
|
||||
});
|
||||
|
||||
it("throw if the sign up identifier is ['email', 'sms'] but the user's email and phone are missing", async () => {
|
||||
const user = {
|
||||
...mockUser,
|
||||
primaryEmail: null,
|
||||
primaryPhone: null,
|
||||
};
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
password: true,
|
||||
verify: true,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
checkRequiredProfile(createContext(), createProvider(), user, signInExperience)
|
||||
).rejects.toThrowError(new RequestError({ code: 'user.require_email_or_sms', status: 422 }));
|
||||
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ continueSignIn: { userId: user.id, expiresAt: mockedExpiredAt } })
|
||||
);
|
||||
});
|
||||
|
||||
it.each([{ primaryEmail: null }, { primaryPhone: null }])(
|
||||
"check successfully if the sign up identifier is ['email', 'sms'] and the user has an email or phone",
|
||||
async (userProfile) => {
|
||||
const user = {
|
||||
...mockUser,
|
||||
...userProfile,
|
||||
};
|
||||
const signInExperience = {
|
||||
...mockSignInExperience,
|
||||
signUp: {
|
||||
...mockSignInExperience.signUp,
|
||||
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
|
||||
password: true,
|
||||
verify: true,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
checkRequiredProfile(createContext(), createProvider(), user, signInExperience)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(interactionResult).not.toBeCalled();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('signInWithPassword()', () => {
|
||||
it('assign result', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { LogPayload, LogType, PasscodeType, SignInExperience, User } from '@logto/schemas';
|
||||
import { SignInIdentifier, logTypeGuard } from '@logto/schemas';
|
||||
import type { Nullable, Truthy } from '@silverhand/essentials';
|
||||
import { isSameArray } from '@silverhand/essentials';
|
||||
import { addSeconds, isAfter, isValid } from 'date-fns';
|
||||
import type { Context } from 'koa';
|
||||
import type { Provider } from 'oidc-provider';
|
||||
|
@ -169,30 +170,29 @@ export const checkRequiredProfile = async (
|
|||
throw new RequestError({ code: 'user.require_password', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Username) && !username) {
|
||||
if (isSameArray(signUp.identifiers, [SignInIdentifier.Username]) && !username) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_username', status: 422 });
|
||||
}
|
||||
|
||||
if (isSameArray(signUp.identifiers, [SignInIdentifier.Email]) && !primaryEmail) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_email', status: 422 });
|
||||
}
|
||||
|
||||
if (isSameArray(signUp.identifiers, [SignInIdentifier.Sms]) && !primaryPhone) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_sms', status: 422 });
|
||||
}
|
||||
|
||||
if (
|
||||
signUp.identifiers.includes(SignInIdentifier.Email) &&
|
||||
signUp.identifiers.includes(SignInIdentifier.Sms) &&
|
||||
isSameArray(signUp.identifiers, [SignInIdentifier.Email, SignInIdentifier.Sms]) &&
|
||||
!primaryEmail &&
|
||||
!primaryPhone
|
||||
) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_email_or_sms', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Email) && !primaryEmail) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_email', status: 422 });
|
||||
}
|
||||
|
||||
if (signUp.identifiers.includes(SignInIdentifier.Sms) && !primaryPhone) {
|
||||
await assignContinueSignInResult(ctx, provider, { userId: id });
|
||||
throw new RequestError({ code: 'user.require_sms', status: 422 });
|
||||
}
|
||||
};
|
||||
/* eslint-enable complexity */
|
||||
|
||||
|
|
Loading…
Reference in a new issue