Compare commits
9 Commits
main
...
oauth-redi
Author | SHA1 | Date | |
---|---|---|---|
|
bbf3000cf5 | ||
|
f4212fc48c | ||
|
c996b8f39f | ||
|
3f5e87b160 | ||
|
1085ae6092 | ||
|
431ab835cc | ||
|
40ad87c120 | ||
|
5d2decf81a | ||
|
b2dd5f2e70 |
|
@ -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 }),
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
];
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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" })
|
||||
|
|
|
@ -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" })
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -7,6 +7,8 @@ import type { ButtonProps } from "@calcom/ui";
|
|||
|
||||
export type IntegrationOAuthCallbackState = {
|
||||
returnTo: string;
|
||||
onErrorReturnTo: string;
|
||||
fromApp: boolean;
|
||||
installGoogleVideo?: boolean;
|
||||
teamId?: number;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user