UNPKG

next-integrate

Version:

An authentication library for making integrations in your Next.js Web Application

1,380 lines (1,359 loc) 29.5 kB
// src/index.ts import { NextRequest } from "next/server"; // src/utils/qs.ts function qs(obj) { const params = new URLSearchParams(); Object.keys(obj).forEach((key) => { const value = obj[key]; if (Array.isArray(value)) { value.forEach((val) => params.append(key, val)); } else { params.append(key, value); } }); return params.toString(); } // src/providers/accuranker.ts function getAccurankerAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://app.accuranker.com/oauth/authorize/?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/accuranker", ...props })}`; } async function getAccurankerAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig({ code, redirect_uri: base_url + "/api/auth/integration/accuranker", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "accuranker", ...tokens }); return { provider: "accuranker", ...tokens }; } function getTokenReqConfig(body) { return { url: "https://app.accuranker.com/oauth/token/", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/click-up.ts function getClickUpAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://app.clickup.com/api?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/click-up", ...props })}`; } async function getClickUpAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig2({ code, redirect_uri: base_url + "/api/auth/integration/click-up", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "click-up", ...tokens }); return { provider: "click-up", ...tokens }; } function getTokenReqConfig2(body) { return { url: "https://api.clickup.com/api/v2/oauth/token", method: "POST", headers: { accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/discord.ts function getDiscordAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://discord.com/oauth2/authorize?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/discord", ...props })}`; } async function getDiscordAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig3({ code, redirect_uri: base_url + "/api/auth/integration/discord", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "discord", ...tokens }); return { provider: "discord", ...tokens }; } function getTokenReqConfig3(body) { return { url: "https://discord.com/api/oauth2/token", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/facebook.ts function getFacebookAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://www.facebook.com/v20.0/dialog/oauth?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/facebook", ...props })}`; } async function getFacebookAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig4({ code, redirect_uri: base_url + "/api/auth/integration/facebook", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "facebook", ...tokens }); return { provider: "facebook", ...tokens }; } function getTokenReqConfig4(body) { return { url: "https://graph.facebook.com/v20.0/oauth/access_token?", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/github.ts function getGithubAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://github.com/login/oauth/authorize?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/github", ...props })}`; } async function getGithubAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig5({ code, redirect_uri: base_url + "/api/auth/integration/github", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "github", ...tokens }); return { provider: "github", ...tokens }; } function getTokenReqConfig5(body) { return { url: "https://github.com/login/oauth/access_token", method: "POST", headers: { Accept: "application/json" }, body: new URLSearchParams(body) }; } // src/providers/google.ts function getGoogleAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://accounts.google.com/o/oauth2/v2/auth?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/google", ...props })}`; } async function getGoogleAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig6({ code, redirect_uri: base_url + "/api/auth/integration/google", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "google", ...tokens }); return { provider: "google", ...tokens }; } function getTokenReqConfig6(body) { return { url: "https://oauth2.googleapis.com/token", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/klaviyo.ts async function getKlaviyoAuthorizeUrl({ client_id, scope, base_url, client_secret, code_challenge, ...props }) { return `https://www.klaviyo.com/oauth/authorize?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/klaviyo", code_challenge_method: "S256", code_challenge, ...props })}`; } async function getKlaviyoAccessToken({ code, base_url, client_id, client_secret, callback, code_verifier }) { const { url, ...init } = getTokenReqConfig7( { code, redirect_uri: base_url + "/api/auth/integration/klaviyo", grant_type: "authorization_code", client_secret, client_id, code_verifier: code_verifier || "" }, { client_id, client_secret } ); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "klaviyo", ...tokens }); return { provider: "klaviyo", ...tokens }; } function getTokenReqConfig7(body, { client_id, client_secret }) { const BASIC = Buffer.from(`${client_id}:${client_secret}`).toString("base64"); return { url: "https://a.klaviyo.com/oauth/token", method: "POST", headers: { Authorization: `Basic ${BASIC}`, "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/linkedin.ts function getLinkedinAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://www.linkedin.com/oauth/v2/authorization?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/linkedin", ...props })}`; } async function getLinkedinAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig8({ code, redirect_uri: base_url + "/api/auth/integration/linkedin", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "linkedin", ...tokens }); return { provider: "linkedin", ...tokens }; } function getTokenReqConfig8(body) { return { url: "https://www.linkedin.com/oauth/v2/accessToken", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/notion.ts function getNotionAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://api.notion.com/v1/oauth/authorize?${qs({ response_type: "code", owner: "user", client_id, scope, redirect_uri: base_url + "/api/auth/integration/notion", ...props })}`; } async function getNotionAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig9( { code, redirect_uri: base_url + "/api/auth/integration/notion", grant_type: "authorization_code", client_secret, client_id }, { client_id, client_secret } ); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "notion", ...tokens }); return { provider: "notion", ...tokens }; } function getTokenReqConfig9(body, { client_id, client_secret }) { const BASIC = Buffer.from(`${client_id}:${client_secret}`).toString("base64"); return { url: "https://api.notion.com/v1/oauth/token", method: "POST", headers: { Authorization: `Basic ${BASIC}`, "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/pinterest.ts function getPinterestAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://www.pinterest.com/oauth/?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/pinterest", ...props })}`; } async function getPinterestAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig10( { code, redirect_uri: base_url + "/api/auth/integration/pinterest", grant_type: "authorization_code", client_secret, client_id }, { client_id, client_secret } ); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "pinterest", ...tokens }); return { provider: "pinterest", ...tokens }; } function getTokenReqConfig10(body, { client_id, client_secret }) { const BASIC = Buffer.from(`${client_id}:${client_secret}`).toString("base64"); return { url: "https://api.pinterest.com/v5/oauth/token", method: "POST", headers: { Authorization: `Basic ${BASIC}`, "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/slack.ts function getSlackAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://slack.com/oauth/v2/authorize?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/slack", ...props })}`; } async function getSlackAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig11({ code, redirect_uri: base_url + "/api/auth/integration/slack", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "slack", ...tokens }); return { provider: "slack", ...tokens }; } function getTokenReqConfig11(body) { return { url: "https://slack.com/api/oauth.v2.access", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/snapchat.ts function getSnapchatAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://accounts.snapchat.com/login/oauth2/authorize?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/snapchat", ...props })}`; } async function getSnapchatAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig12({ code, redirect_uri: base_url + "/api/auth/integration/snapchat", grant_type: "authorization_code", client_secret, client_id }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "snapchat", ...tokens }); return { provider: "snapchat", ...tokens }; } function getTokenReqConfig12(body) { return { url: "https://accounts.snapchat.com/login/oauth2/access_token", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/spotify.ts function getSpotifyAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://accounts.spotify.com/authorize?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/spotify", ...props })}`; } async function getSpotifyAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig13( { code, redirect_uri: base_url + "/api/auth/integration/spotify", grant_type: "authorization_code", client_secret, client_id }, { client_id, client_secret } ); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "spotify", ...tokens }); return { provider: "spofity", ...tokens }; } function getTokenReqConfig13(body, { client_id, client_secret }) { const BASIC = Buffer.from(`${client_id}:${client_secret}`).toString("base64"); return { url: "https://accounts.spotify.com/api/token", method: "POST", headers: { Authorization: `Basic ${BASIC}`, "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/tiktok.ts function getTiktokAuthorizeUrl({ client_id, scope, base_url, client_secret, code_challenge, ...props }) { return `https://www.tiktok.com/v2/auth/authorize?${qs({ response_type: "code", client_key: client_id, scope, redirect_uri: base_url + "/api/auth/integration/tiktok", code_challenge_method: "S256", code_challenge, ...props })}`; } async function getTiktokAccessToken({ code, base_url, client_id, client_secret, callback, code_verifier }) { const { url, ...init } = getTokenReqConfig14({ code, redirect_uri: base_url + "/api/auth/integration/tiktok", grant_type: "authorization_code", client_secret, client_key: client_id, code_verifier: code_verifier || "" }); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "tiktok", ...tokens }); return { provider: "tiktok", ...tokens }; } function getTokenReqConfig14(body) { return { url: "https://open.tiktokapis.com/v2/oauth/token/", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/providers/trustpilot.ts function getTrustpilotAuthorizeUrl({ client_id, scope, base_url, client_secret, ...props }) { return `https://authenticate.trustpilot.com?${qs({ response_type: "code", client_id, scope, redirect_uri: base_url + "/api/auth/integration/trustpilot", ...props })}`; } async function getTrustpilotAccessToken({ code, base_url, client_id, client_secret, callback }) { const { url, ...init } = getTokenReqConfig15( { code, redirect_uri: base_url + "/api/auth/integration/trustpilot", grant_type: "authorization_code", client_secret, client_id }, { client_id, client_secret } ); const res = await fetch(url, init); const tokens = await res.json(); await callback({ provider: "trustpilot", ...tokens }); return { provider: "trustpilot", ...tokens }; } function getTokenReqConfig15(body, { client_id, client_secret }) { const BASIC = Buffer.from(`${client_id}:${client_secret}`).toString("base64"); console.log(BASIC); return { url: "https://api.trustpilot.com/v1/oauth/oauth-business-users-for-applications/accesstoken", method: "POST", headers: { Authorization: `Basic ${BASIC}`, "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(body) }; } // src/helpers.ts async function generateAuthURL({ params, base_url, client_id, scope, code_challenge, ...props }) { if (!base_url) { throw new Error("base_url is required"); } if (!client_id) { throw new Error("client_id is required"); } if (params.includes("integration/google")) { const url = getGoogleAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/pinterest")) { const url = getPinterestAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/facebook")) { const url = getFacebookAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/snapchat")) { const url = getSnapchatAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/spotify")) { const url = getSpotifyAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/slack")) { const url = getSlackAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/klaviyo")) { const url = await getKlaviyoAuthorizeUrl({ client_id, scope, base_url, code_challenge, ...props }); return url; } if (params.includes("integration/notion")) { const url = getNotionAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/discord")) { const url = getDiscordAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/github")) { const url = getGithubAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/tiktok")) { const url = getTiktokAuthorizeUrl({ client_id, scope, base_url, code_challenge, ...props }); return url; } if (params.includes("integration/trustpilot")) { const url = getTrustpilotAuthorizeUrl({ client_id, scope, base_url, code_challenge, ...props }); return url; } if (params.includes("integration/accuranker")) { const url = getAccurankerAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/click-up")) { const url = getClickUpAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } if (params.includes("integration/linkedin")) { const url = getLinkedinAuthorizeUrl({ client_id, scope, base_url, ...props }); return url; } } async function generateTokens({ code, params, base_url, client_id, client_secret, callback, code_verifier }) { if (!base_url) { throw new Error("base_url is required"); } if (!client_id) { throw new Error("client_id is required"); } if (!client_secret) { throw new Error("client_secret is required"); } if (params.includes("integration/google")) { const tokens = await getGoogleAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/pinterest")) { const tokens = await getPinterestAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/facebook")) { const tokens = await getFacebookAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/snapchat")) { const tokens = await getSnapchatAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/spotify")) { const tokens = await getSpotifyAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/slack")) { const tokens = await getSlackAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/klaviyo")) { const tokens = await getKlaviyoAccessToken({ base_url, client_id, client_secret, code, code_verifier, callback }); return tokens; } if (params.includes("integration/notion")) { const tokens = await getNotionAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/discord")) { const tokens = await getDiscordAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/github")) { const tokens = await getGithubAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/tiktok")) { const tokens = await getTiktokAccessToken({ base_url, client_id, client_secret, code, callback, code_verifier }); return tokens; } if (params.includes("integration/trustpilot")) { const tokens = await getTrustpilotAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/accuranker")) { const tokens = await getAccurankerAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/click-up")) { const tokens = await getClickUpAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } if (params.includes("integration/linkedin")) { const tokens = await getLinkedinAccessToken({ base_url, client_id, client_secret, code, callback }); return tokens; } } // src/auth.ts function NextIntegrate(auth) { return { auth }; } // src/handler.ts function base64URLEncode(str) { return str.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } async function generateCodes() { const array = new Uint8Array(32); crypto.getRandomValues(array); const verifier = base64URLEncode(Buffer.from(array)); async function sha256(plain) { const encoder = new TextEncoder(); const data = encoder.encode(plain); return await crypto.subtle.digest("SHA-256", data); } async function generateCodeChallenge(verifier2) { const hashed = await sha256(verifier2); return base64URLEncode(Buffer.from(hashed)); } return { code_verifier: verifier, code_challenge: await generateCodeChallenge(verifier) }; } var placerholder = { auth_url: "/", redirect: "/", options: null, codeChallenge: "", callback: () => { } }; async function handler({ context, req, auth, cookieStore, debug = false }) { const { code_verifier, code_challenge } = await generateCodes(); const params = (await context.params).integration; const code = req.nextUrl.searchParams.get("code"); if (debug) { console.log("code_verifier", code_verifier); console.log("code_challenge", code_challenge); } const newName = req.nextUrl.searchParams.get("name"); const newRedirect = req.nextUrl.searchParams.get("redirect"); if (debug) { console.log("Name", newName); console.log("Redirect", newRedirect); } if (!code) { if (debug) { console.log("Setting cookies"); } (await cookieStore).set("name", newName || ""); (await cookieStore).set("redirect", newRedirect || ""); (await cookieStore).set("code_verifier", code_verifier); } const name = (await cookieStore).get("name")?.value; const redirect = (await cookieStore).get("redirect")?.value; if (debug) { console.log("Getting newly set cookies"); console.log("Name", name); console.log("Redirect", redirect); } if (!name) { if (debug) { console.log("Name is required"); } return { ...placerholder, error: "Name is required" }; } if (debug) { console.log("Getting providers"); } const { providers, base_url } = auth; if (debug) { console.log("Providers", providers); console.log("Base URL", base_url); } const provider = providers.find((p) => p.provider === params[1]); if (debug) { console.log("Provider", provider); } const integration = provider?.integrations.find((i) => i.name === name); if (debug) { console.log("Integration", integration); } if (!provider || !name || !integration) { if (debug) { console.log("Invalid integration"); } return { ...placerholder, error: "Invalid integration" }; } const options = { ...integration.options, client_id: provider.client_id, client_secret: provider.client_secret, code_challenge, base_url, params: params.join("/") }; if (debug) { console.log("Options", options); } const auth_url = await generateAuthURL(options); if (debug) { console.log("Auth URL", auth_url); } if (!auth_url) { if (debug) { console.log("Invalid redirect URL"); } return { ...placerholder, error: "Invalid redirect URL" }; } return { auth_url, redirect: redirect?.replaceAll("__HASH__", "#").replaceAll("__AND__", "&") || "/", options, callback: integration.callback, error: null }; } async function exchange({ options, callback, code, cookieStore, debug = false }) { if (!options) return; if (debug) { console.log("Options", options); } const code_verifier = (await cookieStore).get("code_verifier")?.value; if (debug) { console.log("Code verifier", code_verifier); } const tokens = await generateTokens({ ...options, code_verifier, callback, code }); if (debug) { console.log("Tokens", tokens); } (await cookieStore).delete("name"); (await cookieStore).delete("redirect"); (await cookieStore).delete("code_verifier"); if (debug) { console.log("Cleared cookies"); } return tokens; } async function clearCookies({ cookieStore }) { (await cookieStore).delete("name"); (await cookieStore).delete("redirect"); (await cookieStore).delete("code_verifier"); } // src/integrate.ts function integrate({ name, provider, redirect, base_path }) { if (!name || !provider || !redirect) { throw new Error("Name, redirect and provider are required"); } let redirectUrl = redirect.replaceAll("#", "__HASH__").replaceAll("&", "__AND__"); let url = `/api/auth/integration/${provider}?name=${name}&redirect=${redirectUrl}`; if (base_path) { url = `${base_path}/api/auth/integration/${provider}?name=${name}&redirect=${redirectUrl}`; } return url; } export { NextIntegrate, NextRequest, clearCookies, exchange, generateAuthURL, generateTokens, handler, integrate };