Compare commits

...

9 Commits

Author SHA1 Message Date
Joe Au-Yeung bbf3000cf5 Stripe go back to previous page on cancel 2024-01-12 15:08:04 -05:00
Joe Au-Yeung f4212fc48c Type fix 2024-01-12 14:03:40 -05:00
Joe Au-Yeung c996b8f39f
Merge branch 'main' into oauth-redirect 2024-01-12 13:46:25 -05:00
Joe Au-Yeung 3f5e87b160 When not enough permissions given, invalidate the credential created 2024-01-11 18:09:16 -05:00
Joe Au-Yeung 1085ae6092 On invalid permissions, return to previous page 2024-01-11 17:47:11 -05:00
Joe Au-Yeung 431ab835cc
Merge branch 'main' into oauth-redirect 2024-01-11 16:46:21 -05:00
Joe Au-Yeung 40ad87c120
Merge branch 'main' into oauth-redirect 2024-01-11 16:45:08 -05:00
Joe Au-Yeung 5d2decf81a If no code return to previous page 2024-01-11 16:00:05 -05:00
Joe Au-Yeung b2dd5f2e70 Get `onErrorReturnTo` URL 2024-01-11 15:50:21 -05:00
7 changed files with 66 additions and 5 deletions

View File

@ -1,5 +1,6 @@
import type { UseMutationOptions } from "@tanstack/react-query";
import { useMutation } from "@tanstack/react-query";
import { useRouter } from "next/router";
import type { IntegrationOAuthCallbackState } from "@calcom/app-store/types";
import { WEBAPP_URL } from "@calcom/lib/constants";
@ -28,10 +29,21 @@ type UseAddAppMutationOptions = CustomUseMutationOptions & {
function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMutationOptions) {
const { returnTo, ...options } = allOptions || {};
const router = useRouter();
const onErrorReturnTo = `${WEBAPP_URL}${router.asPath}`;
const mutation = useMutation<
AddAppMutationData,
Error,
{ type?: App["type"]; variant?: string; slug?: string; isOmniInstall?: boolean; teamId?: number } | ""
| {
type?: App["type"];
variant?: string;
slug?: string;
isOmniInstall?: boolean;
teamId?: number;
onErrorReturnTo?: string;
}
| ""
>(async (variables) => {
let type: string | null | undefined;
let isOmniInstall;
@ -57,6 +69,8 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
{ variant: variables && variables.variant, slug: variables && variables.slug },
location.search
),
onErrorReturnTo,
fromApp: true,
...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
...(teamId && { teamId }),
};

View File

@ -8,7 +8,7 @@ import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
const scopes = [
export const scopes = [
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/calendar.events",
];

View File

@ -10,6 +10,7 @@ import prisma from "@calcom/prisma";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
import { scopes } from "./add";
let client_id = "";
let client_secret = "";
@ -19,6 +20,13 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
const state = decodeOAuthState(req);
if (typeof code !== "string") {
if (state?.onErrorReturnTo) {
res.redirect(
getSafeRedirectUrl(state.onErrorReturnTo) ??
getSafeRedirectUrl(state?.returnTo) ??
`${CAL_URL}/apps/installed`
);
}
throw new HttpError({ statusCode: 400, message: "`code` must be a string" });
}
@ -36,18 +44,35 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
let key = "";
let key;
let invalid = false;
if (code) {
const token = await oAuth2Client.getToken(code);
key = token.res?.data;
// Check that the has granted all permissions
const grantedScopes = key.scope;
for (const scope of scopes) {
if (!grantedScopes.includes(scope)) {
if (!state?.fromApp) {
throw new HttpError({
statusCode: 400,
message: "You must grant all permissions to use this integration",
});
} else {
invalid = true;
}
}
}
const credential = await prisma.credential.create({
data: {
type: "google_calendar",
key,
userId: req.session.user.id,
appId: "google-calendar",
invalid,
},
});

View File

@ -17,8 +17,16 @@ let client_secret = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code } = req.query;
const state = decodeOAuthState(req);
if (typeof code !== "string") {
if (state?.onErrorReturnTo) {
res.redirect(
getSafeRedirectUrl(state.onErrorReturnTo) ??
getSafeRedirectUrl(state?.returnTo) ??
`${WEBAPP_URL}/apps/installed`
);
}
res.status(400).json({ message: "No code returned" });
return;
}
@ -120,7 +128,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
const state = decodeOAuthState(req);
return res.redirect(
getSafeRedirectUrl(state?.returnTo) ??
getInstalledAppPath({ variant: "calendar", slug: "office365-calendar" })

View File

@ -16,8 +16,16 @@ let client_secret = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code } = req.query;
const state = decodeOAuthState(req);
if (typeof code !== "string") {
if (state?.onErrorReturnTo) {
res.redirect(
getSafeRedirectUrl(state.onErrorReturnTo) ??
getSafeRedirectUrl(state?.returnTo) ??
`${WEBAPP_URL}/apps/installed`
);
}
res.status(400).json({ message: "No code returned" });
return;
}
@ -95,7 +103,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await createOAuthAppCredential({ appId: "msteams", type: "office365_video" }, responseBody, req);
const state = decodeOAuthState(req);
return res.redirect(
getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "conferencing", slug: "msteams" })
);

View File

@ -4,6 +4,7 @@ import { stringify } from "querystring";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential";
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
import type { StripeData } from "../lib/server";
import stripe from "../lib/server";
@ -19,8 +20,13 @@ function getReturnToValueFromQueryState(req: NextApiRequest) {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code, error, error_description } = req.query;
const state = decodeOAuthState(req);
if (error) {
// User cancels flow
if (error === "access_denied") {
state?.onErrorReturnTo ? res.redirect(state.onErrorReturnTo) : res.redirect("/apps/installed/payment");
}
const query = stringify({ error, error_description });
res.redirect(`/apps/installed?${query}`);
return;

View File

@ -7,6 +7,8 @@ import type { ButtonProps } from "@calcom/ui";
export type IntegrationOAuthCallbackState = {
returnTo: string;
onErrorReturnTo: string;
fromApp: boolean;
installGoogleVideo?: boolean;
teamId?: number;
};