Patch organization invitation emails expiring for existing users and billing logic affected by missing organization populate call

This commit is contained in:
Tuan Dang
2023-04-28 17:57:50 +03:00
parent 330968c7af
commit b9ae224aef
6 changed files with 26 additions and 12 deletions
@@ -1,3 +1,4 @@
import { Types } from 'mongoose';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { MembershipOrg, Organization, User } from '../../models'; import { MembershipOrg, Organization, User } from '../../models';
@@ -139,7 +140,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviteEmail: inviteeEmail, inviteEmail: inviteeEmail,
organization: organizationId, organization: organizationId,
role: MEMBER, role: MEMBER,
status: invitee?.publicKey ? ACCEPTED : INVITED status: INVITED
}).save(); }).save();
} }
} else { } else {
@@ -164,6 +165,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
const organization = await Organization.findOne({ _id: organizationId }); const organization = await Organization.findOne({ _id: organizationId });
if (organization) { if (organization) {
const token = await TokenService.createToken({ const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION, type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail, email: inviteeEmail,
@@ -179,6 +181,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviterEmail: req.user.email, inviterEmail: req.user.email,
organizationName: organization.name, organizationName: organization.name,
email: inviteeEmail, email: inviteeEmail,
organizationId: organization._id.toString(),
token, token,
callback_url: (await getSiteURL()) + '/signupinvite' callback_url: (await getSiteURL()) + '/signupinvite'
} }
@@ -214,13 +217,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
export const verifyUserToOrganization = async (req: Request, res: Response) => { export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user, token; let user, token;
try { try {
const { email, code } = req.body; const {
email,
organizationId,
code
} = req.body;
user = await User.findOne({ email }).select('+publicKey'); user = await User.findOne({ email }).select('+publicKey');
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
organization: new Types.ObjectId(organizationId)
}); });
if (!membershipOrg) if (!membershipOrg)
@@ -85,7 +85,7 @@ export const createOrganization = async (req: Request, res: Response) => {
export const getOrganization = async (req: Request, res: Response) => { export const getOrganization = async (req: Request, res: Response) => {
let organization; let organization;
try { try {
organization = req.membershipOrg.organization; organization = req.organization
} catch (err) { } catch (err) {
Sentry.setUser({ email: req.user.email }); Sentry.setUser({ email: req.user.email });
Sentry.captureException(err); Sentry.captureException(err);
@@ -323,14 +323,14 @@ export const createOrganizationPortalSession = async (
// check if there is a payment method on file // check if there is a payment method on file
const paymentMethods = await stripe.paymentMethods.list({ const paymentMethods = await stripe.paymentMethods.list({
customer: req.membershipOrg.organization.customerId, customer: req.organization.customerId,
type: 'card' type: 'card'
}); });
if (paymentMethods.data.length < 1) { if (paymentMethods.data.length < 1) {
// case: no payment method on file // case: no payment method on file
session = await stripe.checkout.sessions.create({ session = await stripe.checkout.sessions.create({
customer: req.membershipOrg.organization.customerId, customer: req.organization.customerId,
mode: 'setup', mode: 'setup',
payment_method_types: ['card'], payment_method_types: ['card'],
success_url: (await getSiteURL()) + '/dashboard', success_url: (await getSiteURL()) + '/dashboard',
@@ -338,7 +338,7 @@ export const createOrganizationPortalSession = async (
}); });
} else { } else {
session = await stripe.billingPortal.sessions.create({ session = await stripe.billingPortal.sessions.create({
customer: req.membershipOrg.organization.customerId, customer: req.organization.customerId,
return_url: (await getSiteURL()) + '/dashboard' return_url: (await getSiteURL()) + '/dashboard'
}); });
} }
@@ -370,7 +370,7 @@ export const getOrganizationSubscriptions = async (
}); });
subscriptions = await stripe.subscriptions.list({ subscriptions = await stripe.subscriptions.list({
customer: req.membershipOrg.organization.customerId customer: req.organization.customerId
}); });
} catch (err) { } catch (err) {
Sentry.setUser({ email: req.user.email }); Sentry.setUser({ email: req.user.email });
+1
View File
@@ -19,6 +19,7 @@ router.post(
router.post( router.post(
'/verify', '/verify',
body('email').exists().trim().notEmpty(), body('email').exists().trim().notEmpty(),
body('organizationId').exists().trim().notEmpty(),
body('code').exists().trim().notEmpty(), body('code').exists().trim().notEmpty(),
validateRequest, validateRequest,
membershipOrgController.verifyUserToOrganization membershipOrgController.verifyUserToOrganization
@@ -9,7 +9,7 @@
<body> <body>
<h2>Join your organization on Infisical</h2> <h2>Join your organization on Infisical</h2>
<p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p> <p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p>
<a href="{{callback_url}}?token={{token}}&to={{email}}">Join now</a> <a href="{{callback_url}}?token={{token}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
<h3>What is Infisical?</h3> <h3>What is Infisical?</h3>
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p> <p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
</body> </body>
@@ -1,22 +1,25 @@
interface Props { interface Props {
email: string; email: string;
code: string; code: string;
organizationId: string;
} }
/** /**
* This route verifies the signup invite link * This route verifies the signup invite link
* @param {object} obj * @param {object} obj
* @param {string} obj.email - email that a user is trying to verify * @param {string} obj.email - email that a user is trying to verify
* @param {string} obj.organizationId - id of organization that a user is trying to verify for
* @param {string} obj.code - code that a user received to the abovementioned email * @param {string} obj.code - code that a user received to the abovementioned email
* @returns * @returns
*/ */
const verifySignupInvite = ({ email, code }: Props) => fetch('/api/v1/invite-org/verify', { const verifySignupInvite = ({ email, organizationId, code }: Props) => fetch('/api/v1/invite-org/verify', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
email, email,
organizationId,
code code
}) })
}); });
+3 -1
View File
@@ -51,6 +51,7 @@ export default function SignupInvite() {
const router = useRouter(); const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]); const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const token = parsedUrl.token as string; const token = parsedUrl.token as string;
const organizationId = parsedUrl.organization_id as string;
const email = (parsedUrl.to as string)?.replace(' ', '+').trim(); const email = (parsedUrl.to as string)?.replace(' ', '+').trim();
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria. // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
@@ -190,7 +191,8 @@ export default function SignupInvite() {
onButtonPressed={async () => { onButtonPressed={async () => {
const response = await verifySignupInvite({ const response = await verifySignupInvite({
email, email,
code: token code: token,
organizationId
}); });
if (response.status === 200) { if (response.status === 200) {
const res = await response.json(); const res = await response.json();