Skip to content

Commit

Permalink
refactor(core): refactor the identifyUser method
Browse files Browse the repository at this point in the history
refactor the identifyUser method
  • Loading branch information
simeng-li committed Jul 1, 2024
1 parent b8ef8fb commit d3edb57
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 85 deletions.
31 changes: 12 additions & 19 deletions packages/core/src/routes/experience/classes/interaction-session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InteractionEvent, type VerificationType } from '@logto/schemas';
import { InteractionEvent, VerificationType } from '@logto/schemas';
import { z } from 'zod';

import RequestError from '#src/errors/RequestError/index.js';
Expand Down Expand Up @@ -81,24 +81,17 @@ export default class InteractionSession {
}

/** Set the verified accountId of the current interaction session from the verification record */
public identifyUser(verificationRecord: VerificationRecord) {
// Throws an 404 error if the user is not found by the given verification record
// TODO: refactor using real-time user verification. Static verifiedUserId will be removed.
assertThat(
verificationRecord.verifiedUserId,
new RequestError(
{
code: 'user.user_not_exist',
status: 404,
},
{
identifier:
'identifier' in verificationRecord ? verificationRecord.identifier : undefined,
}
)
);

this.accountId = verificationRecord.verifiedUserId;
public async identifyUser(verificationRecord: VerificationRecord) {
switch (verificationRecord.type) {
case VerificationType.Password:
case VerificationType.VerificationCode:
case VerificationType.Social: {
const { id, isSuspended } = await verificationRecord.identifyUser();
assertThat(!isSuspended, new RequestError({ code: 'user.suspended', status: 401 }));
this.accountId = id;
break;
}
}

Check warning on line 94 in packages/core/src/routes/experience/classes/interaction-session.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/interaction-session.ts#L85-L94

Added lines #L85 - L94 were not covered by tests
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {
InteractionEvent,
VerificationType,
verificationCodeIdentifierGuard,
type User,
type VerificationCodeIdentifier,
} from '@logto/schemas';
import { type ToZodObject } from '@logto/schemas/lib/utils/zod.js';
import { generateStandardId } from '@logto/shared';
import { z } from 'zod';

import RequestError from '#src/errors/RequestError/index.js';
import { type createPasscodeLibrary } from '#src/libraries/passcode.js';
import type Libraries from '#src/tenants/Libraries.js';
import type Queries from '#src/tenants/Queries.js';
Expand Down Expand Up @@ -39,7 +41,6 @@ export type CodeVerificationRecordData = {
type: VerificationType.VerificationCode;
identifier: VerificationCodeIdentifier;
interactionEvent: InteractionEvent;
userId?: string;
verified: boolean;
};

Expand All @@ -48,7 +49,6 @@ export const codeVerificationRecordDataGuard = z.object({
type: z.literal(VerificationType.VerificationCode),
identifier: verificationCodeIdentifierGuard,
interactionEvent: z.nativeEnum(InteractionEvent),
userId: z.string().optional(),
verified: z.boolean(),
}) satisfies ToZodObject<CodeVerificationRecordData>;

Expand Down Expand Up @@ -101,22 +101,19 @@ export class CodeVerification implements Verification {
* @remark
* `InteractionEvent.ForgotPassword` triggered verification results can not used as a verification record for other events.
*/
private readonly interactionEvent: InteractionEvent;
/** The userId will be set after the verification if the identifier matches any existing user's record */
private userId?: string;
public readonly interactionEvent: InteractionEvent;
private verified: boolean;

constructor(
private readonly libraries: Libraries,
private readonly queries: Queries,
data: CodeVerificationRecordData
) {
const { id, identifier, userId, verified, interactionEvent } = data;
const { id, identifier, verified, interactionEvent } = data;

Check warning on line 112 in packages/core/src/routes/experience/classes/verifications/code-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/code-verification.ts#L112

Added line #L112 was not covered by tests

this.id = id;
this.identifier = identifier;
this.interactionEvent = interactionEvent;
this.userId = userId;
this.verified = verified;
}

Expand All @@ -125,11 +122,6 @@ export class CodeVerification implements Verification {
return this.verified;
}

/** Returns the userId if it is set */
get verifiedUserId() {
return this.userId;
}

/**
* Verify the `identifier` with the given code
*
Expand All @@ -153,10 +145,31 @@ export class CodeVerification implements Verification {
);

this.verified = true;
}

Check warning on line 148 in packages/core/src/routes/experience/classes/verifications/code-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/code-verification.ts#L148

Added line #L148 was not covered by tests

/**
* Identify the user by the current `identifier`.
* Return undefined if the verification record is not verified or no user is found by the identifier.
*/
async identifyUser(): Promise<User> {
assertThat(
this.verified,
new RequestError({ code: 'session.verification_failed', status: 400 })
);

Check warning on line 158 in packages/core/src/routes/experience/classes/verifications/code-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/code-verification.ts#L155-L158

Added lines #L155 - L158 were not covered by tests

// Try to lookup the user by the identifier
const user = await findUserByIdentifier(this.queries.users, this.identifier);
this.userId = user?.id;

assertThat(
user,
new RequestError(
{ code: 'user.user_not_exist', status: 404 },
{
identifier: this.identifier.value,
}
)
);

return user;

Check warning on line 172 in packages/core/src/routes/experience/classes/verifications/code-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/code-verification.ts#L161-L172

Added lines #L161 - L172 were not covered by tests
}

toJson(): CodeVerificationRecordData {
Expand All @@ -165,7 +178,6 @@ export class CodeVerification implements Verification {
type: this.type,
identifier: this.identifier,
interactionEvent: this.interactionEvent,
userId: this.userId,
verified: this.verified,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
VerificationType,
interactionIdentifierGuard,
type InteractionIdentifier,
type User,
} from '@logto/schemas';
import { type ToZodObject } from '@logto/schemas/lib/utils/zod.js';
import { generateStandardId } from '@logto/shared';
Expand All @@ -20,14 +21,14 @@ export type PasswordVerificationRecordData = {
id: string;
type: VerificationType.Password;
identifier: InteractionIdentifier;
userId?: string;
verified: boolean;
};

export const passwordVerificationRecordDataGuard = z.object({
id: z.string(),
type: z.literal(VerificationType.Password),
identifier: interactionIdentifierGuard,
userId: z.string().optional(),
verified: z.boolean(),
}) satisfies ToZodObject<PasswordVerificationRecordData>;

/**
Expand All @@ -41,54 +42,65 @@ export class PasswordVerification implements Verification {
id: generateStandardId(),
type: VerificationType.Password,
identifier,
verified: false,

Check warning on line 45 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L45

Added line #L45 was not covered by tests
});
}

readonly type = VerificationType.Password;
public readonly identifier: InteractionIdentifier;
public readonly id: string;
/* The userId of the user that has been identifier by the given identifier and password */
private userId?: string;
private verified: boolean;

constructor(
private readonly libraries: Libraries,
private readonly queries: Queries,
data: PasswordVerificationRecordData
) {
const { id, identifier, userId } = data;
const { id, identifier, verified } = data;

Check warning on line 59 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L59

Added line #L59 was not covered by tests

this.id = id;
this.identifier = identifier;
this.userId = userId;
this.verified = verified;

Check warning on line 63 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L63

Added line #L63 was not covered by tests
}

/** Returns true if a userId is set */
get isVerified() {
return this.userId !== undefined;
}

get verifiedUserId() {
return this.userId;
return this.verified;

Check warning on line 68 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L68

Added line #L68 was not covered by tests
}

/** Verifies the password and sets the userId */
async verify(password: string) {
const user = await findUserByIdentifier(this.queries.users, this.identifier);

// Throws an 422 error if the user is not found or the password is incorrect
const { isSuspended, id } = await this.libraries.users.verifyUserPassword(user, password);

const { isSuspended } = await this.libraries.users.verifyUserPassword(user, password);

Check warning on line 76 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L76

Added line #L76 was not covered by tests
assertThat(!isSuspended, new RequestError({ code: 'user.suspended', status: 401 }));

this.userId = id;
this.verified = true;

return user;
}

Check warning on line 82 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L79-L82

Added lines #L79 - L82 were not covered by tests

/** Identifies the user using the username */
async identifyUser(): Promise<User> {
assertThat(
this.verified,
new RequestError({ code: 'session.verification_failed', status: 400 })
);

const user = await findUserByIdentifier(this.queries.users, this.identifier);

assertThat(user, new RequestError({ code: 'user.user_not_exist', status: 404 }));

return user;

Check warning on line 95 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L86-L95

Added lines #L86 - L95 were not covered by tests
}

toJson(): PasswordVerificationRecordData {
return {
id: this.id,
type: this.type,
identifier: this.identifier,
userId: this.userId,
verified: this.verified,

Check warning on line 103 in packages/core/src/routes/experience/classes/verifications/password-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/password-verification.ts#L103

Added line #L103 was not covered by tests
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { generateStandardId } from '@logto/shared';
import { z } from 'zod';

import RequestError from '#src/errors/RequestError/index.js';
import { type WithLogContext } from '#src/middleware/koa-audit-log.js';
import {
createSocialAuthorizationUrl,
Expand All @@ -16,6 +17,7 @@ import {
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 { type Verification } from './verification.js';

Expand All @@ -28,18 +30,13 @@ export type SocialVerificationRecordData = {
* The social identity returned by the connector.
*/
socialUserInfo?: SocialUserInfo;
/**
* The userId of the user that has been verified by the social identity.
*/
userId?: string;
};

export const socialVerificationRecordDataGuard = z.object({
id: z.string(),
connectorId: z.string(),
type: z.literal(VerificationType.Social),
socialUserInfo: socialUserInfoGuard.optional(),
userId: z.string().optional(),
}) satisfies ToZodObject<SocialVerificationRecordData>;

export class SocialVerification implements Verification {
Expand All @@ -58,20 +55,17 @@ export class SocialVerification implements Verification {
public readonly type = VerificationType.Social;
public readonly connectorId: string;
public socialUserInfo?: SocialUserInfo;
public userId?: string;

constructor(
private readonly libraries: Libraries,
private readonly queries: Queries,
data: SocialVerificationRecordData
) {
const { id, connectorId, socialUserInfo, userId } =
socialVerificationRecordDataGuard.parse(data);
const { id, connectorId, socialUserInfo } = socialVerificationRecordDataGuard.parse(data);

Check warning on line 64 in packages/core/src/routes/experience/classes/verifications/social-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/social-verification.ts#L64

Added line #L64 was not covered by tests

this.id = id;
this.connectorId = connectorId;
this.socialUserInfo = socialUserInfo;
this.userId = userId;
}

/**
Expand All @@ -81,10 +75,6 @@ export class SocialVerification implements Verification {
return Boolean(this.socialUserInfo);
}

get verifiedUserId() {
return this.userId;
}

/**
* Create the authorization URL for the social connector.
* Store the connector session result in the provider's interaction storage.
Expand Down Expand Up @@ -134,12 +124,47 @@ export class SocialVerification implements Verification {
);

this.socialUserInfo = socialUserInfo;
}

Check warning on line 127 in packages/core/src/routes/experience/classes/verifications/social-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/social-verification.ts#L127

Added line #L127 was not covered by tests

/**
* Identify the user by the social identity.
* If the user is not found, find the related user by the social identity and throw an error.
*/
async identifyUser(): Promise<User> {
assertThat(
this.isVerified,
new RequestError({ code: 'session.verification_failed', status: 400 })
);

Check warning on line 137 in packages/core/src/routes/experience/classes/verifications/social-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/social-verification.ts#L134-L137

Added lines #L134 - L137 were not covered by tests

const user = await this.findUserBySocialIdentity();
this.userId = user?.id;

if (!user) {
const relatedUser = await this.findRelatedUserBySocialIdentity();

throw new RequestError(
{
code: 'user.identity_not_exist',
status: 422,
},
{
...(relatedUser && { relatedUser: relatedUser[0] }),
}
);
}

return user;
}

Check warning on line 156 in packages/core/src/routes/experience/classes/verifications/social-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/social-verification.ts#L140-L156

Added lines #L140 - L156 were not covered by tests

toJson(): SocialVerificationRecordData {
return {
id: this.id,
connectorId: this.connectorId,
type: this.type,
socialUserInfo: this.socialUserInfo,
};

Check warning on line 164 in packages/core/src/routes/experience/classes/verifications/social-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/social-verification.ts#L159-L164

Added lines #L159 - L164 were not covered by tests
}

async findUserBySocialIdentity(): Promise<User | undefined> {
private async findUserBySocialIdentity(): Promise<User | undefined> {
const { socials } = this.libraries;
const {
users: { findUserByIdentity },
Expand All @@ -158,7 +183,9 @@ export class SocialVerification implements Verification {
return user ?? undefined;
}

async findRelatedUserBySocialIdentity(): ReturnType<typeof socials.findSocialRelatedUser> {
private async findRelatedUserBySocialIdentity(): ReturnType<
typeof socials.findSocialRelatedUser
> {

Check warning on line 188 in packages/core/src/routes/experience/classes/verifications/social-verification.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/verifications/social-verification.ts#L187-L188

Added lines #L187 - L188 were not covered by tests
const { socials } = this.libraries;

if (!this.socialUserInfo) {
Expand All @@ -167,13 +194,4 @@ export class SocialVerification implements Verification {

return socials.findSocialRelatedUser(this.socialUserInfo);
}

toJson(): SocialVerificationRecordData {
return {
id: this.id,
connectorId: this.connectorId,
type: this.type,
socialUserInfo: this.socialUserInfo,
};
}
}
Loading

0 comments on commit d3edb57

Please sign in to comment.