+7








225313e391
* plain custom desin * No display on card * dynamic plain chat component and hmac hash * re-route users to plain.com chat instead of intercom chat when going to /support * provider errors * yarn lock fix * plain chat removed unneeded hmac * fix ts error * error handling improved * remove intercome provider from app-dir * Create getting-started.mdx (#18342) * Delete help directory (#18343) * chore: moved docs/help to /help (#18345) * chore: Delete unused guides directory (#18346) * feat: booking filters (#18303) Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> * chore: Remove `HeadSeo` components where no longer needed + improve app router metadata logic (#18348) * remove HeadSeo for already migrated pages and refactor prepareMetadata * create _generateMetadataWithoutImage and refactor _generateMetadata * chore: app router - /bookings status page (#18183) * chore: app router - /bookings page * remove env vars * fix * Update middleware.ts * revert unneeded change * refactor for the better * fix * cache i18n instances (#18309) * feat: virtual queues tab in insights (#18260) Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: sean-brydon <sean@cal.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Omar López <zomars@me.com> * feat: update translations via @replexica (#18361) Co-authored-by: Replexica <support@replexica.com> * update OOO e2e tests to remove flakiness (#18367) * fix: metadata is overwritten for child managed eventType when updating parent (#18059) * fix: metadata is overwirten * Update * type error * Update * fix test * fix type error * chore: added routing support link (#18369) * Added routing form support link * small change * Type fix --------- Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> * chore: refactor handling logic for embeds in app router (#18362) * refactor handling logic for embeds in app router * fix type checks * add test for withEmbedSsrAppDir * fix * review changes * yarn lock --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Calcom Bot <109866826+calcom-bot@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Co-authored-by: Benny Joo <sldisek783@gmail.com> Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: sean-brydon <sean@cal.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Omar López <zomars@me.com> Co-authored-by: Replexica <support@replexica.com> Co-authored-by: Vijay <vijayraghav22@gmail.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
256 lines
6.9 KiB
TypeScript
256 lines
6.9 KiB
TypeScript
import { cardExamples } from "@pages/api/plain/example-cards";
|
|
import { createHmac } from "crypto";
|
|
import { headers } from "next/headers";
|
|
import { NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
|
|
import { apiRouteMiddleware } from "@calcom/lib/server/apiRouteMiddleware";
|
|
import { UserRepository } from "@calcom/lib/server/repository/user";
|
|
import { userMetadata } from "@calcom/prisma/zod-utils";
|
|
|
|
const inputSchema = z.object({
|
|
customer: z.object({
|
|
email: z.string().email(),
|
|
username: z.string().optional(),
|
|
timeZone: z.string().optional(),
|
|
emailVerified: z.boolean().optional(),
|
|
identityProvider: z.string().optional(),
|
|
twoFactorEnabled: z.boolean().optional(),
|
|
}),
|
|
cardKeys: z.array(z.string()),
|
|
});
|
|
|
|
export async function handler(request: Request) {
|
|
const headersList = headers();
|
|
const requestBody = await request.json();
|
|
|
|
// HMAC verification
|
|
const incomingSignature = headersList.get("plain-request-signature");
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
const expectedSignature = createHmac("sha-256", process.env.PLAIN_HMAC_SECRET_KEY!)
|
|
.update(JSON.stringify(requestBody))
|
|
.digest("hex");
|
|
|
|
if (incomingSignature !== expectedSignature) {
|
|
return new Response("Forbidden", { status: 403 });
|
|
}
|
|
|
|
// Validate request body
|
|
const { cardKeys, customer } = inputSchema.parse(requestBody);
|
|
|
|
const user = await UserRepository.findByEmail({ email: customer.email });
|
|
|
|
if (!user) {
|
|
return NextResponse.json({
|
|
cards: [
|
|
{
|
|
key: "customer-card",
|
|
timeToLiveSeconds: null,
|
|
components: [
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "Email",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentText: {
|
|
text: customer.email || "Unknown",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "Email Verified?",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentBadge: {
|
|
badgeLabel: customer.emailVerified ? "Yes" : "No",
|
|
badgeColor: customer.emailVerified ? "GREEN" : "RED",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "Username",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentText: {
|
|
text: customer.username || "Unknown",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "User ID",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentText: {
|
|
text: "Unknown",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "Time Zone",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentText: {
|
|
text: customer.timeZone || "Unknown",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "Two Factor Enabled?",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentBadge: {
|
|
badgeLabel: customer.twoFactorEnabled ? "Yes" : "No",
|
|
badgeColor: customer.twoFactorEnabled ? "GREEN" : "RED",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
componentSpacer: {
|
|
spacerSize: "M",
|
|
},
|
|
},
|
|
{
|
|
componentRow: {
|
|
rowMainContent: [
|
|
{
|
|
componentText: {
|
|
text: "Identity Provider",
|
|
textColor: "MUTED",
|
|
},
|
|
},
|
|
],
|
|
rowAsideContent: [
|
|
{
|
|
componentText: {
|
|
text: customer.identityProvider || "Unknown",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
// Parse user metadata
|
|
const parsedMetadata = userMetadata.parse(user.metadata);
|
|
|
|
const cards = await Promise.all(
|
|
cardExamples.map(async (cardFn) => {
|
|
return cardFn(
|
|
user.email,
|
|
user.id.toString(),
|
|
user.username || "Unknown",
|
|
user.timeZone,
|
|
user.emailVerified,
|
|
user.twoFactorEnabled
|
|
);
|
|
})
|
|
);
|
|
|
|
const filteredCards = cards.filter((card) => {
|
|
return cardKeys.length === 0 || cardKeys.includes(card.key);
|
|
});
|
|
|
|
return NextResponse.json({
|
|
cards: filteredCards,
|
|
user: {
|
|
...user,
|
|
metadata: parsedMetadata,
|
|
},
|
|
});
|
|
}
|
|
|
|
export const POST = apiRouteMiddleware(handler);
|