feat: Add email verification and password reset
This commit is contained in:
@@ -2,7 +2,7 @@ import {Controller, Get, Middleware} from '@overnightjs/core';
|
||||
import type {NextFunction, Request, Response} from 'express';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {ActivityService, ActivityType} from '../services/ActivityService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -21,7 +21,7 @@ export class Activity {
|
||||
* - endDate: ISO date string
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getActivities(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -62,7 +62,7 @@ export class Activity {
|
||||
* - endDate: ISO date string (defaults to now)
|
||||
*/
|
||||
@Get('stats')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getStats(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -82,7 +82,7 @@ export class Activity {
|
||||
* - minutes: number (default 5)
|
||||
*/
|
||||
@Get('recent-count')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getRecentCount(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -98,7 +98,7 @@ export class Activity {
|
||||
* Get available activity types (for UI filters)
|
||||
*/
|
||||
@Get('types')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getTypes(_req: Request, res: Response, _next: NextFunction) {
|
||||
const types = Object.values(ActivityType);
|
||||
@@ -114,7 +114,7 @@ export class Activity {
|
||||
* - daysAhead: number (default 30, max 90)
|
||||
*/
|
||||
@Get('upcoming')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getUpcoming(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -2,7 +2,7 @@ import {Controller, Get, Middleware} from '@overnightjs/core';
|
||||
import type {NextFunction, Request, Response} from 'express';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {AnalyticsService} from '../services/AnalyticsService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -19,7 +19,7 @@ export class Analytics {
|
||||
* Returns daily aggregated email metrics (sent, opened, clicked, bounced, delivered)
|
||||
*/
|
||||
@Get('timeseries')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getTimeSeries(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -41,7 +41,7 @@ export class Analytics {
|
||||
* - endDate: ISO date string (defaults to now)
|
||||
*/
|
||||
@Get('top-campaigns')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getTopCampaigns(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -65,7 +65,7 @@ export class Analytics {
|
||||
* Returns aggregate stats: total campaigns, active, completed, average rates
|
||||
*/
|
||||
@Get('campaign-stats')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getCampaignStats(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -89,7 +89,7 @@ export class Analytics {
|
||||
* Returns events sorted by frequency with trend data
|
||||
*/
|
||||
@Get('top-events')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getTopEvents(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import {Controller, Get, Post} from '@overnightjs/core';
|
||||
import {AuthenticationSchemas} from '@plunk/shared';
|
||||
import {EmailVerificationEmail, PasswordResetEmail, sendPlatformEmail} from '@plunk/email';
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import type {NextFunction, Request, Response} from 'express';
|
||||
import * as React from 'react';
|
||||
|
||||
import {GITHUB_OAUTH_ENABLED, GOOGLE_OAUTH_ENABLED} from '../app/constants.js';
|
||||
import {
|
||||
DASHBOARD_URI,
|
||||
EMAIL_VERIFICATION_RATE_LIMIT,
|
||||
EMAIL_VERIFICATION_RATE_WINDOW,
|
||||
GITHUB_OAUTH_ENABLED,
|
||||
GOOGLE_OAUTH_ENABLED,
|
||||
LANDING_URI,
|
||||
PASSWORD_RESET_RATE_LIMIT,
|
||||
PLUNK_ENABLED,
|
||||
TOKEN_EXPIRY_SECONDS,
|
||||
} from '../app/constants.js';
|
||||
import {prisma} from '../database/prisma.js';
|
||||
import {redis, REDIS_ONE_MINUTE} from '../database/redis.js';
|
||||
import {jwt} from '../middleware/auth.js';
|
||||
import {BadRequest, NotAuthenticated, RateLimitError} from '../exceptions/index.js';
|
||||
import {jwt, parseJwt} from '../middleware/auth.js';
|
||||
import {AuthService} from '../services/AuthService.js';
|
||||
import {NtfyService} from '../services/NtfyService.js';
|
||||
import {UserService} from '../services/UserService.js';
|
||||
@@ -64,6 +78,8 @@ export class Auth {
|
||||
email,
|
||||
password: await AuthService.generateHash(password),
|
||||
type: 'PASSWORD',
|
||||
// Auto-verify email if platform emails are disabled
|
||||
emailVerified: !PLUNK_ENABLED,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -72,6 +88,27 @@ export class Auth {
|
||||
// Send notification about new user signup
|
||||
await NtfyService.notifyUserSignup(created_user.email, created_user.id);
|
||||
|
||||
// Send email verification if platform emails are enabled
|
||||
if (PLUNK_ENABLED) {
|
||||
const verificationToken = randomBytes(32).toString('hex');
|
||||
await redis.setex(
|
||||
Keys.User.emailVerificationToken(verificationToken),
|
||||
TOKEN_EXPIRY_SECONDS,
|
||||
JSON.stringify({userId: created_user.id, email: created_user.email, createdAt: Date.now()}),
|
||||
);
|
||||
|
||||
const verificationUrl = `${LANDING_URI}/auth/verify-email?token=${verificationToken}`;
|
||||
await sendPlatformEmail(
|
||||
created_user.email,
|
||||
'Verify your email address',
|
||||
React.createElement(EmailVerificationEmail, {
|
||||
email: created_user.email,
|
||||
verificationUrl,
|
||||
landingUrl: LANDING_URI,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const token = jwt.sign(created_user.id);
|
||||
const cookie = UserService.cookieOptions();
|
||||
|
||||
@@ -97,4 +134,159 @@ export class Auth {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Post('verify-email')
|
||||
@CatchAsync
|
||||
public async verifyEmail(req: Request, res: Response, _next: NextFunction) {
|
||||
const {token} = AuthenticationSchemas.verifyEmail.parse(req.body);
|
||||
|
||||
// Look up token in Redis
|
||||
const data = await redis.get(Keys.User.emailVerificationToken(token));
|
||||
|
||||
if (!data) {
|
||||
throw new BadRequest('Invalid or expired verification token');
|
||||
}
|
||||
|
||||
const {userId} = JSON.parse(data);
|
||||
|
||||
// Update user
|
||||
await prisma.user.update({
|
||||
where: {id: userId},
|
||||
data: {emailVerified: true},
|
||||
});
|
||||
|
||||
// Delete token (single use) and invalidate cache
|
||||
await redis.del(Keys.User.emailVerificationToken(token));
|
||||
await redis.del(Keys.User.id(userId));
|
||||
|
||||
return res.json({success: true, data: {message: 'Email verified successfully'}});
|
||||
}
|
||||
|
||||
@Post('request-verification')
|
||||
@CatchAsync
|
||||
public async requestVerification(req: Request, res: Response, _next: NextFunction) {
|
||||
const userId = parseJwt(req);
|
||||
const user = await UserService.id(userId);
|
||||
|
||||
if (!user) {
|
||||
throw new NotAuthenticated();
|
||||
}
|
||||
|
||||
if (user.emailVerified) {
|
||||
return res.json({success: true, data: {message: 'Email already verified'}});
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
const rateLimitKey = Keys.User.emailVerificationRateLimit(userId);
|
||||
const count = await redis.get(rateLimitKey);
|
||||
|
||||
if (count && parseInt(count) >= EMAIL_VERIFICATION_RATE_LIMIT) {
|
||||
throw new RateLimitError('Too many verification emails sent. Please try again later.');
|
||||
}
|
||||
|
||||
// Generate token
|
||||
const token = randomBytes(32).toString('hex');
|
||||
await redis.setex(
|
||||
Keys.User.emailVerificationToken(token),
|
||||
TOKEN_EXPIRY_SECONDS,
|
||||
JSON.stringify({userId, email: user.email, createdAt: Date.now()}),
|
||||
);
|
||||
|
||||
// Send email
|
||||
const verificationUrl = `${LANDING_URI}/auth/verify-email?token=${token}`;
|
||||
await sendPlatformEmail(
|
||||
user.email,
|
||||
'Verify your email address',
|
||||
React.createElement(EmailVerificationEmail, {email: user.email, verificationUrl, landingUrl: LANDING_URI}),
|
||||
);
|
||||
|
||||
// Increment rate limit
|
||||
if (count) {
|
||||
await redis.incr(rateLimitKey);
|
||||
} else {
|
||||
await redis.setex(rateLimitKey, EMAIL_VERIFICATION_RATE_WINDOW, '1');
|
||||
}
|
||||
|
||||
return res.json({success: true, data: {message: 'Verification email sent'}});
|
||||
}
|
||||
|
||||
@Post('request-password-reset')
|
||||
@CatchAsync
|
||||
public async requestPasswordReset(req: Request, res: Response, _next: NextFunction) {
|
||||
const {email} = AuthenticationSchemas.requestPasswordReset.parse(req.body);
|
||||
|
||||
// Check rate limit
|
||||
const rateLimitKey = Keys.User.passwordResetRateLimit(email);
|
||||
const count = await redis.get(rateLimitKey);
|
||||
|
||||
if (count && parseInt(count) >= PASSWORD_RESET_RATE_LIMIT) {
|
||||
// Still return success to prevent enumeration
|
||||
return res.json({success: true, data: {message: 'If that email exists, a reset link has been sent'}});
|
||||
}
|
||||
|
||||
// Look up user
|
||||
const user = await UserService.email(email);
|
||||
|
||||
// Only send email if user exists and is PASSWORD type
|
||||
if (user && user.type === 'PASSWORD') {
|
||||
const token = randomBytes(32).toString('hex');
|
||||
await redis.setex(
|
||||
Keys.User.passwordResetToken(token),
|
||||
TOKEN_EXPIRY_SECONDS,
|
||||
JSON.stringify({userId: user.id, email: user.email, createdAt: Date.now()}),
|
||||
);
|
||||
|
||||
const resetUrl = `${DASHBOARD_URI}/auth/reset-password?token=${token}`;
|
||||
await sendPlatformEmail(
|
||||
user.email,
|
||||
'Reset your password',
|
||||
React.createElement(PasswordResetEmail, {email: user.email, resetUrl, landingUrl: LANDING_URI}),
|
||||
);
|
||||
|
||||
// Increment rate limit
|
||||
if (count) {
|
||||
await redis.incr(rateLimitKey);
|
||||
} else {
|
||||
await redis.setex(rateLimitKey, EMAIL_VERIFICATION_RATE_WINDOW, '1');
|
||||
}
|
||||
}
|
||||
|
||||
// Always return success (prevent enumeration)
|
||||
return res.json({success: true, data: {message: 'If that email exists, a reset link has been sent'}});
|
||||
}
|
||||
|
||||
@Post('reset-password')
|
||||
@CatchAsync
|
||||
public async resetPassword(req: Request, res: Response, _next: NextFunction) {
|
||||
const {token, newPassword} = AuthenticationSchemas.resetPassword.parse(req.body);
|
||||
|
||||
// Look up token
|
||||
const data = await redis.get(Keys.User.passwordResetToken(token));
|
||||
|
||||
if (!data) {
|
||||
throw new BadRequest('Invalid or expired reset token');
|
||||
}
|
||||
|
||||
const {userId} = JSON.parse(data);
|
||||
|
||||
// Get user and verify type
|
||||
const user = await prisma.user.findUnique({where: {id: userId}});
|
||||
|
||||
if (!user || user.type !== 'PASSWORD') {
|
||||
throw new BadRequest('Invalid reset token');
|
||||
}
|
||||
|
||||
// Hash new password and update
|
||||
const hashedPassword = await AuthService.generateHash(newPassword);
|
||||
await prisma.user.update({
|
||||
where: {id: userId},
|
||||
data: {password: hashedPassword},
|
||||
});
|
||||
|
||||
// Delete token and invalidate cache
|
||||
await redis.del(Keys.User.passwordResetToken(token));
|
||||
await redis.del(Keys.User.id(userId));
|
||||
|
||||
return res.json({success: true, data: {message: 'Password reset successfully'}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {NextFunction, Request, Response} from 'express';
|
||||
|
||||
import {HttpException} from '../exceptions/index.js';
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {CampaignService} from '../services/CampaignService.js';
|
||||
import {DomainService} from '../services/DomainService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
@@ -17,7 +17,7 @@ export class Campaigns {
|
||||
* POST /campaigns
|
||||
*/
|
||||
@Post('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async create(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -60,7 +60,7 @@ export class Campaigns {
|
||||
* GET /campaigns
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async list(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -93,7 +93,7 @@ export class Campaigns {
|
||||
* GET /campaigns/:id
|
||||
*/
|
||||
@Get(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async get(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -112,7 +112,7 @@ export class Campaigns {
|
||||
* PUT /campaigns/:id
|
||||
*/
|
||||
@Put(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async update(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -158,7 +158,7 @@ export class Campaigns {
|
||||
* DELETE /campaigns/:id
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async delete(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -177,7 +177,7 @@ export class Campaigns {
|
||||
* POST /campaigns/:id/duplicate
|
||||
*/
|
||||
@Post(':id/duplicate')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async duplicate(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -197,7 +197,7 @@ export class Campaigns {
|
||||
* POST /campaigns/:id/send
|
||||
*/
|
||||
@Post(':id/send')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async send(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -228,7 +228,7 @@ export class Campaigns {
|
||||
* POST /campaigns/:id/cancel
|
||||
*/
|
||||
@Post(':id/cancel')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async cancel(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -248,7 +248,7 @@ export class Campaigns {
|
||||
* GET /campaigns/:id/stats
|
||||
*/
|
||||
@Get(':id/stats')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async stats(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -267,7 +267,7 @@ export class Campaigns {
|
||||
* POST /campaigns/:id/test
|
||||
*/
|
||||
@Post(':id/test')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async sendTest(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -4,7 +4,7 @@ import multer from 'multer';
|
||||
import signale from 'signale';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {ContactService} from '../services/ContactService.js';
|
||||
import {QueueService} from '../services/QueueService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
@@ -32,7 +32,7 @@ export class Contacts {
|
||||
* List all contacts for the authenticated project with cursor-based pagination
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -51,7 +51,7 @@ export class Contacts {
|
||||
* Returns field names with inferred types (string, number, boolean, date)
|
||||
*/
|
||||
@Get('fields')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getAvailableFields(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -77,7 +77,7 @@ export class Contacts {
|
||||
* Example: /contacts/fields/data.plan/values or /contacts/fields/subscribed/values
|
||||
*/
|
||||
@Get('fields/:field/values')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getFieldValues(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -110,7 +110,7 @@ export class Contacts {
|
||||
* Get a specific contact by ID
|
||||
*/
|
||||
@Get(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -130,7 +130,7 @@ export class Contacts {
|
||||
* Create or update a contact (upsert)
|
||||
*/
|
||||
@Post('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -160,7 +160,7 @@ export class Contacts {
|
||||
* Update a contact
|
||||
*/
|
||||
@Patch(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -181,7 +181,7 @@ export class Contacts {
|
||||
* Delete a contact
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -301,7 +301,7 @@ export class Contacts {
|
||||
* Get import job status
|
||||
*/
|
||||
@Get('import/:jobId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getImportStatus(req: Request, res: Response, _next: NextFunction) {
|
||||
const jobId = req.params.jobId;
|
||||
@@ -332,7 +332,7 @@ export class Contacts {
|
||||
* Returns information about where the field is used and whether it can be safely deleted
|
||||
*/
|
||||
@Get('fields/:field/usage')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getFieldUsage(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -359,7 +359,7 @@ export class Contacts {
|
||||
* Only works if the field is not used in any segments or campaigns
|
||||
*/
|
||||
@Delete('fields/:field')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async deleteField(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -385,7 +385,7 @@ export class Contacts {
|
||||
* Queue bulk subscribe operation
|
||||
*/
|
||||
@Post('bulk-subscribe')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async bulkSubscribe(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -420,7 +420,7 @@ export class Contacts {
|
||||
* Queue bulk unsubscribe operation
|
||||
*/
|
||||
@Post('bulk-unsubscribe')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async bulkUnsubscribe(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -454,7 +454,7 @@ export class Contacts {
|
||||
* Queue bulk delete operation
|
||||
*/
|
||||
@Post('bulk-delete')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async bulkDelete(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -488,7 +488,7 @@ export class Contacts {
|
||||
* Get bulk action job status
|
||||
*/
|
||||
@Get('bulk/:jobId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getBulkActionStatus(req: Request, res: Response, _next: NextFunction) {
|
||||
const jobId = req.params.jobId;
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {NextFunction, Request, Response} from 'express';
|
||||
import {redis} from '../database/redis.js';
|
||||
import {NotFound} from '../exceptions/index.js';
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {isAuthenticated} from '../middleware/auth.js';
|
||||
import {isAuthenticated, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {DomainService} from '../services/DomainService.js';
|
||||
import {Keys} from '../services/keys.js';
|
||||
import {prisma} from '../database/prisma.js';
|
||||
@@ -17,7 +17,7 @@ export class Domains {
|
||||
* Get all domains for a project
|
||||
*/
|
||||
@Get('project/:projectId')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getProjectDomains(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -44,7 +44,7 @@ export class Domains {
|
||||
* Add a new domain to a project
|
||||
*/
|
||||
@Post('')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async addDomain(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -104,7 +104,7 @@ export class Domains {
|
||||
* Check verification status for a domain
|
||||
*/
|
||||
@Get(':id/verify')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async checkVerification(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -141,7 +141,7 @@ export class Domains {
|
||||
* Remove a domain from a project
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async removeDomain(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {NextFunction, Request, Response} from 'express';
|
||||
import signale from 'signale';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {EventService} from '../services/EventService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -14,7 +14,7 @@ export class Events {
|
||||
* Track a custom event (can trigger workflows)
|
||||
*/
|
||||
@Post('track')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async track(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -34,7 +34,7 @@ export class Events {
|
||||
* List events for the project
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -51,7 +51,7 @@ export class Events {
|
||||
* Get event statistics
|
||||
*/
|
||||
@Get('stats')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async stats(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -68,7 +68,7 @@ export class Events {
|
||||
* Get events for a specific contact
|
||||
*/
|
||||
@Get('contact/:contactId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getContactEvents(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -89,7 +89,7 @@ export class Events {
|
||||
* Get unique event names for the project
|
||||
*/
|
||||
@Get('names')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getEventNames(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -105,7 +105,7 @@ export class Events {
|
||||
* Returns information about where the event is used and whether it can be safely deleted
|
||||
*/
|
||||
@Get(':eventName/usage')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getEventUsage(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -132,7 +132,7 @@ export class Events {
|
||||
* Only works if the event is not used in any segments or workflows
|
||||
*/
|
||||
@Delete(':eventName')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async deleteEvent(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -85,6 +85,7 @@ export class Github {
|
||||
data: {
|
||||
email,
|
||||
type: 'GITHUB_OAUTH',
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
isNewUser = true;
|
||||
|
||||
@@ -75,6 +75,7 @@ export class Google {
|
||||
data: {
|
||||
email,
|
||||
type: 'GOOGLE_OAUTH',
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
isNewUser = true;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {MembershipSchemas, UtilitySchemas} from '@plunk/shared';
|
||||
import {prisma} from '../database/prisma.js';
|
||||
import {HttpException} from '../exceptions/index.js';
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {SecurityService} from '../services/SecurityService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -16,7 +16,7 @@ export class Projects {
|
||||
* GET /projects/:id/setup-state
|
||||
*/
|
||||
@Get(':id/setup-state')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async getSetupState(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -89,7 +89,7 @@ export class Projects {
|
||||
* GET /projects/:id/security
|
||||
*/
|
||||
@Get(':id/security')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async getSecurityMetrics(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -121,7 +121,7 @@ export class Projects {
|
||||
* GET /projects/:id/members
|
||||
*/
|
||||
@Get(':id/members')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async getMembers(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -170,7 +170,7 @@ export class Projects {
|
||||
* Body: { email: string, role?: 'ADMIN' | 'MEMBER' }
|
||||
*/
|
||||
@Post(':id/members')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async addMember(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -253,7 +253,7 @@ export class Projects {
|
||||
* Body: { role: 'ADMIN' | 'MEMBER' }
|
||||
*/
|
||||
@Patch(':id/members/:userId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async updateMemberRole(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -345,7 +345,7 @@ export class Projects {
|
||||
* DELETE /projects/:id/members/:userId
|
||||
*/
|
||||
@Delete(':id/members/:userId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
private async removeMember(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -2,7 +2,7 @@ import {Controller, Delete, Get, Middleware, Patch, Post} from '@overnightjs/cor
|
||||
import type {NextFunction, Request, Response} from 'express';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {SegmentService} from '../services/SegmentService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -13,7 +13,7 @@ export class Segments {
|
||||
* List all segments for the authenticated project
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -28,7 +28,7 @@ export class Segments {
|
||||
* Get a specific segment by ID with member count
|
||||
*/
|
||||
@Get(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -48,7 +48,7 @@ export class Segments {
|
||||
* Get contacts that match a segment's filters
|
||||
*/
|
||||
@Get(':id/contacts')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getContacts(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -70,7 +70,7 @@ export class Segments {
|
||||
* Create a new segment
|
||||
*/
|
||||
@Post('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -99,7 +99,7 @@ export class Segments {
|
||||
* Update a segment
|
||||
*/
|
||||
@Patch(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -129,7 +129,7 @@ export class Segments {
|
||||
* Delete a segment
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -149,7 +149,7 @@ export class Segments {
|
||||
* Recompute segment membership for all contacts
|
||||
*/
|
||||
@Post(':id/compute')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async compute(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -169,7 +169,7 @@ export class Segments {
|
||||
* Refresh segment member count
|
||||
*/
|
||||
@Post(':id/refresh')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async refresh(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -3,7 +3,7 @@ import {TemplateType} from '@plunk/db';
|
||||
import type {NextFunction, Request, Response} from 'express';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {DomainService} from '../services/DomainService.js';
|
||||
import {TemplateService} from '../services/TemplateService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
@@ -15,7 +15,7 @@ export class Templates {
|
||||
* List all templates for the authenticated project
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -34,7 +34,7 @@ export class Templates {
|
||||
* Get a specific template by ID
|
||||
*/
|
||||
@Get(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -54,7 +54,7 @@ export class Templates {
|
||||
* Create a new template
|
||||
*/
|
||||
@Post('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -98,7 +98,7 @@ export class Templates {
|
||||
* Update a template
|
||||
*/
|
||||
@Patch(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -133,7 +133,7 @@ export class Templates {
|
||||
* Delete a template
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -153,7 +153,7 @@ export class Templates {
|
||||
* Duplicate a template
|
||||
*/
|
||||
@Post(':id/duplicate')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async duplicate(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -173,7 +173,7 @@ export class Templates {
|
||||
* Get template usage statistics
|
||||
*/
|
||||
@Get(':id/usage')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getUsage(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -4,7 +4,7 @@ import multer from 'multer';
|
||||
import signale from 'signale';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import * as S3Service from '../services/S3Service.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -33,7 +33,7 @@ export class Uploads {
|
||||
* Upload an image file to S3/Minio
|
||||
*/
|
||||
@Post('image')
|
||||
@Middleware([requireAuth, upload.single('image')])
|
||||
@Middleware([requireAuth, requireEmailVerified, upload.single('image')])
|
||||
@CatchAsync
|
||||
public async uploadImage(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {stripe} from '../app/stripe.js';
|
||||
import {prisma} from '../database/prisma.js';
|
||||
import {ErrorCode, HttpException, NotAuthenticated, NotFound} from '../exceptions/index.js';
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {isAuthenticated} from '../middleware/auth.js';
|
||||
import {isAuthenticated, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {BillingLimitService} from '../services/BillingLimitService.js';
|
||||
import {NtfyService} from '../services/NtfyService.js';
|
||||
import {SecurityService} from '../services/SecurityService.js';
|
||||
@@ -20,7 +20,7 @@ import signale from 'signale';
|
||||
@Controller('users')
|
||||
export class Users {
|
||||
@Get('@me')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async me(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -39,7 +39,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Get('@me/projects')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async meProjects(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -54,7 +54,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Post('@me/projects')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async createProject(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -101,7 +101,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Patch('@me/projects/:id')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async updateProject(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -133,7 +133,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Post('@me/projects/:id/regenerate-keys')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async regenerateProjectKeys(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -185,7 +185,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Post('@me/projects/:id/checkout')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async createCheckoutSession(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -264,7 +264,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Post('@me/projects/:id/billing-portal')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async createBillingPortalSession(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -314,7 +314,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Get('@me/projects/:id/billing-limits')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getBillingLimits(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -347,7 +347,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Put('@me/projects/:id/billing-limits')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async updateBillingLimits(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -426,7 +426,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Get('@me/projects/:id/billing-consumption')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getBillingConsumption(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -552,7 +552,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Get('@me/projects/:id/billing-invoices')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getBillingInvoices(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -640,7 +640,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Get('@me/projects/:id/security')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getSecurityHealth(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -673,7 +673,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Post('@me/projects/:id/reset')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async resetProject(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -759,7 +759,7 @@ export class Users {
|
||||
}
|
||||
|
||||
@Delete('@me/projects/:id')
|
||||
@Middleware([isAuthenticated])
|
||||
@Middleware([isAuthenticated, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async deleteProject(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {NextFunction, Request, Response} from 'express';
|
||||
import signale from 'signale';
|
||||
|
||||
import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
||||
import {WorkflowService} from '../services/WorkflowService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Workflows {
|
||||
* List all workflows for the authenticated project
|
||||
*/
|
||||
@Get('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -35,7 +35,7 @@ export class Workflows {
|
||||
* NOTE: This must be defined BEFORE the :id route to avoid conflicts
|
||||
*/
|
||||
@Get('fields')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getAvailableFields(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -58,7 +58,7 @@ export class Workflows {
|
||||
* Get a specific workflow with all steps and transitions
|
||||
*/
|
||||
@Get(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -78,7 +78,7 @@ export class Workflows {
|
||||
* Create a new workflow
|
||||
*/
|
||||
@Post('')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -108,7 +108,7 @@ export class Workflows {
|
||||
* Update a workflow
|
||||
*/
|
||||
@Patch(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -136,7 +136,7 @@ export class Workflows {
|
||||
* Delete a workflow
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -156,7 +156,7 @@ export class Workflows {
|
||||
* Add a step to a workflow
|
||||
*/
|
||||
@Post(':id/steps')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async addStep(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -188,7 +188,7 @@ export class Workflows {
|
||||
* Update a workflow step
|
||||
*/
|
||||
@Patch(':id/steps/:stepId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async updateStep(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -215,7 +215,7 @@ export class Workflows {
|
||||
* Delete a workflow step
|
||||
*/
|
||||
@Delete(':id/steps/:stepId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async deleteStep(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -236,7 +236,7 @@ export class Workflows {
|
||||
* Create a transition between steps
|
||||
*/
|
||||
@Post(':id/transitions')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async createTransition(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -266,7 +266,7 @@ export class Workflows {
|
||||
* Delete a transition
|
||||
*/
|
||||
@Delete(':id/transitions/:transitionId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async deleteTransition(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -287,7 +287,7 @@ export class Workflows {
|
||||
* Start a workflow execution for a contact
|
||||
*/
|
||||
@Post(':id/executions')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async startExecution(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -312,7 +312,7 @@ export class Workflows {
|
||||
* List executions for a workflow
|
||||
*/
|
||||
@Get(':id/executions')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async listExecutions(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -335,7 +335,7 @@ export class Workflows {
|
||||
* Get a specific execution with details
|
||||
*/
|
||||
@Get(':id/executions/:executionId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async getExecution(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -356,7 +356,7 @@ export class Workflows {
|
||||
* Cancel a workflow execution
|
||||
*/
|
||||
@Delete(':id/executions/:executionId')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async cancelExecution(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
@@ -377,7 +377,7 @@ export class Workflows {
|
||||
* Cancel all active executions for a workflow
|
||||
*/
|
||||
@Post(':id/executions/cancel-all')
|
||||
@Middleware([requireAuth])
|
||||
@Middleware([requireAuth, requireEmailVerified])
|
||||
@CatchAsync
|
||||
public async cancelAllExecutions(req: Request, res: Response, _next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
Reference in New Issue
Block a user