Skip to content

Commit

Permalink
Merge pull request #1874 from Giveth/feat/add_verify_email
Browse files Browse the repository at this point in the history
Feat/User email verification
  • Loading branch information
kkatusic authored Nov 22, 2024
2 parents fc407fa + ddaef95 commit 5d7a1d0
Show file tree
Hide file tree
Showing 16 changed files with 608 additions and 87 deletions.
2 changes: 1 addition & 1 deletion config/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,4 @@ STELLAR_HORIZON_API_URL=https://horizon.stellar.org
STELLAR_SCAN_API_URL=https://stellar.expert/explorer/public

ENDAOMENT_ADMIN_WALLET_ADDRESS=0xfE3524e04E4e564F9935D34bB5e80c5CaB07F5b4
SYNC_USER_MODEL_SCORE_CRONJOB_EXPRESSION=0 0 */3 * *
SYNC_USER_MODEL_SCORE_CRONJOB_EXPRESSION=0 0 */3 * *
21 changes: 21 additions & 0 deletions migration/1731071653657-addUserEmailVerificationColumns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddUserEmailVerificationColumns1731071653657
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`
ALTER TABLE "user"
ADD COLUMN IF NOT EXISTS "emailVerificationCode" VARCHAR,
ADD COLUMN IF NOT EXISTS "isEmailVerified" BOOLEAN DEFAULT false;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`
ALTER TABLE "user"
DROP COLUMN IF EXISTS "emailVerificationCode",
DROP COLUMN IF EXISTS "isEmailVerified";
`);
}
}
8 changes: 8 additions & 0 deletions src/adapters/notifications/MockNotificationAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export class MockNotificationAdapter implements NotificationAdapterInterface {
return Promise.resolve(undefined);
}

sendUserEmailConfirmationCodeFlow(params: { email: string }): Promise<void> {
logger.debug(
'MockNotificationAdapter sendUserEmailConfirmationCodeFlow',
params,
);
return Promise.resolve(undefined);
}

userSuperTokensCritical(): Promise<void> {
return Promise.resolve(undefined);
}
Expand Down
5 changes: 5 additions & 0 deletions src/adapters/notifications/NotificationAdapterInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ export interface NotificationAdapterInterface {
networkName: string;
}): Promise<void>;

sendUserEmailConfirmationCodeFlow(params: {
email: string;
user: User;
}): Promise<void>;

projectVerified(params: { project: Project }): Promise<void>;
projectBoosted(params: { projectId: number; userId: number }): Promise<void>;
projectBoostedBatch(params: {
Expand Down
22 changes: 22 additions & 0 deletions src/adapters/notifications/NotificationCenterAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface {
}
}

async sendUserEmailConfirmationCodeFlow(params: {
email: string;
user: User;
}): Promise<void> {
const { email, user } = params;
try {
await callSendNotification({
eventName:
NOTIFICATIONS_EVENT_NAMES.SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW,
segment: {
payload: {
email,
verificationCode: user.emailVerificationCode,
userId: user.id,
},
},
});
} catch (e) {
logger.error('sendUserEmailConfirmationCodeFlow >> error', e);
}
}

async userSuperTokensCritical(params: {
user: User;
eventName: UserStreamBalanceWarning;
Expand Down
2 changes: 2 additions & 0 deletions src/analytics/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ export enum NOTIFICATIONS_EVENT_NAMES {
SUBSCRIBE_ONBOARDING = 'Subscribe onboarding',
CREATE_ORTTO_PROFILE = 'Create Ortto profile',
SEND_EMAIL_CONFIRMATION = 'Send email confirmation',

SEND_USER_EMAIL_CONFIRMATION_CODE_FLOW = 'Send email confirmation code flow',
}
8 changes: 8 additions & 0 deletions src/entities/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const publicSelectionFields = [
'user.totalReceived',
'user.passportScore',
'user.passportStamps',
'user.isEmailVerified',
];

export enum UserRole {
Expand Down Expand Up @@ -195,6 +196,13 @@ export class User extends BaseEntity {
@Field(_type => Float, { nullable: true })
activeQFMBDScore?: number;

@Field(_type => Boolean, { nullable: true })
@Column('bool', { default: false })
isEmailVerified: boolean;

@Column('varchar', { nullable: true, default: null })
emailVerificationCode?: string | null;

@Field(_type => Int, { nullable: true })
async donationsCount() {
// Count for non-recurring donations
Expand Down
15 changes: 15 additions & 0 deletions src/resolvers/projectResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,14 @@ export class ProjectResolver {
throw new Error(
i18n.__(translationErrorMessagesKeys.AUTHENTICATION_REQUIRED),
);

const dbUser = await findUserById(user.userId);

// Check if user email is verified
if (!dbUser || !dbUser.isEmailVerified) {
throw new Error(i18n.__(translationErrorMessagesKeys.EMAIL_NOT_VERIFIED));
}

const { image } = newProjectData;

// const project = await Project.findOne({ id: projectId });
Expand Down Expand Up @@ -1362,6 +1370,13 @@ export class ProjectResolver {
const user = await getLoggedInUser(ctx);
const { image, description } = projectInput;

const dbUser = await findUserById(user.id);

// Check if user email is verified
if (!dbUser || !dbUser.isEmailVerified) {
throw new Error(i18n.__(translationErrorMessagesKeys.EMAIL_NOT_VERIFIED));
}

const qualityScore = getQualityScore(description, Boolean(image), 0);

if (!projectInput.categories) {
Expand Down
Loading

0 comments on commit 5d7a1d0

Please sign in to comment.