mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(core): move verification record id to header (#6801)
This commit is contained in:
parent
640425414f
commit
859495b13b
14 changed files with 152 additions and 163 deletions
|
@ -1,4 +1,3 @@
|
|||
import RequestError from '../errors/RequestError/index.js';
|
||||
import { expirationTime } from '../queries/verification-records.js';
|
||||
import {
|
||||
buildVerificationRecord,
|
||||
|
@ -43,34 +42,6 @@ const getVerificationRecordById = async ({
|
|||
return buildVerificationRecord(libraries, queries, result.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the user sensitive permission by checking if the verification record is valid
|
||||
* and associated with the user.
|
||||
*/
|
||||
export const verifyUserSensitivePermission = async ({
|
||||
userId,
|
||||
id,
|
||||
queries,
|
||||
libraries,
|
||||
}: {
|
||||
userId: string;
|
||||
id: string;
|
||||
queries: Queries;
|
||||
libraries: Libraries;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const record = await getVerificationRecordById({ id, queries, libraries, userId });
|
||||
|
||||
assertThat(record.isVerified, 'verification_record.not_found');
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError) {
|
||||
throw new RequestError({ code: 'verification_record.permission_denied', status: 401 });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a user verification record by its id and type.
|
||||
* This is used to build a verification record for new identifier verifications,
|
||||
|
|
1
packages/core/src/middleware/koa-auth/constants.ts
Normal file
1
packages/core/src/middleware/koa-auth/constants.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const verificationRecordIdHeader = 'logto-verification-id';
|
|
@ -16,6 +16,7 @@ import { type WithAuthContext, type TokenInfo } from './types.js';
|
|||
import { extractBearerTokenFromHeaders, getAdminTenantTokenValidationSet } from './utils.js';
|
||||
|
||||
export * from './types.js';
|
||||
export * from './constants.js';
|
||||
|
||||
export const verifyBearerTokenFromRequest = async (
|
||||
envSet: EnvSet,
|
||||
|
|
|
@ -7,11 +7,15 @@ import Sinon from 'sinon';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
||||
import { MockTenant } from '../../test-utils/tenant.js';
|
||||
|
||||
import type { WithAuthContext } from './index.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const provider = new Provider('https://logto.test');
|
||||
const tenantContext = new MockTenant(provider);
|
||||
|
||||
const mockAccessToken = {
|
||||
accountId: 'fooUser',
|
||||
clientId: 'fooClient',
|
||||
|
@ -70,12 +74,19 @@ describe('koaOidcAuth middleware', () => {
|
|||
},
|
||||
};
|
||||
Sinon.stub(provider.AccessToken, 'find').resolves(mockAccessToken);
|
||||
await koaOidcAuth(provider)(ctx, next);
|
||||
expect(ctx.auth).toEqual({ type: 'user', id: 'fooUser', scopes: new Set(['openid']) });
|
||||
await koaOidcAuth(tenantContext)(ctx, next);
|
||||
expect(ctx.auth).toEqual({
|
||||
type: 'user',
|
||||
id: 'fooUser',
|
||||
scopes: new Set(['openid']),
|
||||
identityVerified: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('expect to throw if authorization header is missing', async () => {
|
||||
await expect(koaOidcAuth(provider)(ctx, next)).rejects.toMatchError(authHeaderMissingError);
|
||||
await expect(koaOidcAuth(tenantContext)(ctx, next)).rejects.toMatchError(
|
||||
authHeaderMissingError
|
||||
);
|
||||
});
|
||||
|
||||
it('expect to throw if authorization header token type not recognized ', async () => {
|
||||
|
@ -86,7 +97,9 @@ describe('koaOidcAuth middleware', () => {
|
|||
},
|
||||
};
|
||||
|
||||
await expect(koaOidcAuth(provider)(ctx, next)).rejects.toMatchError(tokenNotSupportedError);
|
||||
await expect(koaOidcAuth(tenantContext)(ctx, next)).rejects.toMatchError(
|
||||
tokenNotSupportedError
|
||||
);
|
||||
});
|
||||
|
||||
it('expect to throw if access token is not found', async () => {
|
||||
|
@ -98,7 +111,7 @@ describe('koaOidcAuth middleware', () => {
|
|||
};
|
||||
Sinon.stub(provider.AccessToken, 'find').resolves();
|
||||
|
||||
await expect(koaOidcAuth(provider)(ctx, next)).rejects.toMatchError(unauthorizedError);
|
||||
await expect(koaOidcAuth(tenantContext)(ctx, next)).rejects.toMatchError(unauthorizedError);
|
||||
});
|
||||
|
||||
it('expect to throw if sub is missing', async () => {
|
||||
|
@ -113,7 +126,7 @@ describe('koaOidcAuth middleware', () => {
|
|||
accountId: undefined,
|
||||
});
|
||||
|
||||
await expect(koaOidcAuth(provider)(ctx, next)).rejects.toMatchError(unauthorizedError);
|
||||
await expect(koaOidcAuth(tenantContext)(ctx, next)).rejects.toMatchError(unauthorizedError);
|
||||
});
|
||||
|
||||
it('expect to throw if access token does not have openid scope', async () => {
|
||||
|
@ -128,6 +141,6 @@ describe('koaOidcAuth middleware', () => {
|
|||
scopes: new Set(['foo']),
|
||||
});
|
||||
|
||||
await expect(koaOidcAuth(provider)(ctx, next)).rejects.toMatchError(forbiddenError);
|
||||
await expect(koaOidcAuth(tenantContext)(ctx, next)).rejects.toMatchError(forbiddenError);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,18 +1,58 @@
|
|||
import type { MiddlewareType } from 'koa';
|
||||
import type { IRouterParamContext } from 'koa-router';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import {
|
||||
verificationRecordDataGuard,
|
||||
buildVerificationRecord,
|
||||
} from '#src/routes/experience/classes/verifications/index.js';
|
||||
import type Libraries from '#src/tenants/Libraries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import { verificationRecordIdHeader } from './constants.js';
|
||||
import { type WithAuthContext } from './types.js';
|
||||
import { extractBearerTokenFromHeaders } from './utils.js';
|
||||
|
||||
/**
|
||||
* Builds a verification record by its id.
|
||||
* The `userId` is optional and is only used for user sensitive permission verifications.
|
||||
*/
|
||||
const getVerificationRecordResultById = async ({
|
||||
id,
|
||||
queries,
|
||||
libraries,
|
||||
userId,
|
||||
}: {
|
||||
id: string;
|
||||
queries: Queries;
|
||||
libraries: Libraries;
|
||||
userId: string;
|
||||
}): Promise<boolean> => {
|
||||
const record = await queries.verificationRecords.findActiveVerificationRecordById(id);
|
||||
if (record?.userId !== userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = verificationRecordDataGuard.safeParse({
|
||||
...record.data,
|
||||
id: record.id,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const instance = buildVerificationRecord(libraries, queries, result.data);
|
||||
return instance.isVerified;
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth middleware for OIDC opaque token
|
||||
*/
|
||||
export default function koaOidcAuth<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
|
||||
provider: Provider
|
||||
tenant: TenantContext
|
||||
): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
||||
const authMiddleware: MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> = async (
|
||||
ctx,
|
||||
|
@ -20,7 +60,7 @@ export default function koaOidcAuth<StateT, ContextT extends IRouterParamContext
|
|||
) => {
|
||||
const { request } = ctx;
|
||||
const accessTokenValue = extractBearerTokenFromHeaders(request.headers);
|
||||
const accessToken = await provider.AccessToken.find(accessTokenValue);
|
||||
const accessToken = await tenant.provider.AccessToken.find(accessTokenValue);
|
||||
|
||||
assertThat(accessToken, new RequestError({ code: 'auth.unauthorized', status: 401 }));
|
||||
|
||||
|
@ -28,10 +68,22 @@ export default function koaOidcAuth<StateT, ContextT extends IRouterParamContext
|
|||
assertThat(accountId, new RequestError({ code: 'auth.unauthorized', status: 401 }));
|
||||
assertThat(scopes.has('openid'), new RequestError({ code: 'auth.forbidden', status: 403 }));
|
||||
|
||||
const verificationRecordId = request.headers[verificationRecordIdHeader];
|
||||
const identityVerified =
|
||||
typeof verificationRecordId === 'string'
|
||||
? await getVerificationRecordResultById({
|
||||
id: verificationRecordId,
|
||||
queries: tenant.queries,
|
||||
libraries: tenant.libraries,
|
||||
userId: accountId,
|
||||
})
|
||||
: false;
|
||||
|
||||
ctx.auth = {
|
||||
type: 'user',
|
||||
id: accountId,
|
||||
scopes,
|
||||
identityVerified,
|
||||
};
|
||||
|
||||
return next();
|
||||
|
|
|
@ -4,6 +4,8 @@ type Auth = {
|
|||
type: 'user' | 'app';
|
||||
id: string;
|
||||
scopes: Set<string>;
|
||||
/** If the request is verified by a verification record, this will be set to `true`. */
|
||||
identityVerified?: boolean;
|
||||
};
|
||||
|
||||
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
||||
|
|
|
@ -104,7 +104,7 @@ const createRouters = (tenant: TenantContext) => {
|
|||
const anonymousRouter: AnonymousRouter = new Router();
|
||||
|
||||
const userRouter: UserRouter = new Router();
|
||||
userRouter.use(koaOidcAuth(tenant.provider));
|
||||
userRouter.use(koaOidcAuth(tenant));
|
||||
// TODO(LOG-10147): Rename to koaApiHooks, this middleware is used for both management API and user API
|
||||
userRouter.use(koaManagementApiHooks(tenant.libraries.hooks));
|
||||
profileRoutes(userRouter, tenant);
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
"post": {
|
||||
"operationId": "UpdatePassword",
|
||||
"summary": "Update password",
|
||||
"description": "Update password for the user, a verification record is required for checking sensitive permissions.",
|
||||
"description": "Update password for the user, a logto-verification-id in header is required for checking sensitive permissions.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
@ -126,9 +126,6 @@
|
|||
"properties": {
|
||||
"password": {
|
||||
"description": "The new password for the user."
|
||||
},
|
||||
"verificationRecordId": {
|
||||
"description": "The verification record ID for checking sensitive permissions."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +146,7 @@
|
|||
"post": {
|
||||
"operationId": "UpdatePrimaryEmail",
|
||||
"summary": "Update primary email",
|
||||
"description": "Update primary email for the user, a verification record is required for checking sensitive permissions, and a new identifier verification record is required for the new email ownership verification.",
|
||||
"description": "Update primary email for the user, a logto-verification-id in header is required for checking sensitive permissions, and a new identifier verification record is required for the new email ownership verification.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
@ -158,9 +155,6 @@
|
|||
"email": {
|
||||
"description": "The new email for the user."
|
||||
},
|
||||
"verificationRecordId": {
|
||||
"description": "The verification record ID for checking sensitive permissions."
|
||||
},
|
||||
"newIdentifierVerificationRecordId": {
|
||||
"description": "The identifier verification record ID for the new email ownership verification."
|
||||
}
|
||||
|
@ -186,7 +180,7 @@
|
|||
"post": {
|
||||
"operationId": "UpdatePrimaryPhone",
|
||||
"summary": "Update primary phone",
|
||||
"description": "Update primary phone for the user, a verification record is required for checking sensitive permissions, and a new identifier verification record is required for the new phone ownership verification.",
|
||||
"description": "Update primary phone for the user, a logto-verification-id in header is required for checking sensitive permissions, and a new identifier verification record is required for the new phone ownership verification.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
@ -195,9 +189,6 @@
|
|||
"phone": {
|
||||
"description": "The new phone for the user."
|
||||
},
|
||||
"verificationRecordId": {
|
||||
"description": "The verification record ID for checking sensitive permissions."
|
||||
},
|
||||
"newIdentifierVerificationRecordId": {
|
||||
"description": "The identifier verification record ID for the new phone ownership verification."
|
||||
}
|
||||
|
@ -223,15 +214,12 @@
|
|||
"post": {
|
||||
"operationId": "AddUserIdentities",
|
||||
"summary": "Add a user identity",
|
||||
"description": "Add an identity (social identity) to the user, a verification record is required for checking sensitive permissions, and a verification record for the social identity is required.",
|
||||
"description": "Add an identity (social identity) to the user, a logto-verification-id in header is required for checking sensitive permissions, and a verification record for the social identity is required.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"verificationRecordId": {
|
||||
"description": "The verification record ID for checking sensitive permissions."
|
||||
},
|
||||
"newIdentifierVerificationRecordId": {
|
||||
"description": "The identifier verification record ID for the new social identity ownership verification."
|
||||
}
|
||||
|
@ -251,14 +239,7 @@
|
|||
"delete": {
|
||||
"operationId": "DeleteIdentity",
|
||||
"summary": "Delete a user identity",
|
||||
"description": "Delete an identity (social identity) from the user, a verification record is required for checking sensitive permissions.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "verificationRecordId",
|
||||
"in": "query",
|
||||
"description": "The verification record ID for checking sensitive permissions."
|
||||
}
|
||||
],
|
||||
"description": "Delete an identity (social identity) from the user, a logto-verification-id in header is required for checking sensitive permissions.",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The identity was deleted successfully."
|
||||
|
|
|
@ -13,10 +13,7 @@ import koaGuard from '#src/middleware/koa-guard.js';
|
|||
import { EnvSet } from '../../env-set/index.js';
|
||||
import RequestError from '../../errors/RequestError/index.js';
|
||||
import { encryptUserPassword } from '../../libraries/user.utils.js';
|
||||
import {
|
||||
buildVerificationRecordByIdAndType,
|
||||
verifyUserSensitivePermission,
|
||||
} from '../../libraries/verification.js';
|
||||
import { buildVerificationRecordByIdAndType } from '../../libraries/verification.js';
|
||||
import assertThat from '../../utils/assert-that.js';
|
||||
import { PasswordValidator } from '../experience/classes/libraries/password-validator.js';
|
||||
import type { UserRouter, RouterInitArgs } from '../types.js';
|
||||
|
@ -144,12 +141,16 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
router.post(
|
||||
'/profile/password',
|
||||
koaGuard({
|
||||
body: z.object({ password: z.string().min(1), verificationRecordId: z.string() }),
|
||||
body: z.object({ password: z.string().min(1) }),
|
||||
status: [204, 400, 401, 422],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const { password, verificationRecordId } = ctx.guard.body;
|
||||
const { id: userId, identityVerified } = ctx.auth;
|
||||
assertThat(
|
||||
identityVerified,
|
||||
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
|
||||
);
|
||||
const { password } = ctx.guard.body;
|
||||
const { fields } = ctx.accountCenter;
|
||||
assertThat(
|
||||
fields.password === AccountCenterControlValue.Edit,
|
||||
|
@ -161,13 +162,6 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
const passwordPolicyChecker = new PasswordValidator(signInExperience.passwordPolicy, user);
|
||||
await passwordPolicyChecker.validatePassword(password, user);
|
||||
|
||||
await verifyUserSensitivePermission({
|
||||
userId,
|
||||
id: verificationRecordId,
|
||||
queries,
|
||||
libraries,
|
||||
});
|
||||
|
||||
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
|
||||
const updatedUser = await updateUserById(userId, {
|
||||
passwordEncrypted,
|
||||
|
@ -187,14 +181,17 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
koaGuard({
|
||||
body: z.object({
|
||||
email: z.string().regex(emailRegEx),
|
||||
verificationRecordId: z.string(),
|
||||
newIdentifierVerificationRecordId: z.string(),
|
||||
}),
|
||||
status: [204, 400, 401],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId, scopes } = ctx.auth;
|
||||
const { email, verificationRecordId, newIdentifierVerificationRecordId } = ctx.guard.body;
|
||||
const { id: userId, scopes, identityVerified } = ctx.auth;
|
||||
assertThat(
|
||||
identityVerified,
|
||||
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
|
||||
);
|
||||
const { email, newIdentifierVerificationRecordId } = ctx.guard.body;
|
||||
const { fields } = ctx.accountCenter;
|
||||
assertThat(
|
||||
fields.email === AccountCenterControlValue.Edit,
|
||||
|
@ -203,13 +200,6 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
|
||||
assertThat(scopes.has(UserScope.Email), 'auth.unauthorized');
|
||||
|
||||
await verifyUserSensitivePermission({
|
||||
userId,
|
||||
id: verificationRecordId,
|
||||
queries,
|
||||
libraries,
|
||||
});
|
||||
|
||||
// Check new identifier
|
||||
const newVerificationRecord = await buildVerificationRecordByIdAndType({
|
||||
type: VerificationType.EmailVerificationCode,
|
||||
|
@ -237,14 +227,17 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
koaGuard({
|
||||
body: z.object({
|
||||
phone: z.string().regex(phoneRegEx),
|
||||
verificationRecordId: z.string(),
|
||||
newIdentifierVerificationRecordId: z.string(),
|
||||
}),
|
||||
status: [204, 400, 401],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId, scopes } = ctx.auth;
|
||||
const { phone, verificationRecordId, newIdentifierVerificationRecordId } = ctx.guard.body;
|
||||
const { id: userId, scopes, identityVerified } = ctx.auth;
|
||||
assertThat(
|
||||
identityVerified,
|
||||
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
|
||||
);
|
||||
const { phone, newIdentifierVerificationRecordId } = ctx.guard.body;
|
||||
const { fields } = ctx.accountCenter;
|
||||
assertThat(
|
||||
fields.phone === AccountCenterControlValue.Edit,
|
||||
|
@ -253,13 +246,6 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
|
||||
assertThat(scopes.has(UserScope.Phone), 'auth.unauthorized');
|
||||
|
||||
await verifyUserSensitivePermission({
|
||||
userId,
|
||||
id: verificationRecordId,
|
||||
queries,
|
||||
libraries,
|
||||
});
|
||||
|
||||
// Check new identifier
|
||||
const newVerificationRecord = await buildVerificationRecordByIdAndType({
|
||||
type: VerificationType.PhoneVerificationCode,
|
||||
|
@ -286,14 +272,17 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
'/profile/identities',
|
||||
koaGuard({
|
||||
body: z.object({
|
||||
verificationRecordId: z.string(),
|
||||
newIdentifierVerificationRecordId: z.string(),
|
||||
}),
|
||||
status: [204, 400, 401],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId, scopes } = ctx.auth;
|
||||
const { verificationRecordId, newIdentifierVerificationRecordId } = ctx.guard.body;
|
||||
const { id: userId, scopes, identityVerified } = ctx.auth;
|
||||
assertThat(
|
||||
identityVerified,
|
||||
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
|
||||
);
|
||||
const { newIdentifierVerificationRecordId } = ctx.guard.body;
|
||||
const { fields } = ctx.accountCenter;
|
||||
assertThat(
|
||||
fields.social === AccountCenterControlValue.Edit,
|
||||
|
@ -302,13 +291,6 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
|
||||
assertThat(scopes.has(UserScope.Identities), 'auth.unauthorized');
|
||||
|
||||
await verifyUserSensitivePermission({
|
||||
userId,
|
||||
id: verificationRecordId,
|
||||
queries,
|
||||
libraries,
|
||||
});
|
||||
|
||||
// Check new identifier
|
||||
const newVerificationRecord = await buildVerificationRecordByIdAndType({
|
||||
type: VerificationType.Social,
|
||||
|
@ -350,15 +332,14 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
'/profile/identities/:target',
|
||||
koaGuard({
|
||||
params: z.object({ target: z.string() }),
|
||||
query: z.object({
|
||||
// TODO: Move all sensitive permission checks to the header
|
||||
verificationRecordId: z.string(),
|
||||
}),
|
||||
status: [204, 400, 401, 404],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId, scopes } = ctx.auth;
|
||||
const { verificationRecordId } = ctx.guard.query;
|
||||
const { id: userId, scopes, identityVerified } = ctx.auth;
|
||||
assertThat(
|
||||
identityVerified,
|
||||
new RequestError({ code: 'verification_record.permission_denied', status: 401 })
|
||||
);
|
||||
const { target } = ctx.guard.params;
|
||||
const { fields } = ctx.accountCenter;
|
||||
assertThat(
|
||||
|
@ -368,13 +349,6 @@ export default function profileRoutes<T extends UserRouter>(
|
|||
|
||||
assertThat(scopes.has(UserScope.Identities), 'auth.unauthorized');
|
||||
|
||||
await verifyUserSensitivePermission({
|
||||
userId,
|
||||
id: verificationRecordId,
|
||||
queries,
|
||||
libraries,
|
||||
});
|
||||
|
||||
const user = await findUserById(userId);
|
||||
|
||||
assertThat(
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import { type UserProfileResponse } from '@logto/schemas';
|
||||
import { type KyInstance } from 'ky';
|
||||
|
||||
const verificationRecordIdHeader = 'logto-verification-id';
|
||||
|
||||
export const updatePassword = async (
|
||||
api: KyInstance,
|
||||
verificationRecordId: string,
|
||||
password: string
|
||||
) => api.post('api/profile/password', { json: { password, verificationRecordId } });
|
||||
) =>
|
||||
api.post('api/profile/password', {
|
||||
json: { password },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const updatePrimaryEmail = async (
|
||||
api: KyInstance,
|
||||
|
@ -14,7 +20,8 @@ export const updatePrimaryEmail = async (
|
|||
newIdentifierVerificationRecordId: string
|
||||
) =>
|
||||
api.post('api/profile/primary-email', {
|
||||
json: { email, verificationRecordId, newIdentifierVerificationRecordId },
|
||||
json: { email, newIdentifierVerificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const updatePrimaryPhone = async (
|
||||
|
@ -24,7 +31,8 @@ export const updatePrimaryPhone = async (
|
|||
newIdentifierVerificationRecordId: string
|
||||
) =>
|
||||
api.post('api/profile/primary-phone', {
|
||||
json: { phone, verificationRecordId, newIdentifierVerificationRecordId },
|
||||
json: { phone, newIdentifierVerificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const updateIdentities = async (
|
||||
|
@ -33,7 +41,8 @@ export const updateIdentities = async (
|
|||
newIdentifierVerificationRecordId: string
|
||||
) =>
|
||||
api.post('api/profile/identities', {
|
||||
json: { verificationRecordId, newIdentifierVerificationRecordId },
|
||||
json: { newIdentifierVerificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const deleteIdentity = async (
|
||||
|
@ -42,7 +51,7 @@ export const deleteIdentity = async (
|
|||
verificationRecordId: string
|
||||
) =>
|
||||
api.delete(`api/profile/identities/${target}`, {
|
||||
searchParams: { verificationRecordId },
|
||||
headers: { [verificationRecordIdHeader]: verificationRecordId },
|
||||
});
|
||||
|
||||
export const updateUser = async (api: KyInstance, body: Record<string, unknown>) =>
|
||||
|
|
|
@ -13,12 +13,14 @@ import {
|
|||
updatePrimaryPhone,
|
||||
updateUser,
|
||||
} from '#src/api/profile.js';
|
||||
import { createVerificationRecordByPassword } from '#src/api/verification-record.js';
|
||||
import { expectRejects } from '#src/helpers/index.js';
|
||||
import {
|
||||
createDefaultTenantUserWithPassword,
|
||||
deleteDefaultTenantUser,
|
||||
signInAndGetUserApi,
|
||||
} from '#src/helpers/profile.js';
|
||||
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
|
||||
import { devFeatureTest, generateEmail, generatePhone } from '#src/utils.js';
|
||||
|
||||
const { describe, it } = devFeatureTest;
|
||||
|
@ -30,6 +32,7 @@ const expectedError = {
|
|||
|
||||
describe('profile, account center fields disabled', () => {
|
||||
beforeAll(async () => {
|
||||
await enableAllPasswordSignInMethods();
|
||||
await updateAccountCenter({
|
||||
enabled: true,
|
||||
fields: {
|
||||
|
@ -79,38 +82,29 @@ describe('profile, account center fields disabled', () => {
|
|||
status: 400,
|
||||
});
|
||||
|
||||
await expectRejects(updatePassword(api, 'verification-record-id', 'new-password'), {
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
await expectRejects(updatePassword(api, verificationRecordId, 'new-password'), {
|
||||
code: 'account_center.filed_not_editable',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
await expectRejects(
|
||||
updatePrimaryEmail(
|
||||
api,
|
||||
generateEmail(),
|
||||
'verification-record-id',
|
||||
'new-verification-record-id'
|
||||
),
|
||||
updatePrimaryEmail(api, generateEmail(), verificationRecordId, 'new-verification-record-id'),
|
||||
expectedError
|
||||
);
|
||||
|
||||
await expectRejects(
|
||||
updatePrimaryPhone(
|
||||
api,
|
||||
generatePhone(),
|
||||
'verification-record-id',
|
||||
'new-verification-record-id'
|
||||
),
|
||||
updatePrimaryPhone(api, generatePhone(), verificationRecordId, 'new-verification-record-id'),
|
||||
expectedError
|
||||
);
|
||||
|
||||
await expectRejects(
|
||||
updateIdentities(api, 'verification-record-id', 'new-verification-record-id'),
|
||||
updateIdentities(api, verificationRecordId, 'new-verification-record-id'),
|
||||
expectedError
|
||||
);
|
||||
|
||||
await expectRejects(
|
||||
deleteIdentity(api, mockSocialConnectorTarget, 'verification-record-id'),
|
||||
deleteIdentity(api, mockSocialConnectorTarget, verificationRecordId),
|
||||
expectedError
|
||||
);
|
||||
|
||||
|
|
|
@ -33,14 +33,10 @@ describe('profile (email and phone)', () => {
|
|||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const newEmail = generateEmail();
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(
|
||||
updatePrimaryEmail(
|
||||
api,
|
||||
newEmail,
|
||||
'invalid-verification-record-id',
|
||||
'new-verification-record-id'
|
||||
),
|
||||
updatePrimaryEmail(api, newEmail, verificationRecordId, 'new-verification-record-id'),
|
||||
{
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
|
@ -144,14 +140,10 @@ describe('profile (email and phone)', () => {
|
|||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const newPhone = generatePhone();
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(
|
||||
updatePrimaryPhone(
|
||||
api,
|
||||
newPhone,
|
||||
'invalid-verification-record-id',
|
||||
'new-verification-record-id'
|
||||
),
|
||||
updatePrimaryPhone(api, newPhone, verificationRecordId, 'new-verification-record-id'),
|
||||
{
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
|
|
|
@ -254,8 +254,9 @@ describe('profile', () => {
|
|||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const newPassword = '123456';
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(updatePassword(api, 'invalid-varification-record-id', newPassword), {
|
||||
await expectRejects(updatePassword(api, verificationRecordId, newPassword), {
|
||||
code: 'password.rejected',
|
||||
status: 422,
|
||||
});
|
||||
|
|
|
@ -54,9 +54,10 @@ describe('profile (social)', () => {
|
|||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(
|
||||
updateIdentities(api, 'invalid-verification-record-id', 'new-verification-record-id'),
|
||||
updateIdentities(api, verificationRecordId, 'new-verification-record-id'),
|
||||
{
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
|
@ -107,7 +108,6 @@ describe('profile (social)', () => {
|
|||
const api = await signInAndGetUserApi(username, password, {
|
||||
scopes: [UserScope.Profile, UserScope.Identities],
|
||||
});
|
||||
|
||||
await expectRejects(
|
||||
createSocialVerificationRecord(api, 'invalid-connector-id', state, redirectUri),
|
||||
{
|
||||
|
@ -173,14 +173,12 @@ describe('profile (social)', () => {
|
|||
it('should fail if scope is missing', async () => {
|
||||
const { user, username, password } = await createDefaultTenantUserWithPassword();
|
||||
const api = await signInAndGetUserApi(username, password);
|
||||
const verificationRecordId = await createVerificationRecordByPassword(api, password);
|
||||
|
||||
await expectRejects(
|
||||
deleteIdentity(api, mockSocialConnectorTarget, 'invalid-verification-record-id'),
|
||||
{
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
}
|
||||
);
|
||||
await expectRejects(deleteIdentity(api, mockSocialConnectorTarget, verificationRecordId), {
|
||||
code: 'auth.unauthorized',
|
||||
status: 400,
|
||||
});
|
||||
|
||||
await deleteDefaultTenantUser(user.id);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue