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

View File

@@ -1,3 +1,4 @@
import { Types } from 'mongoose';
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { MembershipOrg, Organization, User } from '../../models';
@@ -139,7 +140,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: invitee?.publicKey ? ACCEPTED : INVITED
status: INVITED
}).save();
}
} else {
@@ -164,6 +165,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
const organization = await Organization.findOne({ _id: organizationId });
if (organization) {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail,
@@ -179,6 +181,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviterEmail: req.user.email,
organizationName: organization.name,
email: inviteeEmail,
organizationId: organization._id.toString(),
token,
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) => {
let user, token;
try {
const { email, code } = req.body;
const {
email,
organizationId,
code
} = req.body;
user = await User.findOne({ email }).select('+publicKey');
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
status: INVITED,
organization: new Types.ObjectId(organizationId)
});
if (!membershipOrg)

View File

@@ -85,7 +85,7 @@ export const createOrganization = async (req: Request, res: Response) => {
export const getOrganization = async (req: Request, res: Response) => {
let organization;
try {
organization = req.membershipOrg.organization;
organization = req.organization
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@@ -323,14 +323,14 @@ export const createOrganizationPortalSession = async (
// check if there is a payment method on file
const paymentMethods = await stripe.paymentMethods.list({
customer: req.membershipOrg.organization.customerId,
customer: req.organization.customerId,
type: 'card'
});
if (paymentMethods.data.length < 1) {
// case: no payment method on file
session = await stripe.checkout.sessions.create({
customer: req.membershipOrg.organization.customerId,
customer: req.organization.customerId,
mode: 'setup',
payment_method_types: ['card'],
success_url: (await getSiteURL()) + '/dashboard',
@@ -338,7 +338,7 @@ export const createOrganizationPortalSession = async (
});
} else {
session = await stripe.billingPortal.sessions.create({
customer: req.membershipOrg.organization.customerId,
customer: req.organization.customerId,
return_url: (await getSiteURL()) + '/dashboard'
});
}
@@ -370,7 +370,7 @@ export const getOrganizationSubscriptions = async (
});
subscriptions = await stripe.subscriptions.list({
customer: req.membershipOrg.organization.customerId
customer: req.organization.customerId
});
} catch (err) {
Sentry.setUser({ email: req.user.email });

View File

@@ -19,6 +19,7 @@ router.post(
router.post(
'/verify',
body('email').exists().trim().notEmpty(),
body('organizationId').exists().trim().notEmpty(),
body('code').exists().trim().notEmpty(),
validateRequest,
membershipOrgController.verifyUserToOrganization

View File

@@ -9,7 +9,7 @@
<body>
<h2>Join your organization on Infisical</h2>
<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>
<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>

View File

@@ -1,22 +1,25 @@
interface Props {
email: string;
code: string;
organizationId: string;
}
/**
* This route verifies the signup invite link
* @param {object} obj
* @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
* @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',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
organizationId,
code
})
});

View File

@@ -51,6 +51,7 @@ export default function SignupInvite() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const token = parsedUrl.token as string;
const organizationId = parsedUrl.organization_id as string;
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.
@@ -190,7 +191,8 @@ export default function SignupInvite() {
onButtonPressed={async () => {
const response = await verifySignupInvite({
email,
code: token
code: token,
organizationId
});
if (response.status === 200) {
const res = await response.json();