105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import type { NextApiRequest, NextApiResponse } from "next";
|
|
import { z } from "zod";
|
|
|
|
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
|
import logger from "@calcom/lib/logger";
|
|
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { decodeOAuthState } from "../../_utils/decodeOAuthState";
|
|
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
|
import { LARK_HOST } from "../common";
|
|
import { getAppAccessToken } from "../lib/AppAccessToken";
|
|
import type { LarkAuthCredentials } from "../types/LarkCalendar";
|
|
|
|
const log = logger.getChildLogger({ prefix: [`[[lark/api/callback]`] });
|
|
|
|
const callbackQuerySchema = z.object({
|
|
code: z.string().min(1),
|
|
});
|
|
|
|
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
|
const { code } = callbackQuerySchema.parse(req.query);
|
|
const state = decodeOAuthState(req);
|
|
|
|
try {
|
|
const appAccessToken = await getAppAccessToken();
|
|
|
|
const response = await fetch(`https://${LARK_HOST}/open-apis/authen/v1/access_token`, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer " + appAccessToken,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
grant_type: "authorization_code",
|
|
code,
|
|
}),
|
|
});
|
|
|
|
const responseBody = await response.json();
|
|
|
|
if (!response.ok || responseBody.code !== 0) {
|
|
log.error("get user_access_token failed with none 0 code", responseBody);
|
|
return res.redirect("/apps/installed?error=" + JSON.stringify(responseBody));
|
|
}
|
|
|
|
const key: LarkAuthCredentials = {
|
|
expiry_date: Math.round(+new Date() / 1000 + responseBody.data.expires_in),
|
|
access_token: responseBody.data.access_token,
|
|
refresh_token: responseBody.data.refresh_token,
|
|
refresh_expires_date: Math.round(+new Date() / 1000 + responseBody.data.refresh_expires_in),
|
|
};
|
|
|
|
/**
|
|
* A user can have only one pair of refresh_token and access_token effective
|
|
* at same time. Newly created refresh_token and access_token will invalidate
|
|
* older ones. So we need to keep only one lark credential per user only.
|
|
* However, a user may connect many times, since both userId and type are
|
|
* not unique in schema, so we have to use credential id as index for looking
|
|
* for the unique access_token token. In this case, id does not exist before created, so we cannot use credential id (which may not exist) as where statement
|
|
*/
|
|
const currentCredential = await prisma.credential.findFirst({
|
|
where: {
|
|
userId: req.session?.user.id,
|
|
type: "lark_calendar",
|
|
},
|
|
});
|
|
|
|
if (!currentCredential) {
|
|
await prisma.credential.create({
|
|
data: {
|
|
type: "lark_calendar",
|
|
key,
|
|
userId: req.session?.user.id,
|
|
appId: "lark-calendar",
|
|
},
|
|
});
|
|
} else {
|
|
await prisma.credential.update({
|
|
data: {
|
|
type: "lark_calendar",
|
|
key,
|
|
userId: req.session?.user.id,
|
|
appId: "lark-calendar",
|
|
},
|
|
where: {
|
|
id: currentCredential.id,
|
|
},
|
|
});
|
|
}
|
|
|
|
res.redirect(
|
|
getSafeRedirectUrl(state?.returnTo) ??
|
|
getInstalledAppPath({ variant: "calendar", slug: "lark-calendar" })
|
|
);
|
|
} catch (error) {
|
|
log.error("handle callback error", error);
|
|
res.redirect(state?.returnTo ?? "/apps/installed");
|
|
}
|
|
}
|
|
|
|
export default defaultHandler({
|
|
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
|
|
});
|